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.tomlfor non-sensitive Cloudflare values.dev.varsfor local Cloudflare secrets.envor 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
.envor 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): setSITE_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 SQLitepostgres:orpostgresql: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=./dataLOCAL_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:
- Repo-root Docker / Node example:
.env.example - Package-internal Node example:
packages/core/.env.node.example
.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
noindexon - Disables account deletion, password changes, and some account-management actions
- Setting
DEMO_EMAILorDEMO_PASSWORDalone 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
- Writing and organizing — start using Jant
- Theming — adjust the appearance
- Backups and recovery — get ready to run long-term