Install stet
stet works by putting the copy in your codebase behind typed keys, so you can change the words on a page without rebuilding or redeploying anything. This page takes you from installing it in an existing site to publishing your first change. stet doesn't touch any of your components until you tell it to.
You need Node 22 or newer and a git repository. A Postgres database is optional. If you don't have one, stet runs snapshot-only and keeps your copy in a committed file.
stet works with Next.js, React and Astro sites. A site that's only HTML files has its own chapter: plain HTML sites.
01Run init
init detects your framework and whether you have a database, and asks two questions: what kind of site this is, and whether it sends email. It writes the content registry (content/descriptor.json), the committed fallback (content/defaults.json), the read path and the generated key types. Except for mounting the provider in your root layout, your components are completely untouched, and running it twice is a no-op. On an Astro site there's no provider to mount: you read copy with copy('key') in your templates.
It also leaves a short, marked guidance note in AGENTS.md (or CLAUDE.md) so coding agents route copy changes through stet instead of editing source — eject removes it. It ends by running stet check and printing the next command.
$ npm install @getstet/stet
$ npx stet init
site: core (default — the pack catalogue expands in a later release)
wrote content/descriptor.json
⋮
src/pages/_app.tsx: mounted CopyProvider
wrote AGENTS.md (agent guidance)
snapshot: 5 keys declared, 0 missing, 0 stale
generated: content/keys.ts current
generated: content/defaults.ts current
values: 5 checked
next: stet scan02Render your first key
Every string is a typed key. The value comes from your store when you have one, and from defaults.json when you don't — or whenever a read fails. A missing value is an absence, never a broken page. A misspelled key is a compile error, not a blank space in production.
import { copy } from "@/lib/content";
<h1>{copy('hero_headline')}</h1>03Scan for the copy you already wrote
scan reads your components and lists the string literals that are really copy. Nothing is changed.
$ npx stet scan04Register what it found
register prints each rewrite as a diff — the leaf only, callers untouched — and --write applies it. The adopted values land in defaults.json in the same commit.
$ npx stet register --from scan05Change a sentence
With a store, publishing is a database transaction — no build, no deploy, live on the next request.
Snapshot-only, the committed value is the live value: change it in the dashboard (npx stet dev) or in defaults.json, and commit. Your deploy is the publish.
$ npx stet draft hero_headline --value "Copy changes go live in seconds."
$ npx stet publish hero_headline
published hero_headline (default) as cli:neil — version 1506Check it
doctor verifies the install end to end. seo check audits your committed copy offline — no network, no database, so it runs anywhere, including a fork PR with every secret unset.
$ npx stet doctor
$ npx stet seo checkNext
- How stet works — the model behind every command you just ran
- Everything stet does — the launch capabilities
- Email slots — make email copy editable without touching your templates
- The eject promise — leaving is one command, and your repo already holds everything