secrets
Credentials your app uses without ever holding them. For the model — the person’s secret catalog, slots, and linking — see the Secret Store.
import { defineSecrets, secret, secrets, SecretUnavailableError } from "@lodekit/sdk";Unlike every other handle, secrets is server-only: values are read in
server functions and never reach the browser.
The src/secrets.ts contract
Section titled “The src/secrets.ts contract”Declare the slots your app needs in src/secrets.ts and make the result the
default export:
defineSecrets<S extends SecretSlots>(slots: S): DefinedSecrets<S>Each slot is built with secret():
secret({ label: string, description: string }): SecretSlotDef- Slot keys are snake_case (
/^[a-z0-9_]+$/), max 64 characters. labelanddescriptionare required — they’re what the person sees when linking.
A slot is a named request, not a value. The person links a secret from their catalog to each slot in the dashboard — the link is the grant, and it can be revoked there too. Your app never sees which catalog entry was linked, only the value at read time.
Example
Section titled “Example”import { defineSecrets, secret } from "@lodekit/sdk";
export default defineSecrets({ weather_api_key: secret({ label: "Weather API key", description: "Used to fetch the local forecast for watering advice.", }),});Importing that default export gives you a typed get() keyed by your slots:
import appSecrets from "./secrets";
const key = await appSecrets.get("weather_api_key");secrets.get()
Section titled “secrets.get()”get(slot: string): Promise<string>slot— the slot key declared insrc/secrets.ts.
Resolves to the linked secret’s value. Server-only: in the browser it
throws immediately — before any request is made — with
Error("secrets are server-only; call secrets.get() from a server function").
When the slot can’t produce a value, it throws SecretUnavailableError:
reason: "unlinked"— the person hasn’t linked a secret to this slot yet.reason: "unavailable"— the secret store is degraded (for example, the Keychain-held master key can’t be read right now).
Example
Section titled “Example”Treat “unlinked” as a state to render, not an error to crash on:
try { const key = await secrets.get("weather_api_key");} catch (e) { if (e instanceof SecretUnavailableError && e.reason === "unlinked") { return { forecast: null }; // person hasn't linked a key yet — degrade, don't crash } throw e;}SecretSlotDef
Section titled “SecretSlotDef”interface SecretSlotDef { label: string; description: string;}SecretUnavailableError
Section titled “SecretUnavailableError”class SecretUnavailableError extends Error { constructor(reason: "unlinked" | "unavailable", message?: string); readonly reason: "unlinked" | "unavailable";}reason—"unlinked"(link a secret in the dashboard) or"unavailable"(the secret store is degraded).