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 package creates and checks the distributable crate archive
  • cargo publish packages 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 package

This 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 publish

A strong practical model is:

  • cargo package is the local packaging and verification step
  • cargo publish is 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_demo

Initial 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 package

A 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 package

If 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 --list

This 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 --list

Pre-Publish Metadata Quality

Publishing quality is strongly affected by package metadata. A healthy published crate usually has at least:

  • description
  • license or license-file
  • readme
  • repository
  • often documentation, homepage, keywords, and categories

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 = false

This 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_support

In practice, that often means:

cargo publish -p core_support
cargo publish -p app_support

This 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 publish

The 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 --workspace

This 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.rs

Root manifest:

[workspace]
members = ["crates/*"]
resolver = "3"

Internal tool manifest:

[package]
name = "internal_tool"
version = "0.1.0"
edition = "2024"
publish = false

Then publishing may look like:

cargo publish -p core_support
cargo publish -p app_support

while 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 = false

Pre-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-run

This 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 publish

If 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_support

This 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 include or exclude policy
  • 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_lab

Set 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-run

Then 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 package is the local packaging and verification step
  • cargo publish is the upload step built on top of packaging
  • file selection, metadata quality, and dependency readiness are part of publishing health
  • publish = false and 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.