Configuration

Jant configuration comes from two places:

  • Environment variables: control infrastructure and runtime behavior
  • Settings page: site name, appearance, time zone, and other options you can adjust online

Most single-site installs only need one value: AUTH_SECRET. Everything else is on-demand.

Environment variables

Use:

  • wrangler.toml for non-sensitive Cloudflare values
  • .dev.vars for local Cloudflare secrets
  • .env or process environment variables for Node and Docker

Required

Every runtime requires this variable:

Variable Description
AUTH_SECRET Key better-auth uses to sign session cookies. At least 32 characters. Don't commit it to version control.

Don't commit AUTH_SECRET to version control.

  • Cloudflare local development: put it in .dev.vars
  • Cloudflare production: set it as a Worker secret with npx wrangler secret put AUTH_SECRET
  • Node and Docker: put it in .env or process environment variables

Public URL and subpath

In most cases, you don't need to set either of these. Jant derives the origin from the request host and mounts at the root path by default.

Variable Description
SITE_ORIGIN Fixed public origin, e.g. https://example.com. Affects absolute URLs in RSS, sitemap, exports, auth callbacks, etc.
SITE_PATH_PREFIX Public path prefix, e.g. /blog. Affects all routes and static asset paths.

Set them only when:

  • The site sits behind a reverse proxy that doesn't pass Host correctly, so the auto-derived host is wrong: set SITE_ORIGIN=https://example.com
  • Mounted under a subpath (e.g. example.com/blog): set SITE_PATH_PREFIX=/blog
  • You want to pin the domain explicitly

Node and Docker

Under Node and Docker, Jant chooses the database runtime from DATABASE_URL:

  • file: means SQLite
  • postgres: or postgresql: means Postgres

Minimal SQLite example:

AUTH_SECRET=your-32-plus-character-secret
SITE_ORIGIN=https://your-jant.example
DATABASE_URL=file:./data/jant.sqlite

Minimal Postgres example:

AUTH_SECRET=your-32-plus-character-secret
SITE_ORIGIN=https://your-jant.example
DATABASE_URL=postgres://USER:PASSWORD@HOST:5432/DBNAME

Common Node and Docker variables:

Variable Default Description
DATA_DIR ./data Base directory for default SQLite and local media paths
LOCAL_STORAGE_PATH <DATA_DIR>/media Override the local media directory
LOCAL_PUBLIC_URL unset Public base URL when media is served outside Jant; leave unset to use Jant's /media/* routes
HOST 127.0.0.1 on bare Node Bind address for jant start
PORT 3000 Bind port for jant start
TRUST_PROXY false Trust forwarded headers from the reverse proxy

The official Docker image already defaults DATA_DIR to /var/lib/jant, and Docker Compose commonly sets TRUST_PROXY=true.

Feed defaults (optional)

Variable Default Description
MAIN_RSS_FEED featured Controls what /feed returns: featured or latest

featured is the default on purpose. Jant assumes many posts should remain on the site without automatically becoming the default subscriber feed.

Pagination (optional)

Variable Default Description
PAGE_SIZE 50 Default page size for timelines and APIs
SEARCH_PAGE_SIZE inherits PAGE_SIZE Override search pagination only
ARCHIVE_PAGE_SIZE inherits PAGE_SIZE Override archive pagination only

Set SEARCH_PAGE_SIZE and ARCHIVE_PAGE_SIZE only when search or archive really needs a different page size from the rest of the site.

Storage

Storage depends on the runtime:

Runtime Default Supported drivers
Cloudflare Workers r2 r2, s3
Node and Docker local local, s3

Node does not support r2.

Cloudflare does not support local.

Switch drivers via the STORAGE_DRIVER environment variable, e.g. STORAGE_DRIVER=s3. When unset, the runtime default is used.

For Node and Docker, local is the fastest way to start; s3 is usually the better long-term production choice.

Local storage (fastest start for Node / Docker)

Local storage needs no extra driver configuration.

Use it when:

  • You want the simplest possible setup
  • Local testing
  • A small single-machine install

Defaults:

  • DATA_DIR=./data
  • LOCAL_STORAGE_PATH=<DATA_DIR>/media

Override the path when you want media files elsewhere:

LOCAL_STORAGE_PATH=/absolute/path/to/jant-media

Set LOCAL_PUBLIC_URL only when another web server will serve those files directly.

R2 (default)

Cloudflare Workers use R2 by default.

Variable Description
R2_PUBLIC_URL Public URL that serves media directly

R2 itself is configured through the [[r2_buckets]] binding in wrangler.toml.

Setting R2_PUBLIC_URL is strongly recommended. It still works without it, but Jant has to proxy every media request through the Worker.

[vars]
R2_PUBLIC_URL = "https://media.yourdomain.com"

S3-compatible storage

Use S3-compatible storage when:

  • You want the recommended long-term storage option on Node or Docker
  • You want the same storage backend on Cloudflare and Node
  • You prefer S3, Backblaze B2, MinIO, DigitalOcean Spaces, or another compatible service
  • You need browser direct uploads with presigned URLs
Variable Description
STORAGE_DRIVER Set to s3
S3_ENDPOINT S3 API endpoint
S3_BUCKET Bucket name
S3_REGION Bucket region, defaults to auto
S3_PUBLIC_URL Public URL where uploaded files are served
S3_ACCESS_KEY_ID Access key, keep secret
S3_SECRET_ACCESS_KEY Secret key, keep secret

Example:

[vars]
STORAGE_DRIVER = "s3"
S3_ENDPOINT = "https://s3.us-east-1.amazonaws.com"
S3_BUCKET = "my-bucket"
S3_REGION = "us-east-1"
S3_PUBLIC_URL = "https://cdn.example.com"

Put these credentials in secrets storage. Don't commit them to version control.

CORS for browser direct uploads

If you use STORAGE_DRIVER=s3, the bucket must enable CORS for the actual upload origin.

Recommended CORS policy:

[
  {
    "AllowedOrigins": ["https://your-site.example"],
    "AllowedMethods": ["GET", "HEAD", "PUT"],
    "AllowedHeaders": [
      "Content-Type",
      "Content-Disposition",
      "Cache-Control",
      "x-amz-checksum-sha256"
    ],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]

If you upload from multiple origins, list each origin explicitly.

Image transformations (optional)

Variable Description
IMAGE_TRANSFORM_URL Base URL for the image transformation service

When using Cloudflare image transformations, point this at the domain that actually serves the images, plus /cdn-cgi/image.

Example:

[vars]
R2_PUBLIC_URL = "https://media.yourdomain.com"
IMAGE_TRANSFORM_URL = "https://media.yourdomain.com/cdn-cgi/image"

Or, when images are still proxied through the site domain:

[vars]
IMAGE_TRANSFORM_URL = "https://yourdomain.com/cdn-cgi/image"

Static asset CDN (optional)

Variable Description
ASSET_BASE_URL Absolute URL serving built JS/CSS assets (e.g. a separate CDN)

By default Jant serves bundled assets from the same origin as the site under /_assets/. Set ASSET_BASE_URL only when you want those assets to live on a different domain.

[vars]
ASSET_BASE_URL = "https://cdn.yourdomain.com"

The CDN must allow cross-origin requests. Jant ships its client bundle as ES modules (<script type="module">), and browsers enforce CORS on cross-origin module scripts — even though they look like ordinary JS. If the CDN doesn't return Access-Control-Allow-Origin, the browser drops the response and the site won't boot.

Two ways to configure the asset host — pick whichever fits your setup:

Option A — allow any origin (simplest):

Access-Control-Allow-Origin: *

Bundle files are content-hashed and publicly cacheable, so * is safe. The CDN can cache a single response and serve it to every visitor.

Option B — restrict to your site origin:

Access-Control-Allow-Origin: https://yourdomain.com
Vary: Origin

Use this when the same CDN serves several sites and you want each one isolated. Vary: Origin is required so the CDN doesn't return the wrong allow-origin header to a different caller. If your CDN doesn't support varying on Origin, prefer Option A.

Same-origin deployments (no ASSET_BASE_URL set) don't need any CORS configuration.

Cloudflare R2 / S3 (JSON CORS rules)

If the CDN is a bucket exposed directly to the public (R2 with a public bucket, or S3 + CloudFront), set the bucket's CORS rules to:

[
  {
    "AllowedOrigins": ["*"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 86400
  }
]

Or, to restrict to your site origin:

[
  {
    "AllowedOrigins": ["https://yourdomain.com"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 86400
  }
]
  • R2: Dashboard → your bucket → Settings → CORS policy → paste JSON
  • S3: AWS Console → bucket → Permissions → Cross-origin resource sharing (CORS), or aws s3api put-bucket-cors

Caddy / nginx (reverse-proxied CDN)

# Caddy
header /_assets/* Access-Control-Allow-Origin "*"
# nginx
location /_assets/ {
    add_header Access-Control-Allow-Origin "*" always;
}

Slug (optional)

Variable Default Description
SLUG_ID_LENGTH 5 Length of the random slug auto-generated for untitled posts

Upload size limits (optional)

Variable Default Description
UPLOAD_MAX_FILE_SIZE_MB 500 Maximum size for non-image uploads (MB)

Images have their own tighter limits. This setting mainly affects video, audio, and PDF uploads.

Content summaries and RSS limits (optional)

Variable Default Description
SUMMARY_MAX_PARAGRAPHS 5 Maximum paragraphs in auto-generated summaries
SUMMARY_MAX_CHARS 500 Maximum characters in auto-generated summaries
RSS_FEED_LIMIT 50 Maximum number of posts included in RSS feeds

Settings page options

These settings can be changed on Jant's Settings page after setup. Each one can also be seeded from an environment variable of the same name — values changed in Settings take precedence over the environment variable.

Setting What it does
SITE_NAME Site display name
SITE_DESCRIPTION Meta description and feed description
SITE_LANGUAGE Primary language code
TIME_ZONE Display time zone, e.g. UTC or Asia/Shanghai
HOME_DEFAULT_VIEW Whether the home page starts on Latest or Featured
MAIN_RSS_FEED What /feed returns
SITE_FOOTER Custom footer text
SHOW_JANT_BRANDING_ON_HOME Show or hide Jant branding on the home page
NOINDEX Ask search engines not to index the site

Color theme, font theme, custom CSS, avatar, and other appearance details are also managed in Settings.

Reserved paths

These top-level paths are reserved and can't be used as a post or custom page slug:

featured, latest, collections, signin, signout, setup, settings, dash,
api, feed, search, archive, media, pages, reset, compose, new, static, assets,
_assets, healthz, readyz

Config files

wrangler.toml

Put non-sensitive Cloudflare configuration in wrangler.toml:

name = "my-jant-site"
main = "index.js"

[vars]
SITE_ORIGIN = "https://myblog.com"
# SITE_PATH_PREFIX = "/blog"
# R2_PUBLIC_URL = "https://media.myblog.com"
# IMAGE_TRANSFORM_URL = "https://media.myblog.com/cdn-cgi/image"

[[d1_databases]]
binding = "DB"
database_name = "my-jant-site-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

[[r2_buckets]]
binding = "R2"
bucket_name = "my-jant-site-media"

.env (Node and Docker)

For Node and Docker, put these values in .env, or have your process manager inject them:

AUTH_SECRET=your-32-plus-character-secret
SITE_ORIGIN=https://your-jant.example
DATABASE_URL=file:./data/jant.sqlite
# SITE_PATH_PREFIX=/blog
# TRUST_PROXY=true

Useful templates:

.dev.vars (local development)

Put local Cloudflare secrets in .dev.vars:

AUTH_SECRET=your-32-plus-character-secret
DEV_API_TOKEN=local-debug-token
[email protected]
DEMO_PASSWORD=jant-dev-debug-login
DEMO_MODE=false

DEV_API_TOKEN, DEMO_EMAIL, and DEMO_PASSWORD are local debugging helpers — they aren't part of a normal production setup.

Demo Mode

Set DEMO_MODE=true only for a publicly shared demo environment.

Effects:

  • Forces noindex on
  • Disables account deletion, password changes, and some account-management actions
  • Setting DEMO_EMAIL or DEMO_PASSWORD alone does not turn on demo mode

Production secrets

For Cloudflare production, set secrets through Wrangler or the dashboard:

openssl rand -base64 32
npx wrangler secret put AUTH_SECRET

What's next