A native, dependency-light C++17 client-side evaluation SDK for Flipt. It fetches a snapshot of your namespace's flags, segments and rollouts once, then evaluates every request in-process against that snapshot — no network round trip per flag lookup, and evaluation keeps working if Flipt becomes unreachable.
Upstream's flipt-client-sdks
ship client-side SDKs backed by a Rust engine (flipt-engine-ffi) with FFI
bindings per language, but has no C/C++ binding. This is a from-scratch port
of the evaluation algorithm (flipt-evaluation) to plain C++, using only
libcurl and nlohmann/json - no Rust toolchain, no prebuilt cdylibs.
This SDK is currently distributed as source, meant to be vendored or added as a git submodule and built with CMake:
add_subdirectory(flipt-client-cpp)
target_link_libraries(your_target PRIVATE flipt-client-cpp)Requirements: a C++17 compiler, and nlohmann/json
(resolved via find_package(nlohmann_json), falling back to a vendored copy
under third_party/ if not found). libcurl is required unless you set
FLIPT_WITH_CURL=OFF and supply your own flipt::IFetcher.
#include <flipt/client.hpp>
flipt::ClientOptions options;
options.url = "https://your-flipt-instance.example.com";
options.namespaceKey = "default";
flipt::FliptClient client(options);
auto variant = client.evaluateVariant("your-flag-key", "entity-id", {{"context-key", "context-value"}});
if (variant.match)
{
// variant.variantKey, variant.variantAttachment
}
auto boolean = client.evaluateBoolean("your-boolean-flag", "entity-id", {});
if (boolean.enabled)
{
// ...
}
auto batch = client.evaluateBatch({
{"flag-one", "entity-id", {}},
{"flag-two", "entity-id", {}},
});flipt::ClientOptions:
| Field | Default | Description |
|---|---|---|
environment |
"default" |
Flipt environment |
namespaceKey |
"default" |
Flipt namespace |
url |
"http://localhost:8080" |
Base URL of your Flipt instance |
authentication |
unset | See Authentication |
reference |
unset | Reference to evaluate against, if using references |
updateInterval |
120 |
Seconds between automatic snapshot refreshes. 0 disables the background refresh thread entirely - call refresh() yourself instead |
errorStrategy |
Fail |
See Error Strategies |
snapshot |
unset | Seed snapshot from a prior getSnapshot() call - see Snapshotting |
requestTimeoutMillis |
30000 |
Timeout for the built-in curl fetcher; ignored when fetcher is set |
tls |
unset | TLS options for the built-in curl fetcher - see TLS; ignored when fetcher is set |
fetcher |
unset | Custom flipt::IFetcher - see Custom Fetcher |
The flipt::TlsConfig supports configuring TLS settings for secure connections to Flipt servers
// Use the OS-native CA store instead of curl's compiled-in default. Needed
// when curl is linked against OpenSSL (e.g. on Windows), which does not
// consult the OS trust store unless told to.
options.tls = flipt::TlsConfig{ .useSystemCaStore = true };
// Custom CA (e.g. self-signed/internal Flipt instance)
options.tls = flipt::TlsConfig{ .caCertFile = "/path/to/ca.pem" };
// or inline PEM content instead of a path:
options.tls = flipt::TlsConfig{ .caCertData = caPemString };
// Mutual TLS
options.tls = flipt::TlsConfig{
.clientCertFile = "/path/to/client.pem",
.clientKeyFile = "/path/to/client-key.pem",
};
// Development only - do not use in production
options.tls = flipt::TlsConfig::insecure(); // skip all certificate verification
options.tls = flipt::TlsConfig::skipHostnameVerify(); // verify cert, skip hostname match*Data fields take precedence over their *File counterpart when both are
set. Ignored when a custom fetcher is set - configure TLS on your own HTTP
stack instead.
options.authentication = flipt::Authentication{
.clientToken = flipt::ClientTokenAuthentication{"your-client-token"},
};
// or:
options.authentication = flipt::Authentication{
.jwtToken = flipt::JwtAuthentication{"your-jwt"},
};flipt::ErrorStrategy::Fail(default):refresh()and the constructor rethrowflipt::Erroron any fetch/parse failure.flipt::ErrorStrategy::Fallback: failures are swallowed and the client keeps serving the last known-good snapshot (or an empty one, if none was ever fetched). Combine with a seededsnapshotfor fully offline startup.
getSnapshot() returns a base64-encoded, JSON-serialized snapshot of the
client's current state, interoperable with the official SDKs:
std::string snapshot = client.getSnapshot();
// persist `snapshot` to disk...
// later, on a fresh start:
flipt::ClientOptions options;
options.snapshot = snapshot; // evaluate immediately, before the first fetch completes
flipt::FliptClient client(options);Inject your own HTTP stack (to share a connection pool, CA bundle, proxy settings, etc.) instead of the built-in libcurl fetcher:
class MyFetcher : public flipt::IFetcher
{
public:
flipt::FetchResult fetch(const flipt::FetchOptions& options) override
{
flipt::FetchResult result;
// ... perform the request, honoring options.etag as If-None-Match ...
result.status = /* HTTP status code */;
result.body = /* response body */;
result.etag = /* ETag response header, if present */;
return result;
}
};
options.fetcher = std::make_shared<MyFetcher>();The current snapshot lives behind a std::shared_mutex; evaluation calls
take a shared (read) lock and never block on network I/O. Refreshing swaps in
a new snapshot behind a brief unique (write) lock, so concurrent evaluations
are never blocked by a refresh in progress.
Automatic background refresh is opt-in via updateInterval > 0 (default),
which starts a dedicated thread. Set updateInterval = 0 to disable it and
call refresh() yourself - useful when the embedding application already
has its own scheduler/timer and would rather not spawn a second thread.
Every fetch sends If-None-Match with the last known ETag. A 304 response
is not an error: refresh() returns false and the existing snapshot is
kept as-is, avoiding a reparse.
cmake -B build -S . -DFLIPT_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failuremise run lint runs clang-format --dry-run over the SDK's sources.
This project is licensed under the MIT License.