> ## 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 queues

> Early access, one feature at a time: people who already have accounts ask for a part of the product, and a flag lets them in.

A feature queue is a **request**, never a door: the people in it already have
accounts, they are already signed in through your own authentication, and what
they are waiting for is one part of the product — the new checkout, the new
editor, the API that is still in beta.

Which is why it never touches authentication. Being in ten feature queues
changes nothing about anybody's sign-in, and asking for a feature is not
signing in — so nothing here mints a session, mails a way in, or refuses one.

### Asking

The queue's key is the key of the feature's **flag**, in the same environment.
A queue with no flag behind it answers `404 unknown_waitlist` — deliberately:
the flag is what grants the feature (see [Actually turning the feature
on](#actually-turning-the-feature-on)), so a queue without one would be a list
nobody could act on. It is also what keeps the key from being free text written
by anybody holding a contact session.

Create the flag switched off before you open the requests: that is exactly what
it is born as.

```ts theme={null}
const request = await userkit.joinFeatureWaitlist("beta-checkout", {
  metadata: { using: "an API for compilers" },
});
// { waitlist: "beta-checkout", status: "waiting", position: 12, … }
```

In React, the hook draws the whole button:

```tsx theme={null}
import { useFeatureWaitlist } from "@userkit/react";

function BetaCheckoutButton() {
  const beta = useFeatureWaitlist("beta-checkout");
  if (beta.loading) return null;
  if (beta.status === "admitted") return <p>You already have access.</p>;
  if (beta.status === "waiting") return <p>You are #{beta.position} in line.</p>;
  return <button onClick={() => void beta.request()}>Ask for access</button>;
}
```

Asking twice keeps the first place and answers the same either way, so the
button never has to tell "you just joined" from "you were already here".
Nothing is emailed: the person is inside your product, looking at the answer.

`cancel()` takes the request back — and is refused with `409 already_admitted`
once access was granted, because taking access away is your act, in the panel,
and not this endpoint's.

The queue shows up in the panel from the first request, under **Waitlist**, in
the queue selector — the panel lists the queues that have people in them, not
the flags that exist.

### Letting people in

```bash theme={null}
curl -s "$API/v1/organization/waitlist/admit?environment=live&waitlist=beta-checkout" \
  -H "Authorization: Bearer uk_st_…" \
  -H 'Content-Type: application/json' \
  -d '{ "all": true, "label": "the new checkout" }'
```

Admitting here attaches no password and mails no link: these people already
have accounts. What it does is record that they were let in — and publish one
fact per person, which is what the audience reads. `label` is the feature's
name in the announcement email; without it the message falls back to the
queue's key. `"notify": false` turns the announcement off, for when your own
product announces the access.

### Actually turning the feature on

Admitting switches nothing on by itself — your flag does, pointed at a segment
of the people who were let in:

<Steps>
  <Step title="A segment">
    Under **Segments**, one condition: *admitted to* `beta-checkout`.
  </Step>

  <Step title="A flag pointing at it">
    Under **Feature flags**, the `beta-checkout` flag with that segment as its
    audience.
  </Step>

  <Step title="Your code reads the flag">
    `useFlag("beta-checkout")` — like any other. Each admitted person joins the
    audience within seconds, because admission publishes a fact per person.
  </Step>
</Steps>

The queue deliberately does not switch the feature on directly: what each
person sees is the flag's decision, and a queue that turned features on by
itself would be a second place answering the same question — which is exactly
what segments exist to prevent.

<Note>
  Somebody who never asked is not *waiting*. Both segment conditions — *admitted
  to* and *waiting in* — are positive, and a person with no request matches
  neither. "Everybody except the admitted" is already what the flag is when it is
  off.
</Note>
