API Key Management UI: Create, Show Once, Roll, Revoke
Your product has an API, so your settings need an API key management UI: a screen where a developer creates a key, copies it into an environment variable, and later rolls or revokes it without opening a support ticket. Most of what you find on the topic is either a gallery block that renders a table with a masked string or vendor docs on how keys should be stored. Few connect the two. This guide covers both halves: the screen (create with scopes and expiry, show the secret once, copy, roll, rename, revoke) and the backend contract behind it (store a hash, display a prefix), built in Next.js with the API Keys Page block.
What does an API key management UI need?
It needs five things: a scannable list, a create flow with scopes and expiry, a one-time reveal, guarded roll and revoke, and limits with warnings. Good API key UI design keeps all five, because each one prevents a specific failure:
- A list a human can scan. Name, a masked prefix (
ak_live_3f9a••••), status, scopes, created date, last used and expiry. "Last used 3 hours ago" is what tells someone a key is safe to delete. - Create with intent. A name, the scopes the key may use and an expiry. A key without a name becomes "Key 4" and nobody dares revoke it.
- A one-time reveal. The secret appears once, selected, with a Copy button and a warning that it will not be shown again.
- Roll and revoke, behind a confirmation. Both break running integrations, so both ask first, and revoke is red.
- Limits and warnings. A plan cap on active keys that disables Create with a reason, and an amber "Expires in 5 days" before a key silently stops working.
The API Keys Page block ships all five. Try it: open the row menu on "CI pipeline", roll it, then create a new key.
$ npx wingo-ui@latest add api-keys-pageWhat should the server store for each key?
Store a hash of the secret and a short prefix, never the secret itself. The prefix is display-only, so users can tell keys apart. The hash is how you find the key when a request arrives. Stripe's API keys docs (opens in a new tab) (checked November 2026) say that a secret key you create in live mode is displayed one time and cannot be revealed later, and that if you lose it you rotate it. That is the model to copy.
A key format with a recognizable prefix also helps secret scanners. GitHub explained its ghp_, gho_ and related prefixes, plus a checksum, in a post from April 2021 (opens in a new tab): the underscore is not a Base64 character, so random strings rarely look like tokens, and double-clicking selects the whole token. Use letters and digits only in the body for the same reason.
Thirty-two base62 characters carry about 190 bits of entropy, so a fast SHA-256 with a unique index is enough. A slow password hash like bcrypt protects guessable passwords; here it would only add latency to every API request. Checking a request is one indexed lookup:
Status is derived, not stored: a key with revokedAt is revoked, a key past expiresAt is expired, anything else is active. The block treats an active key whose date has passed as expired too, so the list stays honest even if your data lags.
How do you show the secret only once?
Show it in the same dialog that created it, focused and selected, and make it hard to close by accident. In the block, after you press Create key the dialog swaps the form for a read-only field holding the secret, a Copy Button inside it and a warning. Done stays disabled until you copy or tick "I saved this key somewhere safe", and outside clicks and Escape do nothing until then. The secret never goes into a toast, the URL or storage, and it leaves memory once the dialog closes.
$ npx wingo-ui@latest add api-keys-pageThe same reveal runs after a roll, titled with the key's name. That covers the most common support request about keys, "I lost my key." The answer is Roll, never "we will email it to you." Whatever label your generate API key UI uses (Create key, New token, Generate), the secret should exist in the browser only while this dialog is open.
How do you wire the API keys page to Next.js?
Install the block, keep the scope and expiry lists in a shared module, and pass four Server Actions as its handlers. The install pulls in the dialog, table, copy button, alert dialog and the rest:
Keep the scope and expiry lists in a plain module that both the client view and the Server Actions import. The block's own defaults live in a client file, and a server module should not import values from one.
Leaving out a Never option is a product decision. It forces rotation, at the cost of an expiry email you now have to send.
The Server Actions return a result object instead of throwing. The Next.js error handling guide (opens in a new tab) (checked November 2026) recommends modeling expected errors as return values, and production builds do not forward the message of a thrown server error to the client, so a thrown "You reached the limit of 10 keys" would reach the dialog as a generic error.
Every action scopes the query to the caller's workspace. The client sends an id; the server decides whether that id belongs to them. The limit check counts only keys that are neither revoked nor expired, the same rule the block uses for maxKeys, so an old expired key never blocks a new one. requireAdmin() follows the data access layer from the Next.js admin dashboard tutorial: it checks the session and, in this version, returns the signed-in user and their workspace id.
The client view turns a failed result back into an error, which the block shows inline and keeps the form:
The server page.tsx loads the workspace's keys, maps them to ApiKey with timestamps as numbers, and renders <KeysView keys={keys} />. It can sit as one section of a larger React settings page.
Should rolling a key end the old secret right away?
Not always: end it at once when a key leaked, and allow a short overlap for planned API key rotation. The block's default copy says the current secret stops working right away, which is the safe choice when a key leaked. Stripe takes the other route for planned rotation: as of November 2026 its rotation docs (opens in a new tab) say both the old and new keys work for up to 7 days when you rotate in the Dashboard, so integrations can migrate without downtime.
If you want a grace window, keep the old hash in a second column with its own expiry, accept either in authenticate, and change the confirmation text so it tells the truth:
Revoke stays immediate either way. The roll and revoke confirmations use the Alert Dialog pattern: focus starts on Cancel, the action names what it does, a failed request shakes the card and shows the error in place.
When do you need a data table instead?
When you list keys across every workspace, for support or internal admin. The block uses a simple table because one workspace rarely has more than a dozen keys. A support view across all customers is different: thousands of keys, searched by prefix, filtered by status and creator. That is a job for the Data Table, with a badge column for status, relative columns for last used and expiry, and rowActions for revoke. Keep the one-time reveal out of that view entirely. Support staff should be able to revoke a key, not see one.
How does it behave on phones and for keyboard users?
It switches to cards and bottom sheets on phones and works fully from the keyboard. Below 768px each key becomes a card, the row menu opens as a bottom sheet, and the create dialog is a bottom sheet with full-width buttons. The cursor lands in Name when the dialog opens, Enter submits, and the secret field takes focus, selected, so Copy or Cmd+C is one step away. When the dialog closes, focus returns to the row's menu button or to Create key. Missing fields are named inline next to the field, not only in a toast. These details are what separate an API keys page design that works from one that only looks right in a screenshot.
Which React API key manager should you start from?
Start from the Wingo block if you want the whole key lifecycle wired to your handlers, and from the shadcn.io block if your scopes are a grid of resources by access level. Both are paid:
The shadcn.io block is the better fit when you want its "try this request" checker, which the Wingo block does not have. For more screens like this one, browse the Build it category or the role management UI guide, which pairs well with keys on a team settings page. Key events (created, rolled, revoked) also belong in an audit log UI that shows who changed which key and when.
Next step
Install the block with npx wingo-ui@latest add api-keys-page, point its four handlers at your Server Actions, and keep the hash on the server. The Copy Button it uses is free with a free account; the API Keys Page itself is part of Pro.
FAQ
Should users be able to see an API key again after creating it?
For server keys, no. Show the secret once, store only a hash, and offer Roll when someone loses it. Retrievable keys need encryption at rest and a decryption path, which is more code to protect for little gain.
How should I store API keys in the database?
Store a SHA-256 hash of the full key with a unique index, plus a short prefix for display. Look up incoming keys by hashing them. A random key with 190 bits of entropy does not need a slow password hash.
What is the difference between rolling and revoking an API key?
Revoking ends the key for good. Rolling keeps the key's name, scopes and history but issues a new secret, so the user updates one environment variable instead of recreating the key.
Why do API keys have a prefix like sk_live_ or ghp_?
A prefix tells people and secret scanners what the string is and which environment it belongs to. GitHub added prefixes and a checksum to its tokens in April 2021 so leaked tokens can be detected with few false positives.
How should an API keys page work on a phone?
Turn the table into one card per key, open the row menu as a bottom sheet, and show the create and reveal dialogs as bottom sheets with full-width buttons. The Copy button matters most there, because selecting a 45-character secret by hand on a touch screen is error-prone.
- API keys
- Developer settings
- Security
- Next.js
- SaaS

