How to Publish a Rust Library to crates.io
You wrote a Rust library. Now you want others to use it with cargo add. Here are the steps to publish to crates.io, from setup to multi-crate workspaces.
1. Create a crates.io Account
Sign in at crates.io with your GitHub account. Then go to Account Settings → API Tokens and generate a token.
Log in from your terminal. cargo login reads the token from standard input; passing it as an argument leaves it in your shell history, so paste it at the prompt instead.
cargo login
# paste the token at the prompt
This only needs to be done once. The token is saved to ~/.cargo/credentials.toml.
2. Required Cargo.toml Fields
crates.io will reject your publish if these are missing:
[package]
name = "my-awesome-lib"
version = "0.1.0"
edition = "2024"
description = "A brief description (required!)"
license = "MIT OR Apache-2.0"
repository = "https://github.com/username/my-awesome-lib"
| Field | Required | Notes |
|---|---|---|
name | Yes | Must be unique on crates.io |
version | Yes | SemVer format |
edition | Recommended | 2021 or 2024 |
description | Yes | Publish fails without it |
license | Yes | SPDX identifier |
repository | Recommended | GitHub/GitLab URL |
readme | Optional | Defaults to README.md |
keywords | Optional | Up to 5, helps discoverability |
categories | Optional | From crates.io/categories |
3. License
Dual MIT + Apache 2.0 licensing is common in the Rust ecosystem. Create two files in your project root:
LICENSE-MITLICENSE-APACHE
In Cargo.toml:
license = "MIT OR Apache-2.0"
Why dual? Users can pick whichever suits them. MIT is simple and permissive. Apache 2.0 adds patent protection. Most major Rust crates (tokio, serde, clap) use this combination.
4. README.md
This is displayed on your crates.io page. At minimum include:
# my-awesome-lib
One-line description.
## Installation
\```toml
[dependencies]
my-awesome-lib = "0.1"
\```
## Usage
\```rust
use my_awesome_lib::something;
fn main() {
// example
}
\```
## License
MIT OR Apache-2.0
5. Dry Run
Always test before publishing:
cargo publish --dry-run
This will:
- Package your crate (shows which files are included)
- Build from the package to verify it compiles
- Not actually upload anything
Common Errors
The exact wording varies between Cargo versions; these are summaries.
Missing description
→ Add description to Cargo.toml
Missing license
→ Add license to Cargo.toml
Package exceeds the crates.io upload size limit
→ Use [package] exclude in Cargo.toml to remove large files
6. Publish
cargo publish
On success you'll see:
Uploading my-awesome-lib v0.1.0
Published my-awesome-lib v0.1.0 at registry `crates-io`
Anyone can now run cargo add my-awesome-lib.
7. Publishing a Multi-Crate Workspace
If your project has multiple crates, you must publish them in dependency order.
my-core → no deps (publish first)
my-render → depends on core (publish second)
my-lib → depends on both (publish third)
my-cli → depends on all (publish last)
Specify Both path and version
Locally, you reference crates by path. But crates.io needs a version. Use both:
# Before (local only — won't publish)
my-core = { path = "../my-core" }
# After (publishable)
my-core = { version = "0.1.0", path = "../my-core" }
How it works:
- Local build: Cargo uses the
path - Published crate: Cargo resolves by
versionfrom crates.io
Publish in Order
cargo publish -p my-core
cargo publish -p my-render
cargo publish -p my-lib
cargo publish -p my-cli
Use the -p flag to specify which crate. cargo publish waits until the uploaded package appears in the index before it exits, so you don't need to pause between steps. You can also pass -p several times or use --workspace to publish multiple crates in one command; see the cargo publish docs.
Rate Limits
crates.io throttles rapid publishing. If you publish too many crates in succession:
error: 429 Too Many Requests
Please try again after [timestamp]
If you hit this, retry after the time given in the message.
8. Version Updates
To release a new version:
- Bump
versioninCargo.toml - Update the version in dependent crates too
cargo publish
# For 0.x versions
# Compatible change (fix or feature): 0.1.0 → 0.1.1
# Breaking change: 0.1.0 → 0.2.0
version = "0.1.1"
Cargo's compatibility rule, per the Cargo docs: versions are compatible if their left-most non-zero component is the same.
- 1.0 and above: patch (1.2.3 → 1.2.4) is a fix, minor (1.2.0 → 1.3.0) is a compatible feature, major (1.x → 2.0) is breaking.
- 0.x: 0.1.0 → 0.1.1 is compatible, and 0.1.0 → 0.2.0 is breaking. Before 1.0, the minor number plays the role of major.
9. Things You Can't Undo
- Published versions can't be overwritten or deleted. You can only
yankthem: new dependencies on that version are blocked, while projects that already have it inCargo.lockkeep working. - Crate names are first-come, first-served.
- Sensitive data (API keys, .env) cannot be removed once published.
# Yank a version (prevent new installs)
cargo yank --version 0.1.0
# Undo a yank
cargo yank --version 0.1.0 --undo
10. Excluding Files
Prevent unnecessary files from being packaged:
[package]
exclude = ["tests/fixtures/*", "benches/*", ".github/*"]
Or whitelist only what you need:
[package]
include = ["src/**/*", "Cargo.toml", "LICENSE-*", "README.md"]
Pre-Publish Checklist
-
Cargo.tomlhas name, version, description, license, repository - LICENSE file(s) exist in project root
- README.md exists
-
cargo publish --dry-runpasses - No sensitive data included in the package
- For workspaces: dependencies already published to crates.io
- For workspaces: path dependencies have version specified