AstroCraft — home
AstroCraft
Buy a licence
All documentation

Environment variables

Every variable the CMS reads, which of them a .env file can reach, and the measured reason two of them cannot live there.

Last updated

Nothing here is required to build. Every variable is optional, a fresh clone builds and runs with an empty environment, and the features that need one refuse with a readable message rather than failing the build.

The two that go in .env

RESEND_API_KEY=
MAIL_FROM=

RESEND_API_KEY — from Resend → API keys. Used by every action that sends mail.

MAIL_FROM — a verified sender on a domain you own in Resend. One mailbox for the whole product, not a sender per feature. Replies to an invite go to whoever sent it.

Both are read through astro:env/server rather than import.meta.env, because on Workers-style runtimes secrets exist only in the runtime environment. Both must be declared in astro.config.mjs’s env.schema even if you never set a value — see Mounting. The values are optional; the schema is structurally required, and without it the build dies with [MISSING_EXPORT].

What happens with them unset

Everything else works. The screens render, the guard guards, sign-in signs in, and the two mail paths refuse with a message on the page and a line in the server log.

Concretely: /admin/team/ can still mint an invite and hand you the link to copy — it just cannot mail it. And /admin/signup/ answers “we can’t create accounts right now, please try again later”, which is permanent rather than transient, because confirming a mailed link is how an address is proved. Use the accounts CLI for the first account either way.

Resend is reached with a plain fetch — no SDK, ten-second timeout — and it never throws. Provider errors go to the server log only, because they can name the account, the sender domain and the mailbox.

The two that cannot go in .env

export ASTROCRAFT_DB_URL=libsql://…turso.io
export ASTROCRAFT_DB_TOKEN=

These are read from process.env — by astro.config.mjs at config load, and by the accounts CLI under plain Node. A .env file reaches neither: Vite loads it into import.meta.env, which is where the Resend pair is read from and nowhere these are.

This was verified rather than assumed: putting the URL in a .env and rebuilding left sessions on the filesystem and the CLI writing to .accounts/. It would look like it worked and silently do nothing, which is the one thing a configuration file must never do.

Export them in the shell, or use your host’s environment UI. See Bring a database.

The store’s path is not a variable

There is no variable that says where .accounts/ goes. The path is the literal string .accounts, resolved against process.cwd() — so it is decided by where you start the process, not by configuration.

That is worth knowing before you go looking for the setting: the way to move the store is to move the process’s working directory, or to set ASTROCRAFT_DB_URL and stop having a path at all.

The container’s variables

These belong to the deployment rather than to the build, and go in your platform’s environment UI:

Variable Required What it is
GIT_URL yes the repository’s HTTPS clone URL
GIT_PUSH_TOKEN one of these two a fine-grained PAT, Contents: read and write
GIT_SSH_COMMAND e.g. ssh -i /run/secrets/deploy_key, with an SSH GIT_URL
GIT_COMMITTER_NAME yes the deployment’s committer name
GIT_COMMITTER_EMAIL yes the deployment’s committer address
GIT_BRANCH no (default staging) the branch the checkout lives on
PORT / HOST no (4321 / 0.0.0.0) the standalone server

The push credential is passed to git through the environment, never through argv and never written into .git/config. Committer identity and an SSH deploy key cost no code at all — git reads both from the inherited environment.

Your site’s own build

PREVIEW=1 on the staging branch’s deploy, and nowhere else. It is the single flag that lets drafts render, and it also flips robots.txt to Disallow: / for that deploy.

BUILD_REV from git rev-parse HEAD, stamped into the page that answers /. The review screen polls it to confirm a publish went live.

What is deliberately not an environment variable

The publish branch and the staging preview URL. Both are settings an admin sets on screen, stored with everything else. Restarting a deployment should not be how you change which branch you publish to.

Troubleshooting

The build dies with [MISSING_EXPORT] "RESEND_API_KEY". The env.schema block is missing from astro.config.mjs. This is about the schema, not the value.

A database URL in .env did nothing. Expected, and explained above. Export it in the shell instead.

Mail sends without error but never arrives. MAIL_FROM is not a verified sender on a domain you own in Resend. The provider’s rejection is in the server log rather than on screen.

A store URL that cannot be reached takes the whole CMS down. Deliberately. It throws on the first store touch, naming both variables, and does not fall back to files — a deployment running against an empty store looks exactly like a fresh install, and a fresh install re-opens first-admin sign-up to whoever loads the page first.