---
title: How to Override Dependencies with [patch] in Cargo
summary: Use Cargo's [patch] section to substitute a dependency with a fork or local checkout across the entire dependency graph. Learn the syntax, version rules, workspace setup, and when not to use it.
lang: en
slug: how-to-override-dependencies-with-patch-in-cargo
published: 2026-04-17T14:12:14+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-override-dependencies-with-patch-in-cargo/
---

# How to Override Dependencies with [patch] in Cargo

## The deadline situation

Friday afternoon. Your service is hitting a bug in `serde` (or `tokio`, or some HTTP client). You've found the issue, you have a one-line fix on a fork, and a pull request is open upstream. But the maintainer is on vacation and the merge isn't happening today. Your boss wants the fix shipped by Monday.

You have a few bad options. Vendor the dependency into your repo and edit it in place (gross, fragile). Pin to a specific commit on your fork in `Cargo.toml` for that one crate (works, but awkward when transitive dependencies also pull in the original crate). Wait for the upstream fix (your boss says no).

There's a fifth option, and it's the one Cargo was actually designed for. The `[patch]` section. Tell Cargo "everywhere this crate is used, no matter how deep in the dependency graph, use this version instead." That's exactly what `[patch]` does.

## The shape of [patch]

`[patch]` lives in your `Cargo.toml`. It says: for the source listed (usually `crates-io`), replace the named crate with whatever you point at: a local path, a git repo, or a different registry. The replacement applies everywhere in the dependency graph, including indirect dependencies.

Here's the smallest example.

```toml
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"

[dependencies]
# Normal dependency. Cargo would pull serde from crates.io as usual.
serde = "1.0"

# Override: every reference to `serde` from crates.io now resolves to this local checkout.
[patch.crates-io]
serde = { path = "../my-serde" }
```

Run `cargo build`. Cargo notices the patch, reroutes `serde` to your local copy, and recompiles. If any of your dependencies also depends on `serde`, they get the patched version too, which is the whole point.

You're not changing what version your code asks for. You're changing what code that version actually resolves to. Your `Cargo.toml` still says `serde = "1.0"`. Cargo's resolver still treats it as a `1.0`-compatible crate. The `[patch]` simply substitutes the source.

## Pointing at a git fork

Local paths are the easiest case. Git forks are the most common in real life. Here's the same idea but pulling from your fork:

```toml
[dependencies]
tokio = "1"

# Use my fork's bugfix branch instead of crates.io for tokio.
[patch.crates-io]
tokio = { git = "https://github.com/myname/tokio", branch = "fix-leak-in-spawn" }
```

`cargo update -p tokio` will fetch the latest from that branch. When the upstream fix lands and gets released, you remove the `[patch]` block and bump your `tokio = "1.x"` constraint to a version that contains the fix. Your code doesn't change.

You can also pin to a specific commit, which is what you want for reproducible builds:

```toml
[patch.crates-io]
tokio = { git = "https://github.com/myname/tokio", rev = "abc1234" }
```

Use `branch` while you're iterating, `rev` once you're shipping.

## The version compatibility rule

Here's the part people miss the first time. The patched version has to be **semver-compatible** with the version your dependency graph asked for. If your project asks for `serde = "1.0"`, the patched `serde` has to also be a `1.0.x`. If you point `[patch]` at a serde 2.0 fork, Cargo will refuse and emit an error like:

```text
error: failed to resolve patches for `https://github.com/rust-lang/crates.io-index`
Caused by:
  patch for `serde` in `https://github.com/rust-lang/crates.io-index`
  did not resolve to any crates.
```

or, in different conditions, a warning that the patch went unused. The fix: make sure your fork's `Cargo.toml` has a version number that satisfies all the constraints in the dependency graph.

This rule has a flip side. If your dependency tree asks for `serde 1.0` *and* `serde 2.0` (different ranges from different libraries), `[patch]` for `crates-io` only replaces *one* version family at a time. To fix this fully, you may need `[patch]` entries for both, or use the alternative `[patch.unused]`/version mechanism that Cargo offers for advanced cases.

## A more realistic scenario: testing an unreleased fix

You hit a bug in `reqwest`. You fork it, fix it, push to your branch. Now your service can't ship until you can prove the fix works in the real environment. You don't want to publish to crates.io (that requires a name, version bumps, careful semver). You just want to test.

```toml
[package]
name = "my-service"
version = "0.3.0"
edition = "2021"

[dependencies]
# Whatever version the rest of the project agrees on.
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }

# Test my fix in real conditions before opening the PR upstream.
# Once the fix is merged and a new reqwest release ships, delete this block.
[patch.crates-io]
reqwest = { git = "https://github.com/myname/reqwest", branch = "fix-redirect-loop" }
```

Now `cargo build` recompiles with your fork. You run integration tests, hit your staging environment, watch metrics. Once you're confident, you upstream the PR, and once it's released, you remove the patch block.

This is the everyday use of `[patch]`. Not "permanent fork" but "temporary detour while waiting for upstream."

## Workspace-wide patches

In a Cargo workspace (multiple crates in one repo), `[patch]` belongs in the workspace `Cargo.toml`, not in any individual crate. That way every crate in the workspace uses the same patched version.

```toml
[workspace]
members = ["crate-a", "crate-b", "crate-c"]

# This patch applies to every member crate in the workspace.
[patch.crates-io]
serde = { git = "https://github.com/myname/serde", branch = "fix-2024-12" }
```

If you put `[patch]` inside `crate-a/Cargo.toml`, Cargo will warn that the patch is being ignored. The patch always belongs at the workspace root.

## [patch] vs other override mechanisms

Cargo has a few related tools. They look similar and they aren't interchangeable.

`[patch]` rewrites a dependency wherever it's referenced in the graph. It's the right answer for "use my fork instead of upstream, even for transitive dependencies." Versions must be semver-compatible.

`[replace]` was an older mechanism that did something similar. It is now deprecated in favor of `[patch]`. If you find `[replace]` in old code or tutorials, treat it as obsolete.

`[dependencies] foo = { path = "../foo" }` overrides only your direct dependency on `foo`. If a transitive dep also depends on `foo`, it gets the crates.io version, not your local one. That's a real bug if you're trying to test a fix that lives in shared code. Use `[patch]` instead.

`[profile.dev.package.foo] opt-level = 3` is for tweaking compilation settings of a particular crate (not source). Different problem entirely.

For tooling: `cargo update` and `cargo tree` are your friends. `cargo tree -p serde` shows you exactly which crates pull in serde and at which versions. That tells you what you need to patch. After editing `[patch]`, run `cargo update` (sometimes targeted: `cargo update -p serde`) to refresh `Cargo.lock`.

## Common pitfalls

You added a `[patch]` and Cargo says "patch was not used in the crate graph." That means no crate in your dependency tree actually requested the version your patch resolves to. Usually because the version in your fork's `Cargo.toml` doesn't match what's needed. Check `cargo tree` to see what versions are in play.

You added the patch in a sub-crate's `Cargo.toml` instead of the workspace root. The patch is ignored. Move it to the workspace `Cargo.toml`.

You forgot to commit `Cargo.lock` after the patch. Other developers (or CI) end up with a different resolution and the patch silently goes unused for them. `Cargo.lock` belongs in version control for binary projects.

You patched a crate but a transitive dep pinned the original via a `=1.2.3` exact-version requirement. Cargo will refuse to resolve. You need to either patch in the transitive dep too or get them to relax the pin.

You're using `[patch]` long-term as a fork. That's not what `[patch]` is for. If you really need to maintain a permanent fork, publish your fork under a new name (`my-org-serde`) and depend on it directly. Long-running `[patch]` blocks confuse new contributors and hide the divergence.

## When to use what

Use `[patch.crates-io]` when you want to test a fix or temporarily replace a crates.io dependency with your own version, including for transitive uses. The fix is in flight upstream; you don't want to wait.

Use a direct `path = "../foo"` dependency when you only consume the crate yourself, no transitive dependencies share it, and you're developing both crates side by side.

Use a published fork (different crate name) when you need a permanent divergence. That's a real maintenance burden but it's honest.

Don't use `[patch]` to silence semver warnings. If your code wants `1.0` and the new release is `2.0`, the right answer is to update your code, not to patch the new version into the old slot. The compiler errors that follow are protecting you.

## Where to go next

`[patch]` is one of those Cargo features you don't notice until the day you suddenly need it. Then it saves your week.

[How to specify exact dependency versions in Rust](/en/how-to-specify-exact-dependency-versions-in-rust/)

[How to fix dependency resolution conflicts in Cargo](/en/how-to-fix-dependency-resolution-conflicts-in-cargo/)

[How to use semantic versioning for Rust crates](/en/how-to-use-semantic-versioning-for-rust-crates/)
