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.

Bash
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:

TOML
[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"
FieldRequiredNotes
nameYesMust be unique on crates.io
versionYesSemVer format
editionRecommended2021 or 2024
descriptionYesPublish fails without it
licenseYesSPDX identifier
repositoryRecommendedGitHub/GitLab URL
readmeOptionalDefaults to README.md
keywordsOptionalUp to 5, helps discoverability
categoriesOptionalFrom 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-MIT
  • LICENSE-APACHE

In Cargo.toml:

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:

Markdown
# 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:

Bash
cargo publish --dry-run

This will:

  1. Package your crate (shows which files are included)
  2. Build from the package to verify it compiles
  3. Not actually upload anything

Common Errors

The exact wording varies between Cargo versions; these are summaries.

TEXT
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

Bash
cargo publish

On success you'll see:

TEXT
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.

TEXT
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:

TOML
# 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 version from crates.io

Publish in Order

Bash
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:

TEXT
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:

  1. Bump version in Cargo.toml
  2. Update the version in dependent crates too
  3. cargo publish
TOML
# 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 yank them: new dependencies on that version are blocked, while projects that already have it in Cargo.lock keep working.
  • Crate names are first-come, first-served.
  • Sensitive data (API keys, .env) cannot be removed once published.
Bash
# 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:

TOML
[package]
exclude = ["tests/fixtures/*", "benches/*", ".github/*"]

Or whitelist only what you need:

TOML
[package]
include = ["src/**/*", "Cargo.toml", "LICENSE-*", "README.md"]

Pre-Publish Checklist

  • Cargo.toml has name, version, description, license, repository
  • LICENSE file(s) exist in project root
  • README.md exists
  • cargo publish --dry-run passes
  • No sensitive data included in the package
  • For workspaces: dependencies already published to crates.io
  • For workspaces: path dependencies have version specified