This is the Rust Server-Side SDK for the 100% open-source feature flag management platform FeatBit.
The SDK is designed for long-lived, multi-user systems such as web servers and backend
applications. Create one FbClient per FeatBit environment and reuse it for the lifetime of the
process. It is not intended for untrusted client-side, desktop, mobile, or embedded applications,
where an environment secret could be exposed.
OpenFeature support lives in the separate FeatBit OpenFeature Provider for Rust repository.
The SDK keeps feature flags and segments synchronized over WebSocket and evaluates them from an immutable in-memory snapshot. Flag changes are pushed to the client; interrupted connections reconnect automatically and request updates from the last known data version.
Evaluation is synchronous, local, and performs no network I/O. A previously initialized client
continues to evaluate its last snapshot with ClientStatus::Stale while the connection recovers.
See Offline Mode when the application must use a static bootstrap without network
access.
The latest stable SDK is available on crates.io. Add it and a logger implementation with Cargo:
cargo add featbit-server-sdk
cargo add env_loggerThese commands update Cargo.toml. To pin this release exactly, use
cargo add featbit-server-sdk@=0.1.1. The equivalent manual configuration is:
[dependencies]
featbit-server-sdk = "0.1.1"
env_logger = "0.11"The Cargo package uses hyphens and is imported as featbit_server_sdk in Rust code.
Before using the SDK, obtain the environment secret and SDK URLs:
Use wss:// and https:// endpoints in production. The default ws://localhost:5100 and
http://localhost:5100 endpoints are intended for local development.
use featbit_server_sdk::{FbClient, FbOptions, FbUser};
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::init();
let options = FbOptions::builder(std::env::var("FEATBIT_ENV_SECRET")?)
.streaming_url(std::env::var("FEATBIT_STREAMING_URL")?)
.event_url(std::env::var("FEATBIT_EVENT_URL")?)
.build()?;
let client = FbClient::with_options(options);
if !client.initialized() {
eprintln!("FbClient is not initialized; variation calls use fallbacks");
}
let user = FbUser::builder("user-123")
.name("Ada")
.custom("country", "CN")
.build();
let enabled = client.bool_variation("new-checkout", &user, false);
if enabled {
// Run the new code path.
}
client.close();
Ok(())
}- Console application:
cargo run --example fbclient_console - Axum application:
cargo run --example fbclient_axum
The Axum example creates one client during startup, shares cloneable handles through application state, drains in-flight requests, and then closes the SDK. It exposes:
GET /health/readyfor readiness checks;GET /api/flags/{flag_key}/evaluatefor browser-friendly evaluation with query parameters;POST /api/flags/{flag_key}/evaluatefor JSON request bodies.
For GET evaluation, targetingKey, name, and defaultValue are reserved parameters. Every other
query parameter becomes a custom user attribute:
http://127.0.0.1:3000/api/flags/example-flag/evaluate?targetingKey=user-123&country=CN&plan=pro&defaultValue=false
FbClient is the heart of the SDK. It owns data synchronization, local evaluation, analytics
delivery, and lifecycle state. Applications should instantiate a single client for each FeatBit
environment.
Cloning an FbClient is cheap: every clone shares the same snapshot and background workers.
Defaults connect to a local FeatBit evaluation server:
use featbit_server_sdk::FbClient;
# fn example() -> Result<(), featbit_server_sdk::ConfigError> {
let client = FbClient::new("environment-secret")?;
# client.close();
# Ok(())
# }use std::time::Duration;
use featbit_server_sdk::{FbClient, FbOptions};
# fn example() -> Result<(), featbit_server_sdk::ConfigError> {
let options = FbOptions::builder("environment-secret")
.streaming_url("wss://app-eval.featbit.co")
.event_url("https://app-eval.featbit.co")
.start_wait(Duration::from_secs(3))
.build()?;
let client = FbClient::with_options(options);
# client.close();
# Ok(())
# }Construction can wait up to start_wait for initial data. In an async application, construct the
client with tokio::task::spawn_blocking so the bounded startup wait does not block an async worker.
The SDK uses Rust's standard log facade and never installs or replaces a
global logger. The application chooses the implementation:
fn main() {
env_logger::init();
// Construct FbClient after the logger is installed.
}Set RUST_LOG=featbit_server_sdk=debug for lifecycle and delivery diagnostics. Environment secrets,
authorization headers, and complete user/event bodies are not logged.
FbUser describes the subject of an evaluation. key is mandatory and must uniquely identify the
user. name and custom string attributes can be referenced by targeting rules and included in
FeatBit analytics:
use featbit_server_sdk::FbUser;
let bob = FbUser::builder("a-unique-user-key")
.name("Bob")
.custom("age", "15")
.custom("country", "FR")
.build();
assert_eq!(bob.key(), "a-unique-user-key");The SDK calculates flag values locally from the latest consistent snapshot. Each type has a value-only method and a detail method:
| Flag type | Value-only method | Method with diagnostics |
|---|---|---|
| Boolean | bool_variation |
bool_variation_detail |
| Integer | int_variation |
int_variation_detail |
| Float | float_variation |
float_variation_detail |
| String | string_variation |
string_variation_detail |
| JSON | json_variation |
json_variation_detail |
Value-only methods always return the supplied fallback when the client is not ready, the flag does not exist, remote data is malformed, the context is invalid, or the variation has the wrong type. Application-facing evaluation methods do not panic.
use featbit_server_sdk::{FbClient, FbUser};
# fn example(client: &FbClient, user: &FbUser) {
let enabled = client.bool_variation("new-checkout", user, false);
let detail = client.bool_variation_detail("new-checkout", user, false);
println!(
"value={}, variation={}, reason={}",
detail.value, detail.variation_id, detail.reason
);
# let _ = enabled;
# }all_variations returns a sorted, inspection-only snapshot of every known flag and does not emit
evaluation analytics.
FbClient::status returns:
| Status | Meaning |
|---|---|
NotReady |
No valid data set has been received yet |
Ready |
The local snapshot is synchronized |
Stale |
The last snapshot is usable while synchronization recovers |
Closed |
The client was closed or synchronization stopped unrecoverably |
flush requests a non-blocking analytics flush. flush_and_wait waits up to a caller-provided
timeout and reports whether covered events were delivered. close is bounded and idempotent; call
it after the application has drained in-flight work.
Offline mode performs no network I/O and sends no analytics. Without bootstrap data, no feature flags or segments are available and evaluations return their caller-provided fallbacks.
Do not construct a production bootstrap file by hand because the FeatBit data-sync format is protocol-owned and may evolve. Download the current flags and segments for an environment directly from its Evaluation Server or FeatBit Agent:
curl --fail --show-error \
--header "Authorization: <your-environment-secret>" \
"<evaluation-server-or-agent-url>/api/public/sdk/server/latest-all" \
--output featbit-bootstrap.jsonFor a direct Evaluation Server connection, use the HTTP(S) base URL also supplied to event_url,
such as http://localhost:5100 for local development. The Authorization value is the FeatBit
environment secret, not a REST API access token. See
Retrieve feature flags with API
for the server-side endpoint contract.
The response is a full data-sync envelope with messageType = "data-sync" and
data.eventType = "full". Make the initial request without a timestamp query parameter because
incremental responses are not valid bootstrap documents. See
examples/featbit-bootstrap.json for a synthetic,
non-production example. A downloaded snapshot contains flag configuration, targeting rules, and
segments: treat it as sensitive configuration, do not commit a production snapshot, and refresh it
whenever the environment changes.
Use the downloaded file to initialize the local snapshot:
use featbit_server_sdk::{FbClient, FbOptions};
# fn example() -> Result<(), Box<dyn std::error::Error>> {
let bootstrap = std::fs::read_to_string("featbit-bootstrap.json")?;
let options = FbOptions::builder("offline-placeholder")
.offline(true)
.disable_events(false, false)
.bootstrap_json(bootstrap)
.build()?;
let client = FbClient::with_options(options);
# client.close();
# Ok(())
# }Bootstrap JSON is accepted only in offline mode so static data cannot compete with the live synchronizer. The SDK validates that the document is a full data-sync envelope. Offline mode does not poll for updates; unknown flags continue to resolve to fallbacks.
disable_events(disable, allow_track) controls automatic evaluation analytics and explicit
tracking independently:
| Configuration | Automatic evaluation events | Explicit evaluation/metric tracking |
|---|---|---|
disable_events(false, true) (default) |
enabled | allowed |
disable_events(false, false) |
enabled | rejected |
disable_events(true, true) |
disabled | allowed |
disable_events(true, false) |
disabled | rejected; no event worker |
For application-controlled exposure tracking, disable_events(true, true) is recommended.
Successful detail evaluations retain an immutable event snapshot. This lets the application record the exact variation only after a real exposure, even if the flag changes in the meantime:
use featbit_server_sdk::{FbClient, FbUser};
# fn user_was_exposed() -> bool { true }
# fn example(client: &FbClient, user: &FbUser) {
let detail = client.bool_variation_detail("new-checkout", user, false);
if user_was_exposed() {
if let Some(event) = detail.evaluation_event.as_ref() {
let _accepted = client.track_eval_event(user, event);
}
}
let _accepted = client.track_metric_event(user, "checkout-completed", 1.0);
# }Evaluation and tracking calls enqueue without waiting for network I/O. Pending analytics are
bounded by both event count (10,000 by default) and approximate retained payload memory (64 MiB by
default), including queued, buffered, and in-flight events. Large user attributes and JSON
variation values are delivered intact when admitted; the SDK never truncates them. Applications
that intentionally send larger payloads can raise
FbOptionsBuilder::max_event_queue_size_bytes. Analytics may be dropped under sustained overload
rather than delaying application requests.
evaluate_raw, complete_raw_evaluation, and observe_evaluation_error form the transport-neutral
adapter boundary. They expose FeatBit reason, flag metadata, and the immutable event snapshot without
depending on an external standard. Most applications should use the typed variation methods.
The separate OpenFeature provider uses this boundary and does not maintain a second evaluation engine.
The optional featbit-server-sdk-opentelemetry crate emits
feature_flag.evaluation through an application-owned OpenTelemetry logger. It excludes context
identifiers and raw variation values by default and remains independent of FeatBit analytics
delivery.
The crate uses Rust edition 2021 and has a minimum supported Rust version (MSRV) of 1.95.0. CI tests Rust 1.95, 1.96, and 1.97. Stable Rust only is supported.
- Ask FeatBit usage questions in FeatBit Slack.
- Report SDK bugs or request features in featbit-rust-sdk issues.
The architecture and compatibility requirements are documented in AGENTS.md. Run the full quality gate before submitting a change:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo test --workspace --doc
The crates.io approval, versioning, prerelease, and recovery workflow is documented in RELEASING.md.
Licensed under the Apache License, Version 2.0. See LICENSE.