T tomlkit·org
Inspect Formatter Validator Lint Stats Keys Query Set value pyproject Cargo Convert TOMLJSON JSONTOML TOMLYAML YAMLTOML INITOML TOMLINI .envTOML TOML.env TOMLTS TOMLCSV CSVTOML TOMLXML TOML.properties .propertiesTOML Transform Sort keys Flatten Unflatten Redact Minify Generate Go struct Rust struct Python types JSON Schema Compare Diff Merge

Cargo.toml Validator

updated 30 August 2026

A Cargo.toml can be flawless TOML and still be wrong: edition as a number, a feature that names a dependency which is not optional, a branch that cargo silently ignores because there is no git next to it. This checks the manifest against cargo's rules and gives you the line number.

package · dependencies · features · workspace · profiles

The mistakes that cost the most time

Cargo's manifest errors are better than most, but the expensive ones are the mistakes it does not report — keys it reads past because they are not keys it knows.

edition = 2021. Without the quotes it is a TOML integer, and cargo answers invalid type: integer `2021`, expected a string. It is the most common Cargo.toml error there is, and the fix is two characters.

default_features = false. The key cargo reads is default-features, with a hyphen. Written with an underscore it is an unknown key on a dependency, so the default features stay switched on — and the build that was supposed to be no_std quietly is not. Cargo has warned about this since 1.68; in a large manifest the warning scrolls past.

version = "1.0" in [package]. A package version is SemVer and SemVer has three numbers. A dependency requirement may be written "1.0" — that is a different grammar, and it means ^1.0, not =1.0.

A git dependency with a branch and a tag. Only one of branch, tag or rev is allowed, and either of them without a git key is ignored outright — the dependency resolves from crates.io while the manifest says otherwise.

A feature that enables something that is not there. json = ["serde/derive"] is fine if serde is a dependency; ["missing/feature"] fails at build time with a message about a feature you did not write. Optional dependencies are the mirror image: a feature can only switch on a dependency marked optional = true, and in [dev-dependencies] that word is not allowed at all.

Features, and the implicit ones

Every optional dependency creates a feature with the same name unless some feature refers to it as dep:name. That is convenient until you rename the crate or want the feature to mean something slightly different, at which point the implicit feature is in the way. The validator points out optional dependencies that no feature mentions explicitly, because that is the moment to decide whether the implicit feature is what you want.

Cycles are the other feature trap. fast = ["simd"] with simd = ["fast"] is an error, and cargo's message names only one of the two. The report here prints the whole loop.

Workspaces

Inheritance — version.workspace = true, serde = { workspace = true } — is checked against a [workspace] table in the same file, which means it is checked when you paste the workspace root and skipped when you paste a member. Alongside an inherited dependency only features, optional and default-features may be added; anything else, a version in particular, is an error.

A virtual manifest — [workspace] with no [package] — should set resolver = "2" or "3". Without it cargo uses resolver 1 no matter what edition the members declare, and features unify across build-dependencies and target-specific dependencies in ways that produce a build failure in one member and not another.

What it deliberately does not do

It resolves nothing. No request leaves the page, so it cannot tell you that a crate name is misspelled, that a version does not exist, or that two requirements cannot both be satisfied. What it can tell you is that a string is not a version requirement, that a key is not one cargo reads, and that a feature points at nothing — which is the whole class of failures you can fix without a network.