Configuration: crisptastic.toml
Every crisptastic.toml setting that has an effect, and the ones that do not.
crisptastic.toml sits in the site folder. It is optional for development
(dev, run, check) and required to deploy.
Read this first
- Unknown keys are refused. A typo at the top level, or a section that
only exists in older documents, stops
check,dev,runand the host withconfig.unknown_keynaming the key. The sections that are read refuse unknown keys inside them too. The keys that specifications mention but no code reads are listed under Keys that do nothing. - The file is found in the current directory only.
crisptastic dev ./my-siterun from a parent folder does not read./my-site/crisptastic.toml. Eithercd my-sitefirst, or pass--config ./my-site/crisptastic.toml. (With--config, a positional site path is resolved relative to the config file's folder.) - Relative paths inside the file (
assets,sessions,root,sass_load_paths, component paths) are resolved relative to the folder that contains the file. - The file is read once at startup.
devdoes not watch it; restart after editing. - Command-line flags override the file, which overrides the defaults. (The
exception is
[fetch], whereCRISPTASTIC_FETCH_*environment variables override the file.)
Top-level keys
root = "." # site root; default: current directory
assets = "./assets" # static folder; default: ./assets if it exists
sass_load_paths = ["./vendor/sass"] # extra Sass search folders
bind = "127.0.0.1:6174" # listen address for dev/run
watch = true # watch for changes (dev always watches)
sessions = "./.crisptastic/sessions" # session folder
bind, watch and sessions' default location apply to dev and run. On
a deployed host the public address is the host's --bind flag, and watch has
no effect.
[deployment]: which domains a site serves
[deployment]
domains = ["example.com", "www.example.com"]
This is the only setting that decides which host names a deployed site
answers for. It is read by crisptastic deploy. Each domain is lower-cased and
trimmed of a trailing dot; schemes, ports, paths, wildcards and whitespace are
rejected. A site with no domains builds but cannot be activated.
There is no [deploy] section and no authorities key; older documents that
show them are wrong.
[env] and [env_from_process]
[env]
SITE_NAME = "My Site" # a literal value
[env_from_process]
STRIPE_KEY = "STRIPE_SECRET_KEY" # NAME_IN_SITE = "NAME_IN_YOUR_SHELL"
[env]holds literal strings.[env_from_process]reads a variable from the environment of the process that runs the command, under the name on the left. A variable that is missing is an error. Names use letters, digits and_.- Crisptastic reads only the names you declare. It never copies your whole environment.
--env NAMEor--env NAME=VALUEondevandrunadds more.
What these become depends on where the site runs:
| Where | Result |
|---|---|
dev, run, serve | Plain values, readable in pages as context.env.NAME. |
| A deployed host | Secure variables: sealed inside the encrypted Capsule, held only in locked memory, and not visible to pages. context.env is empty. Only server-side capabilities ([llm], [mail], [arbax]) can use them. |
So context.env.SITE_NAME works on your laptop and renders nothing in
production. For public settings that pages must read in production, put them in
the page source, a partial or an API response instead.
Deploy-time rules for secure variables: names must be uppercase
([A-Z][A-Z0-9_]*, up to 128 characters), each value at most 64 KiB, at most
256 variables. A lowercase name that works in dev fails deploy.
On macOS the macrun helper keeps values in the Keychain and exposes them as
environment variables to the one command you run:
macrun set my-site production STRIPE_SECRET_KEY
macrun run my-site production -- crisptastic deploy . --recipient ... --signing-key ... --out ...
Crisptastic itself does not know about macrun; any tool that sets
environment variables for a single command works, including CI systems.
[fetch]
Defaults applied to every outbound @fetch. All keys are
optional.
[fetch]
api_key = "id.secret" # adds Authorization: ApiKey id.secret
scope = "tenant-42" # adds x-scope
cache_ttl_ms = 60000 # in-memory cache for GETs (0 = off)
max_concurrent_requests = 8 # 0 = unlimited
headers = { x-client = "my-site" }
These apply to every destination. See the warning in Fetching and shaping data.
[security] and [[cors.routes]]
Extra response headers. These sections are strict: unknown keys are errors.
[security]
frame = "same-origin" # "deny" (default) or "same-origin"
[security.csp] # present => sends Content-Security-Policy
default_src = ["'self'"]
img_src = ["'self'", "data:", "https://cdn.example.com"]
object_src = ["'none'"]
upgrade_insecure_requests = true
[[cors.routes]]
route = "/api/data.json" # must equal a compiled route pattern
allowed_origins = ["https://app.example.com"] # or exactly ["*"]
allowed_methods = ["GET", "POST"] # GET, HEAD, POST only
allowed_headers = ["Content-Type"]
exposed_headers = []
allow_credentials = false
max_age_seconds = 600 # at most 86400
- Every response always carries
X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,X-Frame-Options(DENYunlessframe = "same-origin") and a restrictivePermissions-Policy. - A CSP table must set at least one directive. Available directives:
default_src script_src style_src img_src font_src connect_src media_src object_src frame_src frame_ancestors base_uri form_action worker_src manifest_src<code>, plus the boolean</code>upgrade_insecure_requests<code>. Plain</code>http://sources are rejected. - This section is always read from
crisptastic.tomlin the site folder, even when--configpoints elsewhere. devinjects its live-reload client into every HTML page. So that a strict policy (script_src = ["'self'"],connect_src = ["'none'"]) does not silently block it,devalone adds a'nonce-…'for that one script toscript-srcand'self'toconnect-src(deriving either directive fromdefault-srcwhen it is absent).run,serveand a host send the policy exactly as written.
[routes]
A route may declare what it accepts. A route with no declaration accepts what it always has. The name must be a compiled route, or the site does not load.
[routes."/f/subscribe"]
methods = ["POST"] # required; GET implies HEAD
body = "form" # "form" or "json"
max_body_bytes = 4096
capabilities = ["iris"] # what the page may @call
rate = [
{ per = "client", limit = 10, window = "1m" },
{ per = "client", limit = 100, window = "1d" },
{ per = "body.email", limit = 3, window = "1d" },
]
The host answers a request that breaks a declaration before the page runs, so no capability is called.
rate limits are answered with 429 and Retry-After:
per = "client"counts the client address. Behind a proxy that is the hop--trusted-hopsselects, and it is never read from the application. An IPv6 client counts as its /64.per = "body.<field>"counts a form or JSON field, lower-cased and trimmed, whoever sends it. A request without the field counts nothing, and a repeated field counts each value.windowis a number ands,m,hord, at most30d. A window opens at the first request counted for a key.- Up to 8 limits per route. Every limit is checked before any is counted, so a request one limit refuses spends nothing of another.
GETandHEADare never counted, so a limit on a route that accepts only those is refused when the site loads.- Counters live in the host's memory, shared by a site's generations: a redeploy forgives nobody and a restart forgives everyone. Each host counts for itself.
- Pages cannot read or change them.
[telemetry]
Request logging for development. It applies to dev, run and serve only;
a deployed host does not emit telemetry.
[telemetry]
endpoint = "stdout://" # stdout://, file://PATH, http://..., tcp://...
filter = "crisptastic*"
app = "crisptastic"
flush_ms = 250
trace_http = false
Other keys: token, token_env, where, instance. For quick use, run
crisptastic dev --debug instead (see CLI reference).
Integration sections
These are documented, with examples, in Capabilities and integrations:
| Section | Purpose |
|---|---|
[auth.*] | Auth0 sign-in helper endpoints |
[payments.*] | Stripe Checkout helper endpoints |
[storage.*] | S3 presigned uploads and Cloudflare Images |
[llm], [mail], [arbax] | Experimental Wasm capabilities |
[data] | Embedded Residiuum store: content and record collections; dev, run and a single host (see Integrations) |
[asset_publication] | Deploy-time publishing of assets to S3 or Cloudflare |
Keys that do nothing
These appear in specifications, older documentation, example folders or
templates, but no code reads them. Naming one at the top level of
crisptastic.toml is a config.unknown_key error rather than a silent no-op:
schema, name, app_id, [runtime] (including minimum_version and
language_profile), [limits] (render limits are fixed; see
Pages and templating), [deploy]
(including authorities and health_route), [session], [secrets],
[capabilities.*], and crisptastic.local.toml.
Practical consequence: you cannot require authentication in the configuration
file. Do that in the page, by checking context.session.
Validation behaviour
- A value of the wrong type in a section that is read (for example
watch = "yes") stops startup withfailed to parse run config. - The top level rejects unknown keys with
config.unknown_key, naming them. So do[deployment],[fetch],[telemetry],[auth],[payments],[storage],[data],[llm],[mail],[arbax],[asset_publication],[security]and[cors]. crisptastic checkapplies the same validation to thecrisptastic.tomlin the site folder it is given, so a misspelt table is reported before the site is run.- A
*_envsetting that names an environment variable that is not set stops startup with a message naming the variable.