Public and Private
Who can read which flag: browsers get public flags with the project ID, servers with a key also get private ones
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.
Every flag is public or private. Both kinds live in the same project snapshot and are decided by the same rules on the Edge. The only difference is who is allowed to receive the answer. Anything that reaches a browser is public by nature: DevTools shows every key and payload.
| Public flag | Private flag | |
|---|---|---|
| Who can read it | Anyone: browsers, apps and servers | Only servers that send a valid server API key |
| What the browser gets | The flag, its answer and its payload | Nothing: the flag is absent from the response, not false |
| Use it for | UI switches, layouts, copy, themes | Server decisions: trusted modes, internal limits, back-office behaviour |
| Read it with | @coframe/sdk/feature-flags, @coframe/sdk/react |
@coframe/sdk/server with apiKey, or the endpoint with Authorization: Bearer |
01One endpoint, two views
Browsers and servers call the same POST /api/v1/flags. The Authorization
header decides what a request may see:
- No header: anonymous. Public flags only; private flags are silently left out, even when named in the selector.
Bearer cffs_v1_…: the Edge hashes the whole token, compares it in constant time with the stored hash, requires the key to be active, and requires the body’sprojectIdto match the key’s project. Then public and private flags are returned.- Bad, revoked or wrong-project key:
401or403. A bad key is refused, never downgraded to an anonymous request.
02How to use each side
// Browser: public flags, project ID only
import { featureFlags } from '@coframe/sdk/feature-flags'
import { useFeatureFlag } from '@coframe/sdk/react'
featureFlags.init({ projectId: 'your-project-id' })
const layout = useFeatureFlag('checkout_experience_v1')
// Server: public and private flags, secret key from the environment
import { evaluateFlags } from '@coframe/sdk/server'
const result = await evaluateFlags({
projectId: 'your-project-id',
apiKey: process.env.COFRAME_FEATURE_FLAGS_SERVER_API_KEY, // never NEXT_PUBLIC_*
userToken: user.id,
})When a server renders the page, pass only the public flags into the HTML.
03Selector groups and private flags
A selector group only expands to the flags that list it. A private flag that is in no group is never returned by a group request, even with a key: ask for it by name.
# Public and Private
Who can read which flag: browsers get public flags with the project ID, servers with a key also get private ones
Every flag is public or private. Both kinds live in the same project
snapshot and are decided by the same rules on the Edge. The only difference is
who is allowed to receive the answer. Anything that reaches a browser is
public by nature: DevTools shows every key and payload.
|
Public flag |
Private flag |
Who can read it |
Anyone: browsers, apps and servers |
Only servers that send a valid server API key |
What the browser gets |
The flag, its answer and its payload |
Nothing: the flag is absent from the response, not `false` |
Use it for |
UI switches, layouts, copy, themes |
Server decisions: trusted modes, internal limits, back-office behaviour |
Read it with |
`@coframe/sdk/feature-flags`, `@coframe/sdk/react` |
`@coframe/sdk/server` with `apiKey`, or the endpoint with `Authorization: Bearer` |
## One endpoint, two views
Browsers and servers call the same `POST /api/v1/flags`. The `Authorization`
header decides what a request may see:
- No header: anonymous. Public flags only; private flags are silently left
out, even when named in the selector.
- `Bearer cffs_v1_…`: the Edge hashes the whole token, compares it in
constant time with the stored hash, requires the key to be active, and
requires the body’s `projectId` to match the key’s project. Then public and
private flags are returned.
- Bad, revoked or wrong-project key: `401` or `403`. A bad key is refused,
never downgraded to an anonymous request.
## How to use each side
TS
```
// Browser: public flags, project ID only
import { featureFlags } from '@coframe/sdk/feature-flags'
import { useFeatureFlag } from '@coframe/sdk/react'
featureFlags.init({ projectId: 'your-project-id' })
const layout = useFeatureFlag('checkout_experience_v1')
// Server: public and private flags, secret key from the environment
import { evaluateFlags } from '@coframe/sdk/server'
const result = await evaluateFlags({
projectId: 'your-project-id',
apiKey: process.env.COFRAME_FEATURE_FLAGS_SERVER_API_KEY, // never NEXT_PUBLIC_*
userToken: user.id,
})
```
When a server renders the page, pass only the public flags into the HTML.
Private is not secret storage
Private flags never reach a browser, but still keep passwords, API keys and
connection strings out of every payload. And keep server keys on the server:
never in browser code or a `NEXT_PUBLIC_*` variable. A key that was pasted
into a chat or a ticket should be rotated.
## Selector groups and private flags
A selector group only expands to the flags that list it. A private flag that
is in no group is never returned by a group request, even with a key: ask for
it by name.