Skip to content

Variables

An environment is a named set of variables stored as JSON. A workspace can have any number of environments; one is “active” at a time. The active environment is recorded in the workspace’s personal state file so it survives restarts; if that saved environment no longer exists, Fetchpier falls back to the first available one.

Variables are used by:

  • {{name}} placeholders in the URL, headers, body, and form fields.
  • Auth fields — Bearer token, Basic credentials, and API key values can all reference variables. The Basic-auth password is a masked secret field: a literal value is hidden while typing (with a brief last-character reveal), and the value is shown in clear text only when it is exactly a complete {{variable}} reference — any other value, including a literal that begins with {, stays masked.
  • TLS passphrases — a workspace client certificate’s passphrase can be a {{name}} placeholder, plain or secret, so credentials stay in the environment rather than in committed files. App-global certificates remain literal-only — see TLS and certificates.

Wherever a secret is typed behind masking — the certificate passphrase, the Basic-auth password, the proxy password — the same rule applies: characters are hidden, with the most recently typed character shown briefly to help confirm input. In the fields that also accept variables (the workspace certificate passphrase and the Basic-auth password), the value is shown in clear text only when it is exactly a complete {{variable}} reference; every other value stays masked — the same rule that decides the clear-text-literal passphrase warning, so the two never disagree.

Pre-request scripts can mutate environment variables with fp.environment.set('token', value), and Fetchpier persists those changes after the response so subsequent requests see the new value. Each send also has an ephemeral “variables” scratch space (fp.variables.*) that lives only for the duration of that send — useful for passing values between the pre-request and post-response scripts of the same call without polluting the persisted environment.

For the resolution order when several sources define the same name, see Variable precedence.

Sourcing values from the OS or a .env file

Section titled “Sourcing values from the OS or a .env file”

A variable value can reference the host environment with {{$env.NAME}}. The reference is resolved on every send against a fixed source chain:

  1. The workspace’s .env file (if present). When the key exists in .env — including with an empty value — that value wins, even over the OS shell.
  2. The environment of the running Fetchpier (or fp) process.
  3. The empty string when neither source defines the key. The request still goes out, with the unresolved hole visible in the outgoing payload (e.g. Authorization: Bearer ).

This is the supported way to keep an environment’s config.json committable while keeping secrets out of the repository: the JSON contains "apiKey": "{{$env.OPENAI_KEY}}", and the secret lives in .env (gitignored by default) or in the operator’s shell.

{{$env.NAME}} is only recognised inside environment variable values. Putting it directly into a .http URL, header, body, or script leaves it literal — environments are the single canonical place for secrets.

The .env file uses the dotenv format: KEY=VALUE per line, "…" (with \n \r \t \\ \" escapes) and '…' (literal) quoting, # comments at line start or inline after a space, blank lines ignored, duplicates resolved last-wins. Variable expansion inside .env is not supported. Files larger than 1 MiB or not UTF-8 are ignored with a warning. There is no watcher — each send re-reads the file.

The environment editor always shows the raw stored value (e.g. {{$env.OPENAI_KEY}}), never the resolved value, so the live secret never appears in the UI.

Any environment variable can be marked secret. Its value is encrypted in place with a per-workspace key and stored as ciphertext in the same variables map as the plain ones, so the environment — credentials included — can be committed and shared through Git without leaking anything. Marking a variable secret rewrites its value in place, so a Git diff of the operation shows a single value changing, not a key moving. Plain values starting with enc: are reserved (that prefix marks encrypted secrets) and are rejected on entry, in the editor and in scripts.

This is the in-repo complement to {{$env.NAME}}: use {{$env.NAME}} to keep a secret entirely out of the repo, or a secret variable to travel the credential with the repo as ciphertext.

The workspace key. The first time a variable is marked secret, Fetchpier generates a 32-byte workspace key and shows it once, in a human-readable grouped form (FPK1-…), for backup. That is the only moment the key is ever displayed — there is no “show key” screen afterward. Every teammate and every other machine needs it; Fetchpier cannot recover it, so save it (a password manager) and share it out-of-band. A workspace manifest committed at .fetchpier/secrets.json lets any clone verify an entered key without exposing anything.

Opening a shared workspace. A clone whose machine does not yet hold the key prompts for it; the key is validated against the manifest. Until it is entered the workspace is locked: everything works except sending a request that references a secret, which fails with a clear “no key” error. Once entered, the key is remembered on machines with an OS keyring. On Linux without a keyring, Fetchpier refuses to persist under an ineffective fallback and keeps the key for the session only, asking again next launch.

Using and revealing. Once unlocked, secret variables interpolate exactly like plain ones. In the editor a secret renders masked with an eye to reveal it on demand; on macOS with Touch ID the reveal asks for fingerprint confirmation (with a short grace window), and on other platforms it reveals directly. A secret can also be left with no value in this workspace (a “needs value” state) — its name and secrecy travel through Git as a template, and a request referencing it fails precisely until a value is entered.

Masking. Secret values never appear in any artifact Fetchpier writes. The activity log, all CLI reporters, and resolved-request views render a secret as {secret:<name>} and flag the entry as redacted; the real value goes only on the wire. If a server echoes a secret back, Fetchpier masks it in the logged response on a best-effort basis. Scripts are trusted code, so a script that reads a secret’s plaintext and transforms it can produce output masking cannot recognize.

Rotation. “Rotate workspace key” generates a new key, re-encrypts every secret across all environments atomically, and shows the new key once for redistribution. Rotation does not revoke anyone who already had the old key, nor does it change the underlying credentials at their providers — the confirmation dialog states both. Likewise, encrypting a variable that already held a value protects it from now on but does not remove copies already in Git history; if it was ever pushed, rotate the credential at its provider. The editor shows this note before encrypting.

Deleting a secret. A secret row offers a direct delete (trash) action, so a credential can be removed in one gesture — without first converting it to plaintext. Deleting removes the variable as ciphertext (it is never decrypted, and works even when the workspace is locked) and also removes any local value set for that name on this machine. The confirmation is honest about reversibility: for an encrypted secret it states plainly that the value cannot be recovered without the original (and never shows the value); for a “needs value” secret it is lighter, since there is nothing encrypted to lose. Deleting a secret never touches the workspace key, even if it was the last secret — key custody lives only in the Workspace keys tab. This is distinct from making a secret plain, which converts it to a committed plaintext variable and keeps the row.

A leaked key (not ciphertext) is the one accident encryption cannot defend against. A copy-pasteable scanner rule for the FPK1- format is provided so teams can catch a key committed by mistake.

A Workspace keys tab in the general settings lists every workspace key this machine holds, showing each one’s hint, short key id, when it was last used, and how it is stored (“Stored” or, in degraded mode, “This session only”). Browsing the list never asks for a keychain or passphrase — it shows metadata only and never reveals, copies, or exports a key. For each key a status says whether any workspace this app knows about still references it: “In use” (naming the workspaces), “Not referenced” (never phrased as invalid or unused — a clone this app has never seen may still need it), or “Could not check” when a known workspace could not be read. From the tab you can:

  • Delete a key — guarded, because deletion can be terminal. If the key is known to be in use by a workspace this app references, deletion is blocked with guidance to remove the dependent secrets from that workspace first. Otherwise the confirmation states the consequence plainly — the key exists only on this machine, so every secret encrypted with it becomes permanently unrecoverable unless its one-time backup is re-entered — shows the affected workspace name as copy-pasteable text, and enables deleting only once you type that name to confirm. The absence of a known reference never blocks deletion: a clone this app has never seen cannot be ruled out, so that case falls through to the typed confirmation rather than a hard block.
  • Add a key ahead of time — paste an FPK1-… key (with an optional hint) before cloning its workspace, e.g. one a teammate shared during onboarding. The key is checked for typos (checksum) on entry; when the workspace is later opened it unlocks with no prompt and its status flips to “In use”. Re-pasting a key you already hold does nothing; the rare case of a different key under the same id (or a stored copy that has become unreadable) offers to replace it.

Every environment has two value layers, shown side by side in the editor as a Shared column and a Local column:

  • Shared is the team’s environment — committed and shared through Git, plain and secret variables alike.
  • Local is a per-machine overlay that is never shared. It is excluded from Git by default and holds two kinds of values: a personal override of a shared variable, and an unshared variable that exists on your machine and nowhere in the team file. When both layers have a value for a name, the local one wins.

A value only ever travels from the shared layer to the local one, never back. Unchecking Sync copies the team’s value down into your local layer; nothing copies a local value up. The only way a value reaches the team is you typing it into the Shared cell. That single rule is what makes every other gesture safe: no checkbox, no bulk action, and no script can publish a private value to the file your team commits.

Secrecy is a property of the variable, not of a layer: marking a variable secret makes it secret in both columns at once. A local value for a secret-named variable is encrypted with the same workspace key; a local value for a plain variable is stored as plain text, since the file is yours and never leaves the machine.

Two paths write the local layer, and only the local layer:

  • Scripts. fp.environment.set(name, value) writes to the local layer and persists across restarts — the canonical use is a pre-request script that refreshes an access token for the next send. Scripts never write the shared team file.
  • The editor. Each variable row carries a Sync check. Checked means the variable follows the shared value and the Local cell is read-only, so you cannot accidentally diverge by typing. Unchecking is the deliberate act of diverging: it creates a local value seeded with the current shared value (or, for a “needs value” secret, an empty field for your own credential). Re-checking returns the variable to the team’s current shared value, discarding your local one — it confirms first only when the two actually differ. A Sync all action returns every diverged variable in one step.

Unshared variables — a name in your local layer with no counterpart in the team file — are treated as an unresolved state rather than a kind of variable. The row sits at the end of the list with its empty Shared cell reading “Not shared — type to share”, an accent bar on its leading edge, and a tooltip explaining it. Typing a value into its Shared cell gives the team that variable while your local value stays and keeps winning for you; checking Sync discards the local value and warns first that the variable will be left empty, pointing out that typing a shared value first would preserve it. Nothing else writes the team’s file: editing other variables, reordering, opening and closing the editor, and sending requests all leave an unshared name exactly where it is.

Making an unshared variable secret encrypts your local value in place and marks the variable in the team file as a secret needing a value — each teammate supplies their own. Locking states secrecy, not sharing; your value is not published. Deleting a variable removes it from both layers, so it cannot come back as an unshared row.

Local values are personal and regenerable, which shapes two behaviours around the workspace key. On rotation — here or by a teammate whose change you pulled — Fetchpier silently re-encrypts your encrypted local values to the new key the next time the environment loads; if the old key is gone from the machine, the affected local values are dropped with a one-time notice and the variable falls back to its shared value. Running fp run from the command line reads and applies local values identically but never rewrites them: in CI a stale local value is warned about and ignored for that run, so a checked-out workspace is never modified by a run.

Alongside the variables you define, Fetchpier offers a fixed set of dynamic variables addressed as {{$name}} — built-in tokens that resolve to freshly generated random data at send time, so you can drop a throwaway value (a unique idempotency key, a fresh UUID for a POST body, the current timestamp in a header) right where it is consumed instead of hardcoding a literal or writing a script.

The v1 set is purely algorithmic, with no curated data: {{$guid}} / {{$randomUUID}} (a v4 UUID), {{$timestamp}} (UNIX seconds) and {{$isoTimestamp}} (ISO-8601 UTC), {{$randomInt}} (0–1000), {{$randomBoolean}}, {{$randomAlphaNumeric}} (one character), {{$randomHexColor}}, {{$randomIP}} / {{$randomIPV6}}, {{$randomMACAddress}}, {{$randomPassword}} (15 characters), and {{$randomSemver}}. Names are case-sensitive.

  • Fresh every time. Each textual occurrence generates its own value, and every send regenerates — {{$guid}} in a header and in the body yields two different UUIDs in the same send. Nothing is stored: the .http file always shows the token, never a generated value, and no environment is touched. The command-line runner resolves them identically.
  • Where they appear. The {{ suggestion list shows them after your own variables in one flat list, marked with a dedicated dice icon and color; typing $ filters the list to dynamic variables only. In the editors a recognized dynamic token gets its own third highlight color, distinct from a resolved variable (green) and an unknown one (red).
  • Unknown $-tokens. A {{$notARealOne}} is treated like any unresolved variable: left literal on the wire, flagged in the UI, and the send still proceeds.
  • Activity log. Because the values are not secrets, the generated value appears unmasked in the activity log, run snapshots, and CLI reports — a faithful record of what went on the wire, though being random, a snapshot is not re-runnable to the same value.

The entire $ prefix is reserved for these system tokens: a user variable name beginning with $ is rejected on entry, in the environment editor and in scripts (fp.environment.set('$x', …) throws).