Another Hello World_

[HOW TO] 📦 Publishing to Crates.io

Publishing a crate to the crates.io registry can be intimidating to new rustaceans, especially when unexpected errors occur. Learn how to get your code in front of the Rust community with minimal pain.

Busy? Read the TLDR.

The logo for the cargo-markdown cargo subcommand which can be found the crates.io website, portrays a box pallet with a two-level stack of boxes with the Rust logo on them, on a green background with a thick yellow border next to a Markdown logo consisting of a capitalize white letter M next to a large white arrow pointing downwards.


Table of Contents

  1. Introduction
  2. Where I went wrong (spoiler: RTFM)
  3. A better way
  4. Ship it!
  5. Ship it again!
  6. Uh oh, unship it!
  7. Does your crate belong on crates.io?
  8. Where to get help
  9. Acknowledgements
  10. TLDR

Introduction

The first crate I published was a simple trait for trimming the paths of anything that implements AsRef<std::path::Path>. The trait was in response to a thread on the Rust discord community from someone looking for help and also so I could gain experience publishing to crates.io. I was also looking to gain first-hand experience with the publishing process before I attempted to publish rusty_paseto, a much larger crate implementing the PASETO protocol (a safer alternative to JWT) which took a few months to write.

Even though I'd read the section on publishing to crates.io in the online Cargo Book, I still bumped into several issues when I tried to publish this simple crate. This article is the distillation of what I learned publishing the simple pathtrim crate as well as several others over the past few weeks.

back to toc

Where I went wrong (spoiler: RTFM)

The pathtrim crate

To be clear, most of the initial problems I incurred were because I didn't wholly RTFM.

The TLDR from the Cargo Book section on publishing to crates.io basically goes as follows:

  1. Create an account on crates.io
  2. Login locally via cargo login with your API key
  3. Ensure you have certain required manifest keys in your cargo.toml manifest file
  4. Run cargo publish --dry-run to uncover warnings or errors before you finally,
  5. Run cargo publish to push your packaged crate up to crates.io

Doesn't seem too tricky, right? I got my code prettied up, added documentation, tests and got ready to publish. Creating an account on crates.io, obtaining an API key and conducting a login were straightforward. I ran cargo publish --dry-run from my project directory, and the only warning I received:

warning: aborting upload due to dry run

Fantastic! All's good, let's ship it!

I ran cargo publish; the crate is packaged, verified, compiled, and uploaded. Sweet!

Hold up...

error: failed to publish to registry

Followed by...

invalid upload request: invalid length 6, expected at most 5 keywords

Ouch. Ok, I guess you can only have 5 keywords. Trimmed my keywords list down and tried a dry-run again. Again all good. Now to ship to crates.io! Running cargo publish yields:

error: failed to publish to registry

...and...

invalid upload request: invalid length 6, expected at most 5 categories

Right. I probably should have checked that. Fix and repeat the command after another successful dry run.

warning: the following are not valid category slugs and were ignored...<some category names>...
...Please see https://crates.io/category_slugs for the list of all category slugs.

Whoops. This one was only a warning, so the crate was successfully uploaded.

And yet I'm still not happy knowing one or more of my category names were ignored, and my crate will be less discoverable than I'd like. After looking at the list of available category names, I fixed up my manifest (I think one was misspelled) and tried again.

Now I get:

error: failed to publish to registry...crate version '1.0.0' is already uploaded

Argh. I wish the previous errors had been caught during the dry run. Now I need to either wait until I can publish a new version with a unique version number or just bite the bullet and publish a bumped semver patch number.

For my first crate, I go ahead and do that. Then I did it a few more times after noticing minor formatting issues on the readme and in the docs.

After pushing up v1.0.6, I finally have things looking the way I want them to, and the pathtrim crate is successfully published to crates.io. I wasn't expecting anybody but myself to use the crate, but I was pleasantly surprised to see dozens of downloads over the next few days nonetheless.

back to toc

The rusty_paseto crate

With rusty_paseto, I was determined not to make the same mistakes twice. I'd worked many hours on this code and wanted to put my best foot forward, especially since I am trying to assemble the beginnings of a portfolio of open source work to start a job hunt to write Rust code full-time.

I ensured my manifest file contained the correct number of keywords, categories and that the categories I selected exactly matched only categories from the official category slug list. I also spent a good amount of time formatting my readme file and previewing it on Github to make sure everything looked just right.

This time I run cargo publish, cross my fingers and then relax when I see rusty_paseto is published successfully with no errors or warnings. Awesome.

I head over to the crate's information page on crates.io and immediately see a bunch of formatting issues with my readme file. I should have considered that crates.io would not support the same Markdown that Github does. Additionally, the content area hosting the readme file as HTML is about 2/3rds the width of the 930 pixels the rest of the main content area takes up. Opening dev tools and viewing in mobile format looks even worse. ☹️

At this point, I stop and re-assess. There's got to be a better way to get my crate published on the first try looking how I intended.

The rest of this article is dedicated to showing you how to do just that.

back to toc

A better way

Ok, you've written your code. You've tested it. You've written documentation. You've written doc tests, and all tests are green (you are using doc tests, aren't you?). Although it's not required for publishing, if you're publishing a library, you should definitely check your public API against the Rust API Guidelines. You're feeling good, and you're ready to send your code out into the world.

Create an account

First things first, you’ll need an account on crates.io to acquire an API token. To do so, visit the home page and log in via a GitHub account (required for now). After this, see your Account Settings page and click on API Tokens on the left side menu. Generate a new token. You'll also need to confirm your email address. Then go to your command line and run the command below with the API key you were provided:

cargo login yourprovidedapikey 

This command will inform Cargo of your API token and store it locally in your ~/.cargo/credentials.toml.

This token is a secret and should not be shared with anyone else. If it leaks for any reason, you should revoke it immediately.
back to toc

Then do it again on staging

Now you have that done, you should do it again, this time on the crates.io staging site

What's that? Didn't you know there was a staging site for crates.io and that you could use it? Me neither! But @pietroalbini from the crates.io team in the rust language discord channel clued me into it while I was figuring things out for my own crates.

In addition to signing up at https://staging.crates.io, verifying your email again, and obtaining an API key, you'll have a couple more steps to accomplish on your local machine before you can log in with your staging API key.

Edit or create your ~/.cargo/config file and add the following entry:

[registries]
staging = { index = "https://github.com/rust-lang/staging.crates.io-index" }

Now you can run the following from your command line:

cargo login --registry=staging yourstagingapikey

And to publish to staging run:

cargo publish --registry=staging

Now you can test things out without worrying about pushing to the live site, which is permanent. You'll still need to increment your version number, but you can just change it back in your Cargo.toml manifest file before you go live since it doesn't matter on staging.

Important caveat about staging!

An unfortunate limitation regarding the staging site severely limits its ease of use. In particular keep the following in mind:

Uploading your crate to staging will fail if any of the other crates you have as a dependency are not already on staging.

Given that at the time of this writing, we're approaching 75 thousand crates published to the live site, but there are less than 7 thousand crates on the staging site, the chances of this impacting you are high.

Unfortunately, you'll get an error as you are publishing (without --dry-run) so you won't even get notified if you have a keywords or category error until you get past the Dependency is hosted on another registry error.

One workaround is to create an empty project cargo new test_crate --lib and copy in your crate's working manifest file (without dependencies), so you can verify your categories and keywords don't fail on staging before going live.

back to toc

Get your manifest file in order

The table below shows some helpful manifest package keys you'll want for publishing. This is far from inclusive as there are plenty more you can use but these are the primary keys I recommend for publishing to crates.io.

A brief description of each key, as well as the warning or error that might be generated, follows:

Relevant manifest keysduring
--dry-run
during
publish
after
publish
license or license file🟡🔴
description🟡🔴
homepage🟡🟡🟡
documentation🟡🟡🟡
repository🟡🟡🟡
keywords🔴
categories🔴🟡
authors
readme
🟡 warning
🔴 error
⚫ nothing
back to toc

🔴 license or license file

Use one or the other:

The license field contains the name of the software license that the package is released under.

The license-file field contains the path to a file containing the text of the license (relative to this Cargo.toml).

See additional details on the manifest reference.

Running cargo publish --dry-run will generate a warning if neither of these keys are specified
Ignoring the warning above and running cargo publish will generate an error and fail to publish as this key is required
back to toc

🔴 description

The description is a short blurb about the package. crates.io will display this with your package. This should be plain text (not Markdown).

Running cargo publish --dry-run will generate a warning if this key is missing
Ignoring the warning above and running cargo publish will generate an error and fail to publish as this key is required
back to toc

🟡 homepage

The homepage field should be a URL to a site that is the home page for your package if you have one. Smaller, simpler crates likely will not.

A warning is generated when homepage, repository and documentation are all missing from the manifest
back to toc

🟡 documentation

The documentation field specifies a URL to a website hosting the crate's documentation. If no URL is set in the manifest file, crates.io will automatically link your crate to the corresponding docs.rs page.

Even if you haven't published yet, you can specify the documentation key as:

documentation = "https://docs.rs/<your_crate_name>/latest/"

A warning is generated when homepage, repository and documentation are all missing from the manifest
back to toc

🟡 repository

Url to the source repository of your crate.

A warning is generated when homepage, repository and documentation are all missing from the manifest
back to toc

🔴 keywords

While not required to be present to publish, they have to be ASCII text (so the first 128 Unicode code points).

Choose any words you like to help people find your crate.

note: Maximum of 5 keywords, start with a letter and no more than 20 characters, letters, numbers, _ or -

Violating the rules in the note above will generate an error when running cargo publish NOT when executing a --dry-run
back to toc

🔴🟡 categories

Also not required to be present when publishing; however, if you DO use them, which you SHOULD for discoverability purposes, you should be aware they are more strict than keywords.

note: Maximum of 5 categories, case-sensitive from this list.

Providing more than 5 categories will generate an error when running cargo publish NOT when executing a --dry-run
Providing an invalid category publishes your crate warning the category was ignored. A SemVer change is required to update.
back to toc

⚫ authors

Lists people or organizations considered "authors" of the package.

See additional details on the manifest reference.

You should be aware the value of this key can't be removed or changed once you publish the crate.
back to toc

⚫ readme

The readme key should be the path to a file in the package root (relative to your Cargo.toml) that contains general information about the package. This file will be transferred to the registry when you publish it. Crates.io will interpret it as Markdown and render it HTML on the crate's page.

See additional details on the manifest reference.

back to toc

⚫ Exclude with excludes

One more thing to check out depending on what you have in your project directory:

Running cargo publish --dry-run, actually runs the command cargo package first which packages up your source code in a file called /target/package/your_crate-semver.crate. You can run the command yourself if you just want to check out the package file that gets created.

If your publish command fails with an error saying the max upload size has been exceeded, you should run cargo package --list to see what files were included in your upload package.

Cargo-package already respects your .gitignore file but you can also specify additional large files you don't need to be included in your package with the excludes manifest key

Package files greater than 10MB in size will cause the upload to fail. No warnings are generated before running cargo publish
back to toc

About that readme

I've participated in several big commercial product launches, been part of startups acquired and failed, and have been an entrepreneur for most my life. This has encouraged a product-based mindset to flavor my passion for technology. Whether open or closed-source, paid or free, code released to the world is a product that hopefully solves a problem for someone else. When it comes to crates released on crates.io, your readme file is the defacto landing page of your open-source product.

Your landing page should be functional and informative and guide the user into your product. Functional in the sense that navigating the page should be intuitive and accessible. Links should work and be innate, images should have descriptive alt tags for screen readers, etc. Informative in the sense that content should be organized in a meaningful way and an effort should be made to adhere to basic graphic design and information architecture principles in a responsive manner (mobile and desktop).

Nobody gets this right on the first go. I know I sure don't. It requires its own iterative development effort. And the layout constraints I mentioned with my experience deploying the rusty_paseto crate highlight a need to be able to test your landing page within the layout shell of the crates.io website.

cargo-markdown FTW

Attempting to iterate rapidly by repeatedly deploying a readme file to the crates.io staging site is far from efficient. This is why I created the cargo-markdown cargo subcommand to help me iterate quickly on my crate-based product frontend. I've found it helpful for my own readme files and hopefully, you will too. It's helped me get from this original readme page to this latest readme page in a single patch version.

If this sounds interesting to you, you can check it out here.

The version I'm working on currently will also do quick zero-copy link and accessibility checks with the nom parser so keep in touch if you want to know when that version is released.

back to toc

Ship it!

If you've gone through everything above you can now run cargo publish and watch your crate push live to crates.io

Take care when publishing a crate because a publish is permanent. The version can never be overwritten, and the code cannot be deleted. However, there is no limit to the number of versions that can be published.

Be patient because the link to the documentation site (if you're using docs.rs) can take a few minutes before your documentation has generated and shows up.

back to toc

Ship it again!

Change the version specified in your Cargo.toml manifest to release a new version. Keep in mind the semver rules, and consult the SemVer compatibility chapter of the Cargo Book for what constitutes a semver-breaking change.

Then run cargo publish to upload the new version.

back to toc

Uh oh, unship it!

Suppose you publish your crate and realize you have an "oh crap, that shouldn't have gone out" situation and users should NOT use a particular version of your crate. You should look at the cargo yank command which won't delete the code from the registry but will prevent users from being able to use the version you specifically yank.

back to toc

Does your crate belong on crates.io?

If it's helpful to you, it's likely to benefit others one day. Many Rust users agree that one of the best things about programming in Rust is the fantastic community. Sharing code on crates.io is a way to contribute your skills to that community. The whole ecosystem improves when others can use your code in various real-world scenarios providing feedback, pull requests, and/or issues to be addressed. You'll learn much about how your code gets used, and plenty about how to create and manage software other people use, even if you're only currently writing it for yourself.

Think small

When it comes to determining what you should share on crates.io, you should favor simplicity. Small, simple crates of functionality are likely to be more valuable since they can be assembled like simple building blocks and you won't be able to predict all the different ways users might want to consume your code when publishing it.

Use feature gates for bigger packages

Larger crates are fine, but put some thought into how you can use feature gates to allow users to only include the tiny bits of functionality they need for their use case. Functionality should be relevant to the spirit of the crate. It's easy to get excited about your code and attempt to include everything under the sun to make it usable for everybody.

As with most products, be ruthless and trim down the extra fat until you have the bare minimum, then trim down a little more. If you have some extra-but-related functionality you know is valuable but don't think it belongs in the crate you're currently publishing, make another crate and work on it in your spare time to add value!

back to toc

Where to get help

This was a long post, but there's plenty more to learn. This should get you comfortable enough to know how to deal with most issues when you're publishing to crates.io. Other places you can find information are listed below:

back to toc

Acknowledgements

The crates.io team assisted greatly with patiently answering questions that weren't clear or covered in the Rust book and providing information on how to use the staging site of crates.io.

In particular, many thanks goes to @pietroalbini, @tbieniek, and @carols10cents.

back to toc

TLDR

back to toc

❤️ Like this article? Follow me on Twitter and Github for more content like this.