> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature flags

> Uma flag decidida uma vez no boot, mantida atual por um documento que só sabe desligar — e por que a direção urgente é desligar.

Uma flag responde "isso já foi entregue para essa pessoa". É uma pergunta de
release sobre o **seu** produto — não "o plano dela permite", que é
[entitlements](/pt-br/guides/entitlements), nem "o assento dela permite", que são
papéis e permissões. Manter os três separados é por que checar uma flag é grátis e
por que servir uma nunca é cobrado: um kill switch que parasse de funcionar porque
uma fatura atrasou falharia no pior momento possível.

## Lendo uma

```ts theme={null}
import { createClient } from "@userkit/js";

const userkit = createClient({ publishableKey: "uk_pk_live_…" });
await userkit.boot();

if (userkit.isEnabled("new-checkout")) {
  // …
}
```

```tsx theme={null}
import { useFlag, Flag } from "@userkit/react";

function Checkout() {
  return (
    <Flag name="new-checkout" fallback={<LegacyCheckout />}>
      <NewCheckout />
    </Flag>
  );
}
```

`isEnabled` e `useFlag` são **síncronos e devolvem um booleano puro**. Isso é o
contrato, não um detalhe de implementação. Um hook que começasse em `undefined`
renderizaria a ausência de toda funcionalidade protegida por um quadro e a
funcionalidade no seguinte — e "ainda não sabemos" e "desligado" são idênticos para
a pessoa, enquanto só um dos dois é seguro. Uma flag que pisca ligada é pior que
uma simplesmente desligada.

Uma chave desconhecida é `false`. Uma flag que ninguém definiu é uma funcionalidade
que ninguém entregou, e uma página que nunca chamou `boot()` vê `false` para tudo,
que é a resposta honesta: ninguém decidiu a audiência dessa pessoa.

## Do que uma flag é feita

| Campo                | Significado                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `key`                | O que seu código lê. Única por ambiente                                                                                  |
| `enabled`            | O portão externo. `false` é desligado para todo mundo — nenhum segmento consultado, nenhum bucket calculado              |
| `segment_id`         | A audiência, ou `null` para todo mundo                                                                                   |
| `rollout_percentage` | Uma porcentagem **daquela audiência**, avaliada depois dela. `null` é sem rampa; `0` é uma rampa que não alcança ninguém |

`null` e `0` serem diferentes é o tipo de distinção que esta API mantém em vez de
colapsar: "sem rampa" e "com rampa, no zero" são estados diferentes de um release,
e guardar `100` para o primeiro perderia o segundo.

**Uma flag mira um segmento e nunca um filtro próprio.** É a regra que impede
audiências de serem reconstruídas módulo a módulo — tudo o que você consegue dizer
sobre quem vê uma funcionalidade, você diz uma vez, num
[segmento](/pt-br/guides/segments), e todo outro módulo lê o mesmo.

## A porcentagem, e qual string ela hasheia

Uma rampa faz bucket pelo **id do contato** assim que a pessoa está identificada, e
pelo **id anônimo do dispositivo** enquanto ela é uma visitante. Os dois são
estáveis enquanto o que eles nomeiam for. Cada flag carrega o próprio sal, então
estar no décimo azarado de uma rampa não diz nada sobre a próxima — um sal
compartilhado faria toda rampa de 10% acertar o *mesmo* décimo dos seus usuários,
que passariam a experimentar seu produto como permanentemente quebrado enquanto
ninguém mais conseguiria reproduzir nada do que eles relatassem.

**Um flicker é real e está dito em vez de escondido.** Uma visitante anônima faz
bucket pelo dispositivo; no momento em que assina, o mesmo humano faz bucket por um
id de contato — string diferente, portanto possivelmente bucket diferente, portanto
uma rampa em que ela estava pode deixá-la de fora um carregamento depois.

Isso é aceito porque as alternativas são piores. Fazer bucket de todo mundo pelo id
do contato não evitaria (uma visitante anônima *tem* uma linha de contato, e assinar
faz o merge dela, então o id muda de qualquer jeito). Carregar o id anônimo para
sempre faria a audiência de uma rampa depender de um cookie que a pessoa pode
limpar, o que transforma "10% dos usuários" em "10% dos navegadores, re-amostrados
sempre que alguém limpa o histórico" — uma porcentagem que deriva em silêncio é pior
que uma que muda uma vez, num momento em que a pessoa já está vendo a página mudar.

O que decorre disso para você é uma frase e não um mecanismo: **não coloque um
checkout atrás de uma flag de porcentagem para tráfego deslogado.**

## O kill switch, e o atraso honesto dele

O valor da flag para *esta pessoa* — segmento, porcentagem, tudo — é decidido pelo
`boot()`, na mesma ida e volta que a sessão já custou. Depois disso, o cliente faz
poll de um documento público:

```http theme={null}
GET /v1/flags/{publishable_key}
```

```json theme={null}
{
  "flags": [{ "key": "new-checkout", "enabled": false }],
  "version": "…",
  "max_age_seconds": 15,
  "generated_at": "2026-08-02T12:00:00Z"
}
```

Ele leva o que é verdade para **todo mundo** — o interruptor — e nada sobre quem.
Aplicado sobre o snapshot do boot, **ele só sabe tirar uma flag**:

* **desligar uma flag alcança uma página aberta em cerca de quinze segundos**, sem
  nenhum refresh de sessão envolvido;
* **alargar uma rampa, ou re-mirar um segmento, vale a partir do próximo boot.**

Essa assimetria é o desenho. A direção urgente é desligar — "o checkout está
quebrado, desliga agora" é uma frase com relógio junto, e "todo mundo do segmento
novo deveria receber isso" não é.

É também por que uma flag **não é claim de JWT**. Um token de cinco minutos torna a
resposta eventual por construção, e um kill switch que não pode valer por cinco
minutos não é um kill switch. Um push — um socket, um stream — seria mais rápido e é
a troca errada neste tamanho: uma conexão por página aberta, mantida através de cada
deploy, cujo modo de falha é o seu produto silenciosamente parar de ouvir sobre
mudanças e cuja recuperação é um poll de qualquer forma.

O documento é cacheável, endereçado pela publishable key que sua página já tem, e
seguro atrás de um CDN. `version` é opaca — compare por igualdade para saber que nada
se moveu, nunca por ordem.

## O que acontece quando a rede não está lá

Um refresh que falha **mantém o último documento**. Ele nunca esvazia as flags. Um
kill switch que abre sempre que a rede cai é o oposto do que ele existe para fazer, e
degradar para "sem flags" durante um minuto ruim faria seu produto voltar ao estado
pré-flag para todo mundo — nossa indisponibilidade virando a sua, por causa de uma
funcionalidade que você ligou semana passada e não pensa mais nela.

O documento leva `stale-if-error` ao lado do max-age pelo mesmo motivo. O poll roda
apenas enquanto a página está **visível**; uma aba em segundo plano não é uma página
onde alguém está prestes a ser surpreendido.

Uma **flag arquivada some do documento**, e uma chave que não está nele é `false`.
Arquivar é como você encerra uma rampa da qual desistiu — para quem lê, é
indistinguível de um delete, que é o que se quer.

## No servidor

Não há intervalo nem visibilidade para se apoiar, então `refreshFlags()` é a leitura
explícita — para um servidor, para um teste, ou para o momento logo depois de um ato
que você sabe ter mudado algo.

```ts theme={null}
await userkit.refreshFlags();
```

Com [`@userkit/nextjs`](/pt-br/customer-auth/session-tokens) o mesmo documento é
buscado pela sua própria origem, então a publishable key fica fora do bundle.

## No painel

**Engagement → Flags**, atrás de `flags:manage` — owner e admin por padrão. Uma
permissão cobre a superfície inteira aqui, diferente de segmentos, onde ler é
`customers:read` porque a leitura de um segmento *é* uma lista filtrada de contatos.
A leitura de uma flag não é a vista de nada que outra permissão já mostre: é o estado
de release do seu produto, então ela não tem outra casa.

Ela é uma permissão separada de `segments:manage` justamente porque as duas são
vizinhas. Definir uma audiência é descrever pessoas; virar uma flag é entregar ou
desentregar uma funcionalidade no minuto em que ela é virada, para quem estiver com
a página aberta. Uma organização que quer seu analista de growth construindo
audiências sem poder desligar o checkout diz isso concedendo uma e não a outra.

Virar uma flag publica `flag.updated` no barramento interno, e esse fato
deliberadamente **não sai do prédio** como webhook: ele nomeia o estado de release de
um ambiente, e o documento já é a forma suportada de saber de uma mudança.

O **sal do bucket nunca é devolvido pela API**. É o único valor que permitiria a
alguém calcular em que bucket está e então escolher um identificador que caia dentro
de uma rampa.
