Server and SSR
Evaluate public and private flags from your server with @coframe/sdk/server, or call the Edge endpoint from any language
Copy this guide and your current settings as a prompt for your AI agent.
Preview agent promptSee the guide, code and settings included when you copy.
Server code sends a server API key and gets public and private flags.
Every call is async and carries apiKey and projectId; the key stays in an
environment variable and never reaches a browser bundle.
01Node and server-side rendering
import {
evaluateFlags,
isFeatureEnabled,
getFeatureFlag,
getRemoteConfig,
} from '@coframe/sdk/server'
const options = {
projectId: 'your-project-id',
apiKey: process.env.COFRAME_FEATURE_FLAGS_SERVER_KEY!,
userToken: user.id,
currentUrl: request.url,
userContext: { plan: user.plan },
}
const cart = await evaluateFlags({ ...options, selector: { groups: ['cart_checkout'] } })
const enabled = await isFeatureEnabled('checkout-redesign', { ...options, defaultValue: false })
const variant = await getFeatureFlag('checkout-layout', options)
const nav = await getRemoteConfig('navigation-config', options)| Function | Behaviour |
|---|---|
evaluateFlags(options) |
One request for several flags; returns the full response (flags, errorsWhileComputingFlags, requestId, snapshotVersion) |
getFeatureFlagResult, getFeatureFlag, isFeatureEnabled(key, { defaultValue }), getFeatureFlagPayload / getRemoteConfig |
Each sends its own request for one flag |
Options: apiKey, projectId, userToken, selector, requestTimeoutMs
(default 3000), edgeUrl, and the context fields currentUrl, languages,
browserPlatform, country, device, userVisitCount, userContext.
On a timeout, a network error or a non-2xx response the helpers return
undefined. Pass a defaultValue so your code always has a safe answer.
Identity on the server
Only what you pass counts. Use userToken: user.id, or read the
coframe.user.token cookie yourself to share identity with the browser SDK.
Pass nothing and any partial rollout or weighted split answers
missing_user_token. Never make up a fresh id per request: every request
would land in a different bucket.
Rendering a page
Evaluate with the key on the server, then pass only the public flags into the HTML the browser receives. Keep an allow-list of the public keys your page needs and drop everything else from the server’s answer. A private flag’s answer can still shape the page, for example a trusted checkout mode, as long as only the outcome reaches the browser.
02Any language: the HTTP endpoint
POST https://edge.cofra.me/api/v1/flags
Content-Type: application/json
Authorization: Bearer cffs_v1_… (only for private flags; never from a browser)
{
"projectId": "your-project-id",
"userToken": "customer-123",
"selector": { "groups": ["cart_checkout"] },
"currentUrl": "https://shop.example.com/checkout",
"languages": ["en-US", "en"],
"userContext": { "plan": "pro" }
}{
"flags": {
"checkout_experience_v1": {
"key": "checkout_experience_v1", "enabled": true, "reason": "condition_match",
"variant": "standard", "payload": { "layout": "standard", "cta": "Place order" },
"metadata": { "version": 1 }
},
"checkout_v2": { "key": "checkout_v2", "enabled": false, "reason": "no_matching_condition", "metadata": { "version": 1 } },
"navigation-config": {
"key": "navigation-config", "enabled": true, "reason": "remote_config",
"payload": { "theme": "ocean", "navigation": "compact" }, "metadata": { "version": 1 }
}
},
"errorsWhileComputingFlags": false,
"requestId": "…uuid…",
"snapshotVersion": 4
}| Field | Notes |
|---|---|
projectId |
Required |
userToken |
Up to 512 characters. Needed for partial rollouts and weighted variants |
selector |
names and/or groups, up to 100 each; the union is evaluated, unknown keys are skipped. Omit it for every flag |
currentUrl |
Absolute URL, up to 4,096 characters |
languages, browserPlatform, country, device (desktop or mobile), userVisitCount |
Optional context. Country defaults to the request’s cf-ipcountry; device is detected from the user agent when omitted |
userContext |
Your own properties, up to 4 KB of JSON, matched by User property conditions |
Every response is Cache-Control: private, no-store, so no CDN or browser
shares one visitor’s answer with another. The snapshot is read from the Edge
store on every request, so a publish is visible on the next request.
See Reference for the status codes and the
reason values.
# Server and SSR
Evaluate public and private flags from your server with @coframe/sdk/server, or call the Edge endpoint from any language
Server code sends a server API key and gets public and private flags.
Every call is async and carries `apiKey` and `projectId`; the key stays in an
environment variable and never reaches a browser bundle.
## Node and server-side rendering
TS
```
import {
evaluateFlags,
isFeatureEnabled,
getFeatureFlag,
getRemoteConfig,
} from '@coframe/sdk/server'
const options = {
projectId: 'your-project-id',
apiKey: process.env.COFRAME_FEATURE_FLAGS_SERVER_KEY!,
userToken: user.id,
currentUrl: request.url,
userContext: { plan: user.plan },
}
const cart = await evaluateFlags({ ...options, selector: { groups: ['cart_checkout'] } })
const enabled = await isFeatureEnabled('checkout-redesign', { ...options, defaultValue: false })
const variant = await getFeatureFlag('checkout-layout', options)
const nav = await getRemoteConfig('navigation-config', options)
```
Function |
Behaviour |
`evaluateFlags(options)` |
One request for several flags; returns the full response (`flags`, `errorsWhileComputingFlags`, `requestId`, `snapshotVersion`) |
`getFeatureFlagResult`, `getFeatureFlag`, `isFeatureEnabled(key, { defaultValue })`, `getFeatureFlagPayload` / `getRemoteConfig` |
Each sends its own request for one flag |
Options: `apiKey`, `projectId`, `userToken`, `selector`, `requestTimeoutMs`
(default 3000), `edgeUrl`, and the context fields `currentUrl`, `languages`,
`browserPlatform`, `country`, `device`, `userVisitCount`, `userContext`.
On a timeout, a network error or a non-2xx response the helpers return
`undefined`. Pass a `defaultValue` so your code always has a safe answer.
Identity on the server
Only what you pass counts. Use `userToken: user.id`, or read the
`coframe.user.token` cookie yourself to share identity with the browser SDK.
Pass nothing and any partial rollout or weighted split answers
`missing_user_token`. Never make up a fresh id per request: every request
would land in a different bucket.
Rendering a page
Evaluate with the key on the server, then pass only the public flags into
the HTML the browser receives. Keep an allow-list of the public keys your page
needs and drop everything else from the server’s answer. A private flag’s
answer can still shape the page, for example a trusted checkout mode, as long
as only the outcome reaches the browser.
## Any language: the HTTP endpoint
HTTP
```
POST https://edge.cofra.me/api/v1/flags
Content-Type: application/json
Authorization: Bearer cffs_v1_… (only for private flags; never from a browser)
{
"projectId": "your-project-id",
"userToken": "customer-123",
"selector": { "groups": ["cart_checkout"] },
"currentUrl": "https://shop.example.com/checkout",
"languages": ["en-US", "en"],
"userContext": { "plan": "pro" }
}
```
JSON
```
{
"flags": {
"checkout_experience_v1": {
"key": "checkout_experience_v1", "enabled": true, "reason": "condition_match",
"variant": "standard", "payload": { "layout": "standard", "cta": "Place order" },
"metadata": { "version": 1 }
},
"checkout_v2": { "key": "checkout_v2", "enabled": false, "reason": "no_matching_condition", "metadata": { "version": 1 } },
"navigation-config": {
"key": "navigation-config", "enabled": true, "reason": "remote_config",
"payload": { "theme": "ocean", "navigation": "compact" }, "metadata": { "version": 1 }
}
},
"errorsWhileComputingFlags": false,
"requestId": "…uuid…",
"snapshotVersion": 4
}
```
Field |
Notes |
`projectId` |
Required |
`userToken` |
Up to 512 characters. Needed for partial rollouts and weighted variants |
`selector` |
`names` and/or `groups`, up to 100 each; the union is evaluated, unknown keys are skipped. Omit it for every flag |
`currentUrl` |
Absolute URL, up to 4,096 characters |
`languages`, `browserPlatform`, `country`, `device` (`desktop` or `mobile`), `userVisitCount` |
Optional context. Country defaults to the request’s `cf-ipcountry`; device is detected from the user agent when omitted |
`userContext` |
Your own properties, up to 4 KB of JSON, matched by User property conditions |
Every response is `Cache-Control: private, no-store`, so no CDN or browser
shares one visitor’s answer with another. The snapshot is read from the Edge
store on every request, so a publish is visible on the next request.
See [Reference](../docs/feature-flags-reference.html) for the status codes and the
`reason` values.