Email me

Guide · 8 min read

Filed under Rust Guides

How to publish a Rust crate to crates.io

Steps
9
Hands-on
30 min
Reading
8 min
Checked
Oct 23

The short version

Make a crates.io account, create an API token, run cargo login, add description and license to Cargo.toml, check the package with cargo publish --dry-run, then run cargo publish. Read the next section before you do, though: every version you publish is permanent. You can yank it, but you can never edit or delete it.

Title card reading How to publish a Rust crate. Publishing is forever. Read this first.
Nine steps from an empty crates.io account to a crate that publishes itself from CI. Image: Tensorust, made for this page.

What you need

  • Rust and Cargo installed (rustup)
  • A GitHub account to sign in to crates.io
  • A verified email on crates.io
  • A crate that builds, with a Cargo.toml you can edit

Part of the article crates.io download count: 80% of ours arrived with a new version. This page answers one question from it in depth.

On this page · 13 sections
  1. Before you start: three facts that change how you publish
  2. Make an account and verify your email
  3. Create a scoped token and run cargo login
  4. Fill in the Cargo.toml metadata
  5. See exactly what will ship
  6. Publish
  7. Publishing a workspace (and the 429)
  8. What happens after you publish
  9. Publish from CI without a stored token
  10. Fixing a bad release
  11. What goes wrong (the short list)
  12. Related
  13. Sources

We have done this 235 times. Between March and October 2026 we published 235 versions of 69 crates, including one release run on October 4 that pushed 68 versions in a single day, and a README pass on October 21 that republished 53 crates one at a time. This guide is the checklist those runs left behind, with the parts the official docs mention only in passing.

Before you start: three facts that change how you publish

  • Publishing is forever. The Cargo Book says it plainly: "The version can never be overwritten, and the code cannot be deleted." If a .env file or an API key ships inside your .crate, it is public until the heat death of the universe. Rotate the key; yanking does not help.
  • Names are first come, first served. Once a name is taken it can never be reused for another crate. We hit this ourselves: our memory-graph crate could not ship as mem-graph because another project already owns that name on crates.io.
  • There is no limit on versions, but there is a rate limit on uploads. crates.io limits publishes per user with a token bucket (crates.io PR #1681). Hit it and cargo publish fails with HTTP 429 and a time to retry.

Step 01: Make an account and verify your email

Log in at crates.io with GitHub (it is the only login option today) and verify your email address on the Account Settings page. crates.io refuses to publish from an account without a verified email.

Step 02: Create a scoped token and run cargo login

Go to crates.io/settings/tokens and create a token. Since 2023 tokens can be limited to specific operations, specific crate names and an expiry date. For a laptop, a token that can publish new crates and update existing ones, with an expiry, is plenty. You will see the token once; copy it before you leave the page.

cargo login

Treat that file like a password. cargo logout removes it.

Step 03: Fill in the Cargo.toml metadata

crates.io needs description and either license (an SPDX expression such as MIT OR Apache-2.0) or license-file. The Cargo Book also lists homepage, repository and readme, and recommends keywords (up to five) and categories. Here is the real [package] block of our most downloaded crate:

Code card showing the package section of the fin-primitives Cargo.toml: name, version 2.15.0, edition 2021, rust-version 1.81, description, repository, homepage, documentation, readme, license MIT, keywords, categories and an include list.
A real published Cargo.toml. The include list is what keeps junk and secrets out of the package. Credit: Tensorust, from our fin-primitives repo.

The line that matters most is the last one. include is an allow list: only those paths go into the .crate. Without it, Cargo packages everything in the crate folder that git does not ignore, and a stray dataset or screenshot folder can push you past crates.io's 10 MB limit on the .crate file.

Two more fields worth setting: rust-version (your minimum supported Rust, so users on old compilers get a clear error) and documentation if you host docs anywhere other than docs.rs.

Step 04: See exactly what will ship

Before every first publish, run both of these:

cargo package --list      # every file that will go into the .crate
cargo publish --dry-run   # package + compile the packaged copy, no upload
Diagram of five boxes: checks, package, verify build, upload, and index plus forever. The first three run on your machine, the fourth over the network, the fifth on crates.io.
cargo publish runs five stages. A dry run stops before the upload, so everything that can fail locally fails there. Credit: Tensorust, steps from the Cargo Book.

The dry run unpacks the .crate into a temp folder and builds it from there. That is the point: if your code reads a file that include left out, or a path = "../shared" dependency has no version, the build fails on your machine instead of for your first user. Cargo also refuses a dirty git tree unless you pass --allow-dirty, which is a feature, not an obstacle: it stops you publishing code you have not committed.

Step 05: Publish

cargo publish

When it returns, the version is in the index. Anyone can now cargo add it, and docs.rs starts building its documentation automatically.

Step 06: Publishing a workspace (and the 429)

If your project is a workspace, every member is its own crate, and members have to go up in dependency order. Since Rust 1.90 (September 18, 2025), cargo publish --workspace does the ordering for you. The release notes warn that it is still not atomic: a network or server failure can leave half a workspace published, so re-run it rather than bumping versions.

The rate limit is where big workspaces hurt. crates.io does not publish its current numbers. From our own runs, new crate names are the tight one: when we published CoreScout 1.2.0 on October 4, 2026 (33 crates, 19 of them brand new names), we paced it around one new crate every ten minutes. For the October 21 republish of 53 crates, the script waited 65 seconds between crates and slept 10 minutes on any 429. Our real loop, trimmed:

p = subprocess.run(["cargo", "publish"], cwd=crate_dir, capture_output=True, text=True)
if p.returncode:
    if "429" in p.stderr or "Too Many" in p.stderr:
        time.sleep(600)          # rate limited: wait, don't hammer
    continue
time.sleep(65)                   # spacing between successful publishes

If you have a genuine one-time need, the 429 message itself tells you how to ask the crates.io team for a temporary higher limit.

Step 07: What happens after you publish

Something downloads every new version within hours, whether or not a human has seen it. Across 181 of our versions, the mean was 13.45 downloads on day 0 (median 10), then about 1 on day 1 and almost nothing after. We dug into why in the crates.io download count article.

Bar chart of mean downloads per new version by day since publish: 13.45 on day 0, 1.21 on day 1, 0.02 on day 2, then under 1.2 on each later day.
A new version collects about 13 downloads on its first day no matter what is in it. Credit: Tensorust, from the crates.io API, Oct 17 2026.
Screenshot of the crates.io page for fin-primitives showing 258 recent downloads, 561 all-time downloads, 13 versions, and a 90-day chart where each colored spike is a different version.
What 13 releases look like on a crate page: each spike is a publish day. Credit: Screenshot of crates.io, Oct 17 2026.

So do not read your first-day number as interest. Watch the quiet days between releases and the Dependents tab instead. Our free crate downloads checker splits any crate's chart into publish-day and quiet-day downloads for you.

Step 08: Publish from CI without a stored token

Once the crate exists, stop pasting tokens into CI secrets. crates.io supports Trusted Publishing (RFC 3691): you tell crates.io which GitHub repository and workflow may publish, and the workflow exchanges its OIDC identity for a short-lived token. The first release still has to be done by hand.

YAML workflow that runs on v tags, grants id-token write permission, uses rust-lang/crates-io-auth-action at v1 with id auth, then runs cargo publish with CARGO_REGISTRY_TOKEN set from the auth step output.
Trusted Publishing: the workflow asks crates.io for a token at run time, and the action revokes it when the job ends. Credit: Tensorust, after the example in the Rust blog.

The crates-io-auth-action revokes the token when the job finishes. It is the same idea as an App Store Connect API key in CI, minus the stored secret: no long-lived credential sits in your repo settings at all. In January 2026 crates.io added GitLab CI support (gitlab.com only) and an option to turn off token-based publishing for a crate entirely.

Step 09: Fixing a bad release

cargo yank --version 1.0.1          # new projects won't pick it
cargo yank --version 1.0.1 --undo   # changed your mind

A yank does not delete anything. Projects with that version in their Cargo.lock keep building, and the code stays downloadable. The fix for a broken release is a new patch version. The fix for a leaked secret is rotating the secret.

Troubleshooting

What goes wrong (the short list)

What you see Cause Fix
Publish refused, asks for a verified email Email not verified Verify it in crates.io Account Settings
Publish refused because you are not an owner Someone already owns that name Pick another name; a taken name never frees up
Upload rejected as too large .crate over 10 MB Add an include list; check cargo package --list
Cargo says a dependency has no version A path dependency without version Add version = "x.y" next to path
HTTP 429 Too Many Requests Publish rate limit Wait the time the message gives; space out workspace publishes
"files in the working directory contain changes that were not yet committed" Uncommitted changes Commit, or pass --allow-dirty if you mean it

Our take: do not publish to pump numbers. It works (we measured it working), which is exactly why nobody evaluating your crate trusts the download count. Publish when something changed, and put the effort into the README and examples, because those are what turn a fetch into a dependent.

Sources

All checked October 23, 2026.

Primary

Secondary

  • Rust users forum thread quoting the 429 message. crates.io does not publish its current rate-limit numbers; the pacing on this page is from our own runs.

Our own data

  • 69 crates, 235 versions, the October 4 run of 68 versions and the 13.45 day-0 mean across 181 versions: crates.io download count, data pulled from the crates.io API on October 17, 2026.
  • The fin-primitives Cargo.toml, the republish loop (65 seconds between crates, 10 minutes on a 429, 53 crates on October 21, 2026) and the CoreScout 1.2.0 release (33 crates, 19 new names, October 4, 2026): our release scripts and notes.

Images

  • Title card, Cargo.toml card, publish diagram and workflow card: made by Tensorust for this page, using the Archivo, JetBrains Mono and Silkscreen fonts (SIL Open Font License). The authors line is left out of the Cargo.toml card.
  • Publish-day chart and the crates.io screenshot: from the parent article, our crates.io API data and our capture of crates.io/crates/fin-primitives on October 17, 2026.

Credit

Do this next

Every number on this page links to its source and was checked on Oct 23, 2026. Spot an error or a newer figure? Email matt@tensorust.com and it gets fixed. Related: Crate downloads checker, Our open-source crates.

Read next

Back to The Manual →   All articles →

Get the next one by email.

What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.

No spam, never sold. Every email has a one-click unsubscribe.

Get the receipts by email.

What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.

No spam, never sold. Every email has a one-click unsubscribe.

Popular in the shop

All 124 tools →

Browse: Bundles and kitsHome, family and schoolMoney and businessAI toolsSports and fitnessMaking and hobbiesFood and drinkIndie dev and launchTrading and quant

Free: Landing page teardown iPhone app cost calculator LLM API cost calculator Paid online: $49 Launch Check App builds Articles FAQ