Variables
Every variable is a write-only secret unless its name starts with FS_PUBLIC_.
Variables are platform-owned, environment-resolved values stored outside the repository. There is nothing to configure about how a value is delivered: the name is the class.
| Name | Class | Browser readable? | Where it goes | Rebuild after a change? |
|---|---|---|---|---|
FS_PUBLIC_* | public | Yes — every visitor | the frontend build environment, then the shipped bundle | Yes |
| anything else | secret | No — write-only | the frontend build environment (leak-scanned), and the edge when an integration references it | No |
A secret is never returned by the API, CLI, or dashboard after you set it; you can rotate it, not read it back. A public value is returned by variable listing because anyone can already read it from the shipped bundle.
Farther Shore Variables do not set environment variables in your backend
application process. Put FS_RUNTIME_TOKEN, database URLs, and other backend
process secrets in that deployment provider's secret manager.
Public: FS_PUBLIC_ values ship to every visitor
Use the FS_PUBLIC_ prefix only for values the provider explicitly documents
as safe for every visitor: a PostHog project key, a Stripe publishable key, a
Sentry DSN, a Supabase anonymous key.
printf '%s' "$POSTHOG_PROJECT_KEY" | \
farthershore variables set my-business FS_PUBLIC_POSTHOG_KEY --idempotency-key <persisted-variables-set-attempt-key>
The value enters the frontend build and is inlined into the bundle. Read it in
the managed frontend as import.meta.env.FS_PUBLIC_POSTHOG_KEY. It is
deliberately excluded from secret leak scanning, because appearing in the
bundle is its purpose.
The dashboard asks for one confirmation before creating a public variable. Never put a secret key behind the prefix, regardless of what the value is called elsewhere.
The retired bundler prefixes (PUBLIC_, NEXT_PUBLIC_, VITE_PUBLIC_) are
rejected as names, so a public-looking name can never be a secret by accident.
Secret: everything else stays private
A variable without the prefix is a secret. It is available to the isolated
frontend build (the runner exposes it to the build command, removes the
temporary assignment, and fails the build if the value survives into the
output in any supported encoding), and it is injected at the edge for any
compiled fs.frontendIntegration() that references it by name.
printf '%s' "$SENTRY_AUTH_TOKEN" | \
farthershore variables set my-business SENTRY_AUTH_TOKEN --idempotency-key <persisted-variables-set-attempt-key>
printf '%s' "$PROVIDER_SECRET" | \
farthershore variables set my-business PROVIDER_SECRET --idempotency-key <persisted-variables-set-attempt-key>
const response = await fs.integration("provider").fetch("/v1/messages", {
method: "POST",
body: { kind: "json", value: { message } },
});
You do not declare where a secret is used. When a compiled integration names
it in secretRef, the platform publishes it to the edge; when no active
integration references it any more, it is retired from the edge. Browser code
never receives the secret or chooses the upstream URL. Referencing an
FS_PUBLIC_ name from secretRef is a compile error.
A secret is not a runtime server environment. Hosted frontends are static artifacts; if browser behavior needs a private credential at request time, use a backend route or a compiled integration.
Environment resolution
Variables are resolved by name for the selected business environment:
- a branch environment value overrides Main;
- when the branch has no row for that name, it inherits Main;
- changing a branch value does not modify Main;
- deleting/revoking the branch override reveals the inherited effective state only after the lifecycle operation completes.
Use --env with a preview environment name or id:
printf '%s' "$STAGING_KEY" | \
farthershore variables set my-business FS_PUBLIC_POSTHOG_KEY --env staging --idempotency-key <persisted-variables-set-attempt-key>
farthershore variables list my-business --env staging --format json
The frontend build and the edge path resolve the same environment selection. Environment routing is platform-owned; frontend code should not switch values by hostname itself.
Rebuild behavior
A public value is part of the bundle, so creating, rotating, revoking, or deleting one enqueues a rebuild for that environment once the new generation is effective.
A secret does not rebuild the frontend. It never enters the bundle; the edge uses the new generation as soon as it is published, and the next build picks it up on its own.
Monitor the mutation and any resulting build:
farthershore variables status my-business <operation-id> --format json
farthershore frontend status my-business --wait
Because a variable mutation has no Git commit identity, this unpinned wait is
advisory only. Add --env <environment-id> to frontend status for a preview
build. For a Git-triggered build, use --ref "$(git rev-parse HEAD)" --wait
instead.
Rotate, revoke, and delete
Values are read from stdin, never from a CLI argument:
printf '%s' "$NEW_VALUE" | \
farthershore variables rotate my-business PROVIDER_SECRET --idempotency-key <persisted-variables-rotate-attempt-key>
farthershore variables revoke my-business PROVIDER_SECRET --yes --idempotency-key <persisted-variables-revoke-attempt-key>
farthershore variables rm my-business PROVIDER_SECRET --yes --idempotency-key <persisted-variables-rm-attempt-key>
Names are immutable. Because the name is the class, turning a secret into a public value (or back) means revoking, deleting, and creating it under the new name — a value can never silently cross that boundary.
revoke removes a value from future builds and, for a secret an integration
references, from the edge once the platform acknowledges it. rm permanently
destroys ciphertext and requires the variable to be revoked first. Use
variables status when a mutation reports publication pending.
Choosing the name
- If every browser user may inspect the value, start the name with
FS_PUBLIC_. - Otherwise it is a secret — the build can use it, a compiled integration can inject it, and no one can read it back.
- If your own server process needs it, use the server host's secret manager, not a frontend Variable.