TILLSECRETS · QUICKSTART

From zero to a pulled secret.

GA

Create a project and an environment, set a value, mint a token, and pull it into a running app. A few minutes, no .env file to leak.

01Setup

Create a project + environment

A project groups your app’s config; an environment (dev / staging / prod) is where the actual keys live. Do it in the dashboard under /<org>/secrets, or from the CLI:

bash
# Create a project, then an environment inside it.
tilldev secrets projects create --name "Acme API" --slug acme-api
# → project  prj_… (copy the id)

tilldev secrets env create --project prj_… --name Production --slug prod
# → environment  env_… (copy the id)
02Write

Set a secret

Keys are env-var-style — uppercase letters, digits, and underscores. Setting a key that already exists allocates a new version; the old one is kept.

bash
# Set a value — prompts for it (hidden input) if you omit --value.
tilldev secrets set STRIPE_KEY --env env_…
tilldev secrets set DATABASE_HOST --env env_… --value db.internal.example.com

# List the keys in an environment (never the values).
tilldev secrets ls --env env_…
03Auth

Mint a service token

An app authenticates its pull with a ts_… service token. Scope it read for a consumer and pin it to a single environment so it can never read another:

bash
# A read-only token, pinned to one environment — what an app boots with.
tilldev secrets tokens create --project prj_… --scope read --env env_…
# → ts_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4   (shown once — copy it now)
Shown once
The raw ts_… is printed a single time and stored only as a sha256 hash — copy it now. If you lose it, revoke it and mint another. It is a server secret: keep it out of client bundles and version control.
04Install

Install an SDK

bash
pnpm add @tilldev/secrets-node

On an edge runtime (Cloudflare Workers, Vercel, Deno, Bun) use @tilldev/secrets-edge instead. Both wrap the same @tilldev/secrets-core.

Provide the token through the environment:

.env
TILLSECRETS_TOKEN=ts_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
# Optional — only needed if the token is project-scoped (not pinned to an env):
# TILLSECRETS_ENV=env_…
# Optional — override the API base (defaults to https://tilldev.dev):
# TILLSECRETS_URL=https://tilldev.dev
05Read

Pull it into your app

On Node, load() injects everything into process.env. Call it before you read any config:

server.ts
// server.ts — the first thing that runs.
import { load } from '@tilldev/secrets-node'

// Reads TILLSECRETS_TOKEN, pulls the environment, injects each KEY
// into process.env. Existing env vars win (like dotenv) unless you
// pass { override: true }.
await load()

console.log(process.env.STRIPE_KEY)   // sk_live_…  (pulled, not committed)

Prefer an explicit client — or need the list of dynamic keys? Use createClient() and pull():

ts
import { createClient } from '@tilldev/secrets-node'

const secrets = createClient()                 // from TILLSECRETS_* env
const { secrets: values, dynamic } = await secrets.pull()

values.DATABASE_HOST      // 'db.internal.example.com'
dynamic                   // ['DATABASE_URL', …] — keys you lease separately

const stripe = await secrets.get('STRIPE_KEY') // convenience over pull()
06Edge

Or on the edge

Edge runtimes have no process.env, so pass url and token explicitly from the platform’s bindings:

worker.ts
import { createClient } from '@tilldev/secrets-edge'

export default {
  async fetch(_req: Request, env: Env) {
    // Edge runtimes have no process.env — pass url + token explicitly.
    const secrets = createClient({ url: env.TILLSECRETS_URL, token: env.TILLSECRETS_TOKEN })
    const { secrets: values } = await secrets.pull()
    return new Response(values.GREETING ?? 'hello')
  },
}
07Next

What's next

  • Config store — versions, rollback, soft-delete, token scopes, the SDKs in depth, and sync targets.
  • Dynamic secrets — register a Postgres backend and lease credentials that expire on their own.
  • Security — the envelope-encryption model and the audit trail.