Keys that never live in your code.
A key pasted into a source file is one careless share away from being public. Hatchable removes that step. Your app declares which keys it needs, you paste each value once into a form the platform owns, and the value is fetched from encrypted storage at request time.
Your agent names the key. You supply the value.
The declaration is the part that is safe to write into a file, so that is the only part your agent writes. The value goes somewhere your agent cannot reach and your project files never mention.
[[secret]]
key = "SLACK_WEBHOOK_URL"
description = "Incoming webhook for #bookings."
# scope defaults to this one project
# required defaults to TRUE, so the app
# asks for this before it will run
import { config, db } from 'hatchable';
export const access = 'public';
export const methods = ['POST'];
export default async function (req, res) {
const { name, slot } = req.body;
await db.query(
`INSERT INTO bookings (name, slot)
VALUES ($1, $2)`,
[name, slot]
);
// resolved server side, at request time
const hook = await config.get('SLACK_WEBHOOK_URL');
await fetch(hook, {
method: 'POST',
body: JSON.stringify({ text: `Booked: ${name}` }),
});
res.json({ ok: true });
}
Everything a key needs to stay a secret.
One store covers all of it: the provider keys behind every AI call, the signing secret that proves an inbound webhook is genuine, and the tokens behind an account you connected once.
Named in the manifest, pasted in a form
A [[secret]] block names the key and says what it is for. Nothing else. Required is the default, so a key your app genuinely needs cannot be quietly forgotten and discovered later by a customer.
The file knows there is a webhook. It does not know the webhook.
Scoped to one app
The default. The value belongs to this project and no other, and it is read back only by this project's code, only while a request is running. Right for anything specific to one app: a webhook URL, a signing secret, an internal token.
The Slack hook in your booking app stays in your booking app.
Set once, found by everything you build
Add a key at the account level and every project on your account can use it without you pasting it again. These never enter app code at all: the platform attaches them to the outbound call itself, which is what makes them safe to share across projects you forked from someone else.
One AI key behind your reading log, your expense tracker, and your Friday digest.
Everyone brings their own
In an app with sign-in, each person can store their own key against their own account. Your code asks for the value on behalf of whoever is making the request, and the platform hands over that person's. Their usage lands on their bill, not yours.
Every teacher who copies the worksheet generator plugs in their own key.
Encrypted, and never handed back
Values are encrypted at rest in all three stores. No endpoint returns one. The console tells you whether a key is set, when it changed, and its last four characters, which is enough to check you pasted the right one and not enough to be worth stealing.
A page you can screen-share without thinking about it.
The app asks for what it is missing
When a required value has not been set, the read raises a setup error instead of a blank 500. In the browser, the platform catches it, shows a paste box for that one key, saves it, and retries the request that failed. Nobody has to go hunting for a settings page.
The missing key becomes a question, at the moment it matters.
The knobs that are not secrets
Headlines, colours, a list of links, an image, a switch. Declare them with [[config]] and the console renders a form for them, validated against what you declared. Saves take effect on the next request, with no redeploy and no rebuild.
Change the tagline on the menu page from your phone.
Sign in once, not for every call
For services with a proper login, declare an [[api]] block and the platform runs the connect flow for you: the redirect, the approval, the token exchange, the refresh before expiry. Your code makes an ordinary call and never holds the token.
Connect the Notion workspace once. Every page read after that just works.
The spec sheet, in plain terms.
[[secret]] block per key in hatchable.toml, carrying the key name plus an optional description, scope, default and allowed-values list.required = false on purpose.await config.get('KEY'). Resolved on the server in one order: the current user, then the project, then the account, then the declared default. First hit wins.expose = true flag allows it for project-scope values only. On account or per-user scope the deploy is rejected with an explanation.[[config]] declares owner-editable fields: text, number, boolean, email, url, colour, image, a choice from a list, or a repeatable list of rows. The console renders and validates the form.Behavior current as of August 2026 and enforced by the deploy validator, not by convention. All three scopes are included on every plan, including free.
Say it like this.
You do not need to know which block is which. Say what you want to be true and your agent picks the right one.
Asked and answered.
Where does the key actually live?
In encrypted platform storage, in one of three stores depending on the scope you chose: the project, your account, or an individual signed-in user of your app. Nothing writes it into your project files, and no endpoint a browser can reach will hand it back. The console shows you whether a key is set and its last four characters, never the key itself.
Does my AI assistant ever see the key?
No. Your agent writes the declaration, which is the name of the key and a note about what it is for. You paste the value yourself, into a form the platform owns. That split is the whole design: the part that gets written into files is the part that is safe to write into files.
What happens if a required key has not been set yet?
The read fails with a typed setup error rather than a mystery crash. A small script the platform injects into every project catches it in the browser, shows a paste box for that one key, saves it, and retries the request that failed. In a background job with nobody watching, the same error surfaces in your logs with the key name and a link to set it.
Can each person who uses my app bring their own key?
Yes. Declare the value at user scope and every signed-in person stores their own. Your handler asks for it on behalf of a specific user and the platform looks up that person's value. Their usage lands on their own bill, which is what makes a shared tool viable when you do not want to pay for everyone.
What is the difference between a secret and a setting?
A secret is something that would be dangerous to show anyone: an API key, a signing secret, a token. A setting is something you want the owner of the app to change: a headline, a colour, a list of links. They use different blocks and different storage. Settings render as a form in the console and save without a redeploy. Secrets never render at all.
Is any of this a paid feature?
No. All three scopes work on every plan, including free, and there is no per-key charge or add-on to switch on. It is part of what a project is.
The full field reference lives in the secrets documentation, with the settings side in the config reference.
Stop pasting keys into files.
Connect the AI you already use, describe the app you want, and the keys it needs are asked for properly instead of typed into a source file.
Want the technical surface? The secrets reference covers scopes, resolution order, and the setup flow.