Cargo Feature Flags: ssr Pattern & Workspaces Guide

Max WellsMax WellsFounder of Rustify

TL;DR: Cargo feature flags let Rust crates include optional code, dependencies, and integrations at compile time. Define them in [features], gate code with #[cfg(feature = "...")], and activate them through Cargo.toml or the Cargo CLI. In 2026, feature flags are the standard way to keep crates modular without forcing every user to pay for every dependency.


What Are Feature Flags?

Feature flags are Cargo's compile-time switches for optional functionality, and they are one of the main ways Rust crates stay modular and dependency-efficient in 2026.

Features let a crate expose optional integrations such as serde, tokio, or sqlx support without forcing every user to compile everything. That matters for binary size, compile times, portability, and long-term API discipline.

This is especially relevant for library authors, full-stack Rust teams, and anyone working across server, desktop, and wasm targets.


How Do Feature Flags Work?

Feature flags work by naming optional behaviors in [features], connecting them to optional dependencies, and compiling code conditionally with cfg attributes.

[package]
name = "my-lib"
version = "0.1.0"
 
[features]
default = ["json"]
json = ["dep:serde_json"]
async = ["dep:tokio"]
postgres = ["dep:sqlx"]
 
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", optional = true }
tokio = { version = "1", features = ["full"], optional = true }
sqlx = { version = "0.8", optional = true }
#[cfg(feature = "async")]
pub async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
    reqwest::get(url).await?.text().await
}
 
pub fn version() -> &'static str {
    if cfg!(feature = "nightly-compat") {
        "nightly"
    } else {
        "stable"
    }
}

The key ideas are:

  • features are declared in [features]
  • optional dependencies often sit behind features
  • cfg attributes control whether code exists in the compiled binary
  • features are additive, not subtractive

When Should You Use Feature Flags?

Use feature flags when optional functionality would otherwise force unnecessary dependencies, compile time, or platform constraints on every user.

Good use cases include:

  • optional Serde support
  • multiple database or runtime backends
  • wasm vs server-only builds
  • heavy integrations users may not need
  • server-side rendering patterns such as ssr

Bad use cases include hiding core behavior behind confusing flags or creating a combinatorial maze that nobody can test or reason about.


Feature Flags vs Runtime Flags in 2026

Choose Cargo feature flags for compile-time code selection in 2026, and choose runtime flags when the decision must change without rebuilding the binary.

Cargo feature flagsRuntime flags
Decision timeCompile timeRuntime
Removes code and deps from buildYesNo
Good for optional integrationsYesSometimes
Good for production experimentsNoYes
Typical Rust useLibraries, targets, backend choicesSaaS rollout, ops config
Best fitBinary shape and dependency controlOperational behavior control

If the goal is "do not compile this code unless requested," use Cargo features. If the goal is "turn behavior on for some users today," use runtime configuration or product feature flags instead.


Why Do Feature Flags Matter Professionally?

Feature flags matter because they are one of the clearest signals that a Rust engineer understands library design, dependency discipline, and deployment constraints.

A beginner sees feature flags as syntax. A stronger engineer sees them as API surface management. Poorly designed features create bloated builds and confusing integrations; well-designed features make a crate easier to adopt in very different environments.

This is a high-intent topic for library authors, backend teams, and full-stack Rust builders because the implied question is usually "how should I structure this crate?" not just "what does this keyword mean?"


How Do You Enable Features in Dependencies and Workspaces?

Features can be enabled from a dependency declaration, the Cargo CLI, or shared workspace dependency configuration.

[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio", "macros"] }
my-lib = { version = "1", features = ["async", "json"] }
cargo build --features "async,postgres"
cargo test --features "async" -p my-crate
cargo build --all-features
cargo build --no-default-features
[workspace.dependencies]
sqlx = { version = "0.8", features = ["postgres", "runtime-tokio"] }

Feature unification still matters in workspaces: if any crate enables a feature on a dependency, that feature is active for the whole build graph.


What Is the ssr Feature Pattern?

The ssr feature pattern is a common full-stack Rust convention where server-only code is compiled behind a dedicated feature.

[features]
ssr = ["dep:tokio", "dep:sqlx", "dep:axum"]
hydrate = ["dep:wasm-bindgen"]
#[cfg(feature = "ssr")]
pub async fn db_query(pool: &sqlx::PgPool) -> Vec<User> {
    sqlx::query_as!(User, "SELECT * FROM users").fetch_all(pool).await.unwrap()
}

This pattern is common in ecosystems such as Leptos because it keeps server-only dependencies out of the client-side wasm build.


Frequently Asked Questions

Yes. Once a feature is enabled anywhere in the dependency graph, it is enabled for that build.

optional = true marks a dependency as not always required. A feature is the named switch that can turn that dependency on.

Usually no. Cargo features are compile-time switches, not runtime rollout controls.

The dep: prefix avoids creating an implicit feature with the same name as the dependency and keeps feature design more explicit.

Yes. Too many interdependent flags can make a crate hard to test and reason about, so keep the feature surface intentional.


Sources



Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?