The Cargo Guide
Publishing Packages
Why Publishing Is Its Own Workflow
Publishing a crate is not just a final cargo publish command. It is a workflow that includes packaging, verification, metadata quality, dependency readiness, release ordering, and registry policy.
A useful mental model is:
cargo packagecreates and checks the distributable crate archivecargo publishpackages and uploads it to a registry- your manifest, repository state, and dependency graph all affect whether the result is healthy and publishable
What cargo package Does
cargo package creates the distributable .crate archive for the current package and performs verification checks.
Example:
cargo packageThis is a useful command even when you are not ready to publish yet, because it lets you see what Cargo would actually ship.
What cargo publish Does
cargo publish packages the crate and uploads it to a registry.
Example:
cargo publishA strong practical model is:
cargo packageis the local packaging and verification stepcargo publishis the upload step built on top of packaging
This is why it is often wise to treat packaging as a first-class pre-publish workflow instead of jumping directly to publish.
Publishing Is Permanent Per Version
A critical publishing principle is that once a version is published to crates.io, that exact version cannot be overwritten. That means release hygiene matters before the upload step.
A practical implication is:
- verify before publishing
- do not treat crates.io publishing like a mutable package repository where mistakes can simply be replaced
A Small Example Package
Suppose you start with a small library crate:
cargo new publish_demo --lib
cd publish_demoInitial manifest:
[package]
name = "publish_demo"
version = "0.1.0"
edition = "2024"
license = "MIT"
readme = "README.md"
repository = "https://github.com/example/publish_demo"
description = "Small examples for Cargo publishing workflows"
[dependencies]Simple code:
pub fn square(x: i32) -> i32 {
x * x
}This is enough to demonstrate packaging, metadata quality, and publishing checks.
Dry Runs
A dry run is one of the safest ways to inspect a publish workflow before doing the real upload.
Examples:
cargo publish --dry-run
cargo packageA useful mental model is:
- dry runs let you test the publishing path without making the release permanent
- they are especially useful for verifying included files, manifest metadata, and dependency readiness
Package Verification
Packaging is not just archive creation. Cargo also performs verification steps when packaging and publishing.
A strong practical habit is to treat cargo package as a verification command:
cargo packageIf the package cannot be packaged cleanly, that is usually a sign that publishing would also be unhealthy.
Inspecting What Will Be Shipped
One of the most useful inspection commands is:
cargo package --listThis shows which files will be included in the package archive.
A good mental model is:
- your repository contains many files
- your published crate should usually contain only the files needed to build, document, and legally distribute the package
include and exclude Behavior
The manifest can control packaged file selection with include and exclude.
Example using exclude:
[package]
name = "publish_demo"
version = "0.1.0"
edition = "2024"
exclude = [
"notes/",
"drafts/",
"*.psd"
]Example using include:
[package]
name = "publish_demo"
version = "0.1.0"
edition = "2024"
include = [
"src/**",
"Cargo.toml",
"README.md",
"LICENSE*"
]A practical rule is that include is often the stricter and more explicit approach, while exclude is often easier when you only need to keep a few things out.
Why File Selection Matters
Package file selection matters for several reasons:
- package size
- repository hygiene
- accidental leakage of internal files
- build reproducibility for downstream users
- legal and documentation completeness
A published package should usually be intentionally small rather than a snapshot of every file in the repository.
Package Size Hygiene
Package size hygiene means keeping the distributable crate focused and lean.
Healthy package contents often include:
- source code
- manifest files
- README
- license files
- minimal docs or schema inputs needed for building
Unhealthy package contents often include:
- large binary assets not required for build or use
- editor artifacts
- internal notes and drafts
- unused generated files
A practical check is:
cargo package --listPre-Publish Metadata Quality
Publishing quality is strongly affected by package metadata. A healthy published crate usually has at least:
descriptionlicenseorlicense-filereadmerepository- often
documentation,homepage,keywords, andcategories
Example:
[package]
name = "publish_demo"
version = "0.1.0"
edition = "2024"
description = "Small examples for Cargo publishing workflows"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/example/publish_demo"
documentation = "https://docs.rs/publish_demo"
keywords = ["cargo", "publishing"]
categories = ["development-tools"]This metadata does not just make the manifest prettier. It affects discoverability and trust.
README, License, and Docs Quality
A crate's README, license declaration, and documentation quality are part of the publishing workflow, not just afterthoughts.
A good README should usually explain:
- what the crate does
- how to use it
- installation or dependency instructions
- feature notes when relevant
A good license setup should clearly declare legal usage.
A good docs story usually includes meaningful API documentation and, when appropriate, working examples.
A Better README-Oriented Package
A more publishing-ready crate might look like this:
[package]
name = "publish_demo"
version = "0.1.0"
edition = "2024"
description = "Small examples for Cargo publishing workflows"
license = "MIT"
readme = "README.md"
repository = "https://github.com/example/publish_demo"And README.md might include:
# publish_demo
A small crate that demonstrates packaging and publishing workflows.Even a simple crate becomes easier to trust when these basics are in place.
Publish Restrictions in the Manifest
Cargo supports publish restrictions directly in the manifest.
To prevent publication entirely:
[package]
name = "internal_tool"
version = "0.1.0"
edition = "2024"
publish = falseThis is especially useful for internal crates, experiments, or workspace members that should never be uploaded.
Restricting Publication to Specific Registries
Cargo also supports restricting publication to specific registries.
Example:
[package]
name = "internal_sdk"
version = "0.1.0"
edition = "2024"
publish = ["company"]This is useful when a crate is meant for an internal or alternate registry rather than crates.io.
Choosing a Registry at Publish Time
Publishing can also be directed at a specific registry with command flags.
Examples:
cargo publish --registry company
cargo publish --index sparse+https://packages.example.com/index/A useful mental model is:
- manifest-level restrictions define allowed publication policy
- command-line flags choose the actual destination for a specific publish command
Dependency Readiness Before Publishing
A crate is not publish-ready if its dependency graph is not publish-ready.
Common publishing issues include:
- depending on local path-only crates that are not published or not publishable
- depending on internal crates in the wrong order
- relying on temporary
[patch]overrides or local-only development setups
A practical publishing mindset is:
- think about the publishability of the whole dependency chain, not just the top crate
Release Ordering for Dependency Chains
If crate app_support depends on crate core_support, then the release order usually matters.
For example:
core_support -> app_supportIn practice, that often means:
cargo publish -p core_support
cargo publish -p app_supportThis is especially important in workspaces or crate families where several publishable packages depend on one another.
Version Bumps
Publishing a new release generally means updating the package version in Cargo.toml.
Example:
[package]
name = "publish_demo"
version = "0.2.0"
edition = "2024"A useful practical model is:
- change the code and docs
- update the version intentionally
- verify the package
- then publish
Version bumps are part of release communication, not just a Cargo syntax detail.
Tags and Release Coordination
Cargo itself does not create source-control tags for you, but tags are often part of a healthy publishing workflow.
A practical release rhythm may include:
git tag v0.2.0
git push origin v0.2.0
cargo publishThe exact order may vary by team policy, but the key idea is that source control, version numbers, and published artifacts should line up cleanly.
Workspace Publishing Patterns
In workspaces, Cargo supports package selection for publishing.
Examples:
cargo publish -p core_support
cargo package -p core_support
cargo package --workspaceThis is useful because many workspaces contain a mix of publishable crates and internal-only crates.
Publishing from a Workspace
Suppose you have this workspace:
my_workspace/
āāā Cargo.toml
āāā crates/
ā āāā core_support/
ā ā āāā Cargo.toml
ā ā āāā src/lib.rs
ā āāā app_support/
ā ā āāā Cargo.toml
ā ā āāā src/lib.rs
ā āāā internal_tool/
ā āāā Cargo.toml
ā āāā src/main.rsRoot manifest:
[workspace]
members = ["crates/*"]
resolver = "3"Internal tool manifest:
[package]
name = "internal_tool"
version = "0.1.0"
edition = "2024"
publish = falseThen publishing may look like:
cargo publish -p core_support
cargo publish -p app_supportwhile internal_tool stays intentionally unpublished.
Partial Publishing Patterns
A healthy workspace often mixes:
- publishable library crates
- internal support crates marked
publish = false - binaries intended for deployment, not registry publication
This is why explicit publishability boundaries are so useful.
Example internal-only crate:
[package]
name = "internal_support"
version = "0.1.0"
edition = "2024"
publish = falsePre-Publish Checks
A practical pre-publish checklist often includes:
- verify tests pass
- verify docs build
- verify package contents
- verify metadata quality
- verify dependency ordering
- verify version bump and changelog alignment
In command form, that often looks like:
cargo test
cargo doc
cargo package --list
cargo publish --dry-runThis is the stage where most publishing mistakes are cheapest to catch.
A Practical Single-Crate Release Flow
A simple release flow for one crate might look like this:
cargo test
cargo doc
cargo package --list
cargo publish --dry-run
cargo publishIf version and source-control tagging are part of the process, they typically surround these checks in a deliberate release sequence.
A Practical Workspace Release Flow
A simple multi-crate release flow might look like this:
cargo test --workspace
cargo package -p core_support
cargo publish --dry-run -p core_support
cargo publish -p core_support
cargo package -p app_support
cargo publish --dry-run -p app_support
cargo publish -p app_supportThis keeps dependency-chain release ordering explicit.
What Can Go Wrong at Publish Time
Common publishing failures include:
- missing or weak metadata
- files unexpectedly excluded from the package
- huge package contents from sloppy
includeorexcludepolicy - unpublished dependency chains
- internal-only path dependencies left in place
- forgetting that a version is permanent once published
These are mostly workflow problems rather than Cargo problems.
A Full Example Manifest
Here is a more publishing-oriented Cargo.toml:
[package]
name = "publish_demo"
version = "0.2.0"
edition = "2024"
description = "Small examples for Cargo publishing workflows"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/example/publish_demo"
documentation = "https://docs.rs/publish_demo"
keywords = ["cargo", "publish", "package"]
categories = ["development-tools"]
include = [
"src/**",
"Cargo.toml",
"README.md",
"LICENSE*"
]
[dependencies]This manifest communicates both packaging intent and published package quality more clearly than a minimal crate manifest.
Hands-On Exercise
Create a small crate and walk through a publish-safe packaging workflow.
Start here:
cargo new publish_lab --lib
cd publish_labSet the manifest:
[package]
name = "publish_lab"
version = "0.1.0"
edition = "2024"
description = "Practice Cargo packaging and publishing workflows"
license = "MIT"
readme = "README.md"
repository = "https://github.com/example/publish_lab"
include = [
"src/**",
"Cargo.toml",
"README.md",
"LICENSE*"
]Use this code:
pub fn greet(name: &str) -> String {
format!("Hello, {name}!")
}Create a simple README.md, then run:
cargo test
cargo doc
cargo package --list
cargo publish --dry-runThen add a fake internal notes directory, exclude or include around it, and rerun cargo package --list to see how file-selection policy changes the package.
Common Beginner Mistakes
Mistake 1: treating cargo publish as the first time to inspect the package.
Mistake 2: forgetting that published versions are permanent.
Mistake 3: publishing without checking package contents with cargo package --list.
Mistake 4: leaving weak README, license, or repository metadata in a public crate.
Mistake 5: trying to publish a crate before its dependency chain is publishable in the right order.
Mistake 6: assuming every workspace member should be publishable.
Mental Model Summary
A strong mental model for publishing packages in Cargo is:
cargo packageis the local packaging and verification stepcargo publishis the upload step built on top of packaging- file selection, metadata quality, and dependency readiness are part of publishing health
publish = falseand registry restrictions let you express publication policy directly- workspaces often need explicit package selection and release ordering
- version bumps, tags, and pre-publish checks are release workflow concerns, not optional polish
Once this model is stable, publishing becomes much easier to treat as deliberate release engineering rather than a one-command finale.
