Feature Flags (OFREP)
Logfire’s managed variables can serve as feature flags for client-side applications like web frontends, mobile apps, and edge services. The OFREP (OpenFeature Remote Evaluation Protocol) endpoints let any OpenFeature-compatible client evaluate variables without the Python SDK.
This guide shows how to set up a JavaScript/TypeScript web application using the official OpenFeature Web SDK and OFREP provider. The same approach works for any language with an OpenFeature SDK and OFREP provider.
- Create your variables in the Logfire UI (Settings > Variables) and mark them as external — see External Variables and OFREP
- Create an API key with the
project:read_external_variablesscope — expand the Advanced section of the API key form and select just this scope (the one-click presets bundle it into broader access) by selecting the “Read external managed variables within a project via OFREP” item under the “Variables” section. This restricted scope is safe to use in client-side code since it only exposes variables you’ve explicitly marked as external
Install the OpenFeature Web SDK and OFREP provider:
npm install @openfeature/web-sdk @openfeature/ofrep-web-provider
pnpm add @openfeature/web-sdk @openfeature/ofrep-web-provider
yarn add @openfeature/web-sdk @openfeature/ofrep-web-provider
Initialize the OpenFeature provider once at application startup. The OFREP provider connects to your Logfire project’s OFREP endpoint and handles authentication via your API key.
import { OFREPWebProvider } from '@openfeature/ofrep-web-provider'
import { OpenFeature } from '@openfeature/web-sdk'
const LOGFIRE_API_KEY = 'your-api-key' // project:read_external_variables scope
const LOGFIRE_API_HOST = 'logfire-api.pydantic.dev' // or your self-hosted API host
const provider = new OFREPWebProvider({
// The provider appends `/ofrep/v1/evaluate/flags` itself, so this is the `/v1` root.
baseUrl: `https://${LOGFIRE_API_HOST}/v1`,
fetchImplementation: (input, init) => {
// Keep the headers the provider already set (it sends `Content-Type:
// application/json`, which the endpoint requires) and add the API key.
const headers = new Headers(input instanceof Request ? input.headers : undefined)
new Headers(init?.headers).forEach((value, key) => headers.set(key, value))
headers.set('Authorization', `Bearer ${LOGFIRE_API_KEY}`)
headers.set('Content-Type', 'application/json')
return fetch(input, { ...init, headers })
},
})
OpenFeature.setProvider(provider)
Set the evaluation context to enable targeting and deterministic rollouts. The targetingKey ensures that the same user always gets the same variant:
await OpenFeature.setContext({
targetingKey: userId,
// Additional attributes for targeting rules
plan: 'enterprise',
region: 'us-east',
})
Any attributes you include in the context can be used by conditional rules configured in the Logfire UI. For example, you could route all enterprise plan users to a specific label.
Use the OpenFeature client to evaluate flags. The client provides typed methods for different value types:
const client = OpenFeature.getClient()
// Boolean flag
const showNewFeature = client.getBooleanValue('show_new_feature', false)
// String value (e.g., a theme or prompt)
const theme = client.getStringValue('ui_theme', 'light')
// Number value
const maxRetries = client.getNumberValue('max_retries', 3)
// Get detailed evaluation info
const details = client.getStringDetails('ui_theme', 'light')
console.log(details.value) // resolved value
console.log(details.variant) // label name (e.g., "production", "canary")
console.log(details.reason) // evaluation reason (e.g., "TARGETING_MATCH", "SPLIT")
The second argument to each method is the default value, returned when the flag can’t be evaluated (e.g., network error, flag not found).
For React applications, OpenFeature provides a React SDK with hooks for flag evaluation:
npm install @openfeature/react-sdk
pnpm add @openfeature/react-sdk
yarn add @openfeature/react-sdk
Wrap your application with the OpenFeatureProvider and use hooks in your components:
import { OpenFeatureProvider, useBooleanFlagValue, useStringFlagDetails } from '@openfeature/react-sdk'
// In your app root
function App() {
return (
<OpenFeatureProvider>
<MyComponent />
</OpenFeatureProvider>
)
}
// In any component
function MyComponent() {
const showBanner = useBooleanFlagValue('show_banner', false)
const theme = useStringFlagDetails('ui_theme', 'light')
return (
<div data-theme={theme.value}>
{showBanner && <PromoBanner />}
<p>Theme variant: {theme.variant}</p>
</div>
)
}
Here’s a complete setup combining initialization, context, and evaluation:
import { OFREPWebProvider } from '@openfeature/ofrep-web-provider'
import { OpenFeature } from '@openfeature/web-sdk'
// Initialize once at app startup
function initFeatureFlags(apiKey: string, apiHost: string) {
const provider = new OFREPWebProvider({
baseUrl: `https://${apiHost}/v1`,
fetchImplementation: (input, init) => {
const headers = new Headers(input instanceof Request ? input.headers : undefined)
new Headers(init?.headers).forEach((value, key) => headers.set(key, value))
headers.set('Authorization', `Bearer ${apiKey}`)
headers.set('Content-Type', 'application/json')
return fetch(input, { ...init, headers })
},
})
OpenFeature.setProvider(provider)
}
// Set context when user authenticates
async function setUserContext(userId: string, attributes: Record<string, string> = {}) {
await OpenFeature.setContext({
targetingKey: userId,
...attributes,
})
}
// Evaluate flags anywhere in your app
function getFeatureFlags() {
const client = OpenFeature.getClient()
return {
showNewDashboard: client.getBooleanValue('show_new_dashboard', false),
pricingTier: client.getStringValue('pricing_tier_config', 'standard'),
maxUploadSize: client.getNumberValue('max_upload_size_mb', 10),
}
}
OpenFeature for Python is two separate installs — the core SDK and the OFREP provider:
pip install openfeature-sdk openfeature-provider-ofrep
from openfeature import api
from openfeature.contrib.provider.ofrep import OFREPProvider
from openfeature.evaluation_context import EvaluationContext
# base_url is the `/v1` root; the provider appends `/ofrep/v1/evaluate/flags/{key}`.
api.set_provider(
OFREPProvider(
base_url='https://logfire-api.pydantic.dev/v1', # or your self-hosted API host
headers_factory=lambda: {'Authorization': 'Bearer YOUR_API_KEY'},
)
)
client = api.get_client()
context = EvaluationContext(targeting_key='user-123')
show_new_feature = client.get_boolean_value('show_new_feature', False, evaluation_context=context)
The provider — like the OpenFeature client in general — evaluates one named flag at a time. To read every variable in a single call, POST the bulk endpoint directly with any HTTP client (there is no bulk method on the client). This example uses httpx (pip install httpx), which is separate from the OpenFeature packages above:
import httpx
response = httpx.post(
'https://logfire-api.pydantic.dev/v1/ofrep/v1/evaluate/flags',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
json={'context': {'targetingKey': 'user-123'}},
)
flags = response.json()['flags'] # [{'key', 'value', 'variant', 'reason'}, ...]
OpenFeature provides SDKs and OFREP providers for many languages. You can use the same Logfire OFREP endpoint with any of them:
| Platform | SDK | OFREP Provider |
|---|---|---|
| Python | openfeature-sdk | openfeature-provider-ofrep |
| JavaScript (Web) | @openfeature/web-sdk | @openfeature/ofrep-web-provider |
| JavaScript (Server) | @openfeature/server-sdk | @openfeature/ofrep-provider |
| Kotlin / Android | OpenFeature Kotlin SDK | OFREP Provider |
| Swift / iOS | OpenFeature Swift SDK | OFREP Provider |
See the OpenFeature ecosystem page for a full list.
The OFREP endpoint format is the same regardless of client:
POST https://<your-api-host>/v1/ofrep/v1/evaluate/flags/{flag_key}
POST https://<your-api-host>/v1/ofrep/v1/evaluate/flags
Both endpoints accept a JSON body with a context object containing targetingKey and any additional targeting attributes:
{
"context": {
"targetingKey": "user-123",
"plan": "enterprise",
"region": "us-east"
}
}
Authenticate with an Authorization: Bearer <api-key> header using a key with project:read_external_variables scope.