Skip to content

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.

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.
  • label and description are 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.

src/secrets.ts
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");
get(slot: string): Promise<string>
  • slot — the slot key declared in src/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).

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;
}
interface SecretSlotDef {
label: string;
description: string;
}
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).