How to publish crate to crates.io

Publish a Rust crate to crates.io by verifying your Cargo.toml, running cargo package, and executing cargo publish.

The contract you sign with the ecosystem

You spent three weekends building a parser. It works. It's fast. You want the world to use it. You type cargo publish and hit Enter. The terminal spits back a wall of red text about missing metadata, or worse, it succeeds and you realize you just uploaded your .env file with your API keys. Publishing isn't just uploading a tarball. It's signing a contract with every developer who will depend on your code.

Crates.io is the central registry for Rust. It's not a git repository. It's an immutable archive of tarballs. Once you publish version 0.1.0, that version is frozen in time. You cannot edit it. You cannot delete it. You can only yank it, which marks it as unavailable for new downloads but preserves the history for anyone who already depends on it. This rule protects the ecosystem. If you could edit a published crate, you could silently break every project that relies on it. The consequence is that you must verify everything before you hit publish.

The pre-flight check

Before you upload anything, run cargo package. This command simulates the publishing process. It collects all the files that will go into the crate, validates your metadata, and builds the tarball locally. If cargo package fails, cargo publish will fail. Treat cargo package as your pre-flight checklist.

cargo package

When this runs, cargo creates a .crate file in target/package/. You can inspect this file to see exactly what the world will receive. The command also checks for common mistakes. It warns you if your Cargo.toml is missing required fields. It warns you if files in your repository are not tracked by git, which means they won't be included in the crate. It warns you if your .gitignore is excluding files that should be published.

Convention aside: Run cargo fmt and cargo clippy before packaging. The community expects formatted code and clean clippy output. Publishing a crate with style violations or clippy warnings invites noise in your issue tracker. Fix the warnings first.

The metadata contract

The Cargo.toml file is your contract with the registry. Every field has a purpose. The name must be unique across all crates. The version must follow semantic versioning. The edition must be set. The description is required. The license is required. Missing any of these will cause cargo publish to reject your crate with a metadata error.

[package]
name = "my-awesome-lib"
version = "0.1.0"
edition = "2021"
// Description is mandatory for crates.io.
// Keep it under 100 characters for the best display.
description = "A fast parser for weird formats"
// License is mandatory. Use SPDX identifiers.
// MIT OR Apache-2.0 is the Rust community standard.
license = "MIT OR Apache-2.0"
// README points to the file displayed on crates.io.
readme = "README.md"
// Homepage links to your project site.
homepage = "https://github.com/user/my-awesome-lib"
// Repository links to the source code.
repository = "https://github.com/user/my-awesome-lib"
// Documentation links to docs.rs.
documentation = "https://docs.rs/my-awesome-lib"

The readme field should point to a file that renders well on crates.io. Often this is the same as README.md, but sometimes you strip badges or add a note about installation. The homepage, repository, and documentation fields help users find your project. The documentation field usually points to docs.rs, which automatically builds documentation for every published crate.

Convention aside: The Rust community expects MIT OR Apache-2.0 for permissive licensing. If you use a different license, include a LICENSE file in your repository and set license-file = "LICENSE" in Cargo.toml. Crates.io validates SPDX identifiers. If your license isn't in the SPDX list, you must use license-file.

The minimum supported Rust version

The rust-version field is becoming standard. It declares the minimum Rust version your crate supports. This helps users decide if they can adopt your crate without upgrading their toolchain. Set this field to the oldest version you test against. Lying about your MSRV breaks trust. Users will file issues when their build fails, and you'll look careless.

[package]
// ... other fields ...
// Declare the minimum Rust version your crate compiles against.
rust-version = "1.70"

When you set rust-version, cargo uses this value to check compatibility. If a user tries to build your crate with an older Rust version, they get a clear error message instead of a confusing compiler error. This field is especially important for libraries that use newer language features.

Convention aside: Test your crate against the declared rust-version in CI. If your CI only tests against the latest stable Rust, your rust-version claim is unverified. Add a job that builds with the minimum version to keep your promise.

Keywords and categories

Crates.io uses keywords and categories to help users discover your crate. Keywords are free-form strings. You can specify up to five. Categories are from a fixed list maintained by the crates.io team. You can specify up to five categories.

[package]
// Keywords help search. Max 5.
// Use terms users are likely to search for.
keywords = ["parser", "fast", "weird", "format", "text"]
// Categories help discovery. Max 5.
// Must be from the official crates.io category list.
categories = ["parsing", "command-line-utilities"]

Choose keywords carefully. They appear in search results. If you use generic keywords like "rust" or "library", you dilute the signal. Use specific terms that describe what your crate does. Categories are more structured. They help users browse crates by topic. Check the official category list before publishing to ensure your choices are valid.

Pitfalls and compiler errors

The .gitignore file controls what goes into your crate. Any file ignored by git is excluded from the published tarball. This catches most accidental leaks, like .env files or build artifacts. However, it also means generated files must be in git or generated during the build. If your crate depends on a file that isn't in git, the build will fail for everyone else.

Another pitfall is the --allow-dirty flag. This flag lets you publish even if your working directory has uncommitted changes. Never use this flag for real publishes. It defeats the safety check that ensures your published crate matches a specific commit. If you need to publish dirty changes, commit them first.

If your crate fails to build during publishing, you'll see rustc errors. These can happen if your code compiles locally but fails on the registry's build environment. Common errors include missing dependencies or platform-specific issues.

If you forget a trait bound in your public API, the compiler rejects you with E0277 (trait bound not satisfied). This can happen if your code uses a type that doesn't implement a required trait on some platform. Always test your crate with cargo test and cargo doc before publishing.

If you try to publish a crate with a name that's already taken, cargo rejects you with a "crate name is already taken" error. You can't override existing names. You must choose a unique name or use a different scope.

Run cargo package until it succeeds. The tarball is what the world gets, not your local branch.

Publishing the crate

Once cargo package passes, you're ready to publish. You need an API token for crates.io. You can generate one on your crates.io account settings. Run cargo login with your token to store it locally.

cargo login YOUR_API_TOKEN

The token is stored in ~/.cargo/credentials. It persists across sessions. You don't need to log in every time.

cargo publish

This command uploads the tarball to crates.io. It shows the crate size and upload progress. Once it succeeds, your crate is live. You can view it on crates.io. Docs.rs will pick it up automatically and build documentation. You'll get an email when the documentation is ready.

Convention aside: Add a CHANGELOG.md to your repository. It's not required by cargo, but it's required by good citizenship. Document every breaking change, new feature, and bug fix. Users rely on the changelog to understand how to upgrade.

Decision matrix

Use cargo publish when your crate is ready for public consumption and you have verified the tarball with cargo package. Use publish = false when the crate is internal to a workspace or a private project that shouldn't appear in the registry. Use cargo yank when you discover a critical bug in a published version and need to prevent new projects from depending on it. Use cargo login to set your API token before publishing; the token is stored in ~/.cargo/credentials and persists across sessions. Use cargo package --list to inspect the files included in the tarball without building it. Use rust-version to declare your minimum supported Rust version and test against it in CI.

Set rust-version and test against it. Your MSRV is a promise to your users.

Where to go next