Environment Variables
Contentrain Studio is configured entirely through environment variables. Copy .env.example to .env and fill in the values for your deployment.
Core Application
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_SESSION_SECRET | Yes | -- | AES-256 cookie encryption key. Minimum 32 characters. |
NUXT_SESSION_SECRET_PREVIOUS | No | -- | Previous secret for key rotation. Set during migration window, remove after. |
NUXT_PUBLIC_SITE_URL | Yes | http://localhost:3000 | Public-facing application URL. Used for OAuth callbacks and email links. |
NUXT_PUBLIC_GITHUB_APP_SLUG | No | contentrain-studio | GitHub App slug for installation redirect URL. |
NUXT_PUBLIC_TEMPLATE_OWNER | No | Contentrain | GitHub org that owns starter kit template repos. |
NUXT_PUBLIC_BILLING_ENABLED | No | false | Billing UI visibility. Auto-derived on boot from configured payment plugins (true when Polar or Stripe is set). Set explicitly only to override. |
NODE_ENV | No | development | Set to production for production deployments. |
Session Secret
Generate a secure session secret:
openssl rand -base64 32Deployment Profile
Studio auto-detects its deployment profile at boot from whether the Enterprise bridge (ee/) loads and whether a payment plugin is configured. Set these only to pin behaviour explicitly.
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_DEPLOYMENT_PROFILE | No | -- (auto-detect) | managed | dedicated | on-premise | community. Server-side profile override. |
NUXT_PUBLIC_DEPLOYMENT_PROFILE | No | -- | Client-visible profile. managed | dedicated | on-premise | community. |
NUXT_PUBLIC_DEPLOYMENT_EDITION | No | -- | Client-visible edition. ee | agpl. |
NUXT_PUBLIC_DEPLOYMENT_BILLING_MODE | No | -- | Client-visible billing mode. polar | stripe | flat | off. |
Production Builds
Production (Nitro compiled output) freezes runtimeConfig.public, so the auto-derive plugin cannot mutate the snapshot. Set the three NUXT_PUBLIC_DEPLOYMENT_* variables explicitly in production to mirror the server-side resolveDeployment() result on the client. Dev mode does not need them.
Provider Selection
Studio's auth and database providers ship as matched pairs, selected at boot. Two pairs are supported: supabase + supabase (the default) and managed + postgres (Supabase-free, plain PostgreSQL 14+). Mixed pairs (e.g. supabase auth with postgres database) are rejected at boot.
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_AUTH_PROVIDER | No | supabase | Auth implementation: supabase | managed. |
NUXT_DATABASE_PROVIDER | No | supabase | Database implementation: supabase | postgres. |
Valid pairs
Only supabase+supabase and managed+postgres are accepted. Setting one without the other, or crossing them, fails boot validation with an explicit error.
Supabase (Auth + Database)
Required only for the Supabase pair (NUXT_DATABASE_PROVIDER=supabase, the default). Leave these unset when running the managed + postgres pair.
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_SUPABASE_URL | Required (Supabase pair) | -- | Supabase project URL (e.g., https://your-project.supabase.co). |
NUXT_SUPABASE_SERVICE_ROLE_KEY | Required (Supabase pair) | -- | Service role key with admin database access. Keep private. Must carry the service_role JWT claim -- boot rejects an anon key pasted here. |
NUXT_SUPABASE_ANON_KEY | Required (Supabase pair) | -- | Anonymous key for client-side auth operations. |
Supabase OAuth (for local Supabase CLI)
These are only needed when running Supabase locally via the CLI:
| Variable | Required | Default | Description |
|---|---|---|---|
SUPABASE_AUTH_EXTERNAL_GITHUB_CLIENT_ID | No | -- | GitHub OAuth client ID for Supabase auth. |
SUPABASE_AUTH_EXTERNAL_GITHUB_SECRET | No | -- | GitHub OAuth client secret for Supabase auth. |
SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID | No | -- | Google OAuth client ID for Supabase auth. |
SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET | No | -- | Google OAuth client secret for Supabase auth. |
Managed + Postgres Pair
Required only when running the Supabase-free pair (NUXT_AUTH_PROVIDER=managed, NUXT_DATABASE_PROVIDER=postgres). The managed auth provider signs its own JWTs, runs the GitHub/Google OAuth dance directly, and sends magic-link/invite emails through Resend. Leave all of these unset on the Supabase pair.
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_POSTGRES_URL | Required (managed pair) | -- | PostgreSQL 14+ connection string (e.g., postgres://user:pass@host:5432/contentrain). Required when NUXT_DATABASE_PROVIDER=postgres. |
NUXT_AUTH_JWT_SECRET | Required (managed pair) | -- | Minimum 32 characters. Signs the managed provider's access/refresh JWTs. Required when NUXT_AUTH_PROVIDER=managed. |
NUXT_SESSION_PASSWORD | Recommended (managed pair) | -- | Minimum 32 characters. Session store for the nuxt-auth-utils OAuth module (separate from NUXT_SESSION_SECRET; stores no durable state). Dev auto-generates it; deployed builds must set it or every SSR load of /api/_auth/session 500s (boot warns, does not fail). |
NUXT_OAUTH_GITHUB_CLIENT_ID | Required (managed pair) | -- | GitHub OAuth App client ID for login. Callback: {NUXT_PUBLIC_SITE_URL}/api/auth/oauth/github. |
NUXT_OAUTH_GITHUB_CLIENT_SECRET | Required (managed pair) | -- | GitHub OAuth App client secret. |
NUXT_OAUTH_GOOGLE_CLIENT_ID | No | -- | Google OAuth client ID for login. Google sign-in is disabled (button hidden) when unset. |
NUXT_OAUTH_GOOGLE_CLIENT_SECRET | No | -- | Google OAuth client secret. |
NUXT_REVIEW_ACCOUNT_EMAIL | No | -- | Directory-review password-login account (managed pair only). The only address the review login accepts. Must be set together with the password. |
NUXT_REVIEW_ACCOUNT_PASSWORD | No | -- | Password for the review-login account. Minimum 16 characters (it gates a real password login). Unset means /auth/review-login does not exist. |
NUXT_OPENAI_APPS_CHALLENGE | No | -- | Domain-verification token served at /.well-known/openai-apps-challenge (managed pair, remote MCP surface). |
Resend is required on the managed pair
NUXT_RESEND_API_KEY (see Email) is required when NUXT_AUTH_PROVIDER=managed -- it delivers magic-link and invite emails. On the Supabase pair it stays optional.
GitHub App
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_GITHUB_APP_ID | Yes | -- | GitHub App ID (numeric). |
NUXT_GITHUB_CLIENT_ID | Yes | -- | GitHub App client ID. |
NUXT_GITHUB_CLIENT_SECRET | Yes | -- | GitHub App client secret. |
NUXT_GITHUB_PRIVATE_KEY | Yes | -- | Base64-encoded .pem private key file. |
NUXT_GITHUB_WEBHOOK_SECRET | Recommended | -- | HMAC-SHA256 secret for webhook payload verification. Validated only when NUXT_GITHUB_APP_ID is set; if missing, boot warns and webhook signature verification is disabled (it does not fail boot). |
Encoding the Private Key
base64 -i your-app.private-key.pem | tr -d '\n'AI (Anthropic)
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_ANTHROPIC_API_KEY | No | -- | Operator-managed Anthropic API key for the Studio instance. If omitted, users need BYOA (Bring Your Own API key). |
CDN / Object Storage (Cloudflare R2)
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_CDN_R2_ACCOUNT_ID | No | -- | Cloudflare account ID. Required for CDN and media features. |
NUXT_CDN_R2_ACCESS_KEY_ID | No | -- | R2 access key ID. |
NUXT_CDN_R2_SECRET_ACCESS_KEY | No | -- | R2 secret access key. |
NUXT_CDN_R2_BUCKET | No | contentrain-cdn | R2 bucket name. |
INFO
CDN and media library features require all four R2 variables to be set. If any are missing, useCDNProvider() and useMediaProvider() return null and those features are gracefully disabled.
Email (Resend)
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_RESEND_API_KEY | No | -- | Resend API key for application emails (invites, notifications). |
RESEND_API_KEY | No | -- | Same key, read by local Supabase CLI for auth SMTP. |
NUXT_EMAIL_SENDER_ADDRESS | No | -- | Sender email address (e.g., [email protected]). |
NUXT_EMAIL_SENDER_NAME | No | -- | Sender display name (e.g., Contentrain Studio). |
Billing
Studio supports two payment plugins. Polar is the default provider; Stripe is supported as a secondary, legacy plugin. When both are configured, Polar takes precedence (the plugin registry preference order is polar -> stripe).
Polar (default)
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_POLAR_ACCESS_TOKEN | No | -- | Polar organization access token (polar_oat_...). Activates the Polar plugin when set. |
NUXT_POLAR_WEBHOOK_SECRET | No | -- | Polar webhook signing secret (Standard Webhooks). |
NUXT_POLAR_STARTER_PRODUCT_ID | No | -- | Polar product ID for the Starter plan. |
NUXT_POLAR_PRO_PRODUCT_ID | No | -- | Polar product ID for the Pro plan. |
NUXT_POLAR_SERVER | No | production | Polar environment: sandbox for testing, production for live. |
Stripe (legacy / optional)
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_STRIPE_SECRET_KEY | No | -- | Stripe secret key. Activates the Stripe plugin when set. |
NUXT_STRIPE_WEBHOOK_SECRET | No | -- | Stripe webhook signing secret for event verification. |
NUXT_STRIPE_STARTER_PRICE_ID | No | -- | Stripe Price ID for the Starter plan. |
NUXT_STRIPE_PRO_PRICE_ID | No | -- | Stripe Price ID for the Pro plan. |
Self-Hosted Bypass
When no payment plugin (Polar or Stripe) is configured, Studio resolves to the on-premise profile (ee/ loaded) or community profile (ee/ missing) and hides the subscription UI. Core features work normally; premium features (CDN preview branches, custom variants, spam filter) remain gated by hasFeature().
Redis
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_URL | No | -- | Redis connection URL (e.g., redis://localhost:6379). Strongly recommended for multi-instance production rate limiting; dev falls back to in-memory. |
REDIS_CA_CERT | No | -- | Custom CA certificate for TLS Redis connections (rediss://). |
Sentry (Error & Performance Monitoring)
Optional. Leave NUXT_PUBLIC_SENTRY_DSN empty to disable Sentry entirely (the SDK no-ops on both client and server, and no telemetry leaves your infrastructure) -- this is the intended Community / self-host default.
| Variable | Required | Default | Description |
|---|---|---|---|
NUXT_PUBLIC_SENTRY_DSN | No | -- | Your Sentry project DSN. Empty disables Sentry completely. |
NUXT_PUBLIC_SENTRY_ENVIRONMENT | No | -- | Environment tag: development | staging | production. |
NUXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE | No | 0.1 | Performance-tracing sample rate. Errors are always captured at 100%. |
NUXT_PUBLIC_SENTRY_RELEASE | No | -- | Optional build/release identifier. |
These three are build-time only, for source map upload during nuxt build. When all three are set, readable stack traces are uploaded. SENTRY_AUTH_TOKEN is a secret -- never expose it to forked CI or the client bundle.
| Variable | Required | Default | Description |
|---|---|---|---|
SENTRY_ORG | No | -- | Sentry org slug (source map upload). |
SENTRY_PROJECT | No | -- | Sentry project slug (source map upload). |
SENTRY_AUTH_TOKEN | No | -- | Sentry auth token. Enables source map upload when present. |
Variable Resolution
Nuxt resolves runtime config variables using the NUXT_ prefix convention:
// nuxt.config.ts
runtimeConfig: {
sessionSecret: '', // -> NUXT_SESSION_SECRET
github: {
appId: '', // -> NUXT_GITHUB_APP_ID
clientId: '', // -> NUXT_GITHUB_CLIENT_ID
privateKey: '', // -> NUXT_GITHUB_PRIVATE_KEY
},
polar: {
accessToken: '', // -> NUXT_POLAR_ACCESS_TOKEN
server: 'production', // -> NUXT_POLAR_SERVER
},
public: {
siteUrl: 'http://localhost:3000', // -> NUXT_PUBLIC_SITE_URL
},
}Access in server code:
const config = useRuntimeConfig()
console.log(config.github.appId) // Server-only
console.log(config.public.siteUrl) // Both server and clientExample .env File
# Core
NUXT_SESSION_SECRET=your-random-32-character-string
NUXT_PUBLIC_SITE_URL=https://studio.yourdomain.com
# Provider pair — Supabase (default). Omit NUXT_AUTH_PROVIDER /
# NUXT_DATABASE_PROVIDER to use it.
NUXT_SUPABASE_URL=https://your-project.supabase.co
NUXT_SUPABASE_SERVICE_ROLE_KEY=eyJ...
NUXT_SUPABASE_ANON_KEY=eyJ...
# Provider pair — managed + postgres (Supabase-free). Use INSTEAD of the
# Supabase block above:
# NUXT_AUTH_PROVIDER=managed
# NUXT_DATABASE_PROVIDER=postgres
# NUXT_POSTGRES_URL=postgres://user:pass@host:5432/contentrain
# NUXT_AUTH_JWT_SECRET=your-random-32-character-string
# NUXT_SESSION_PASSWORD=your-random-32-character-string
# NUXT_OAUTH_GITHUB_CLIENT_ID=your-github-oauth-app-client-id
# NUXT_OAUTH_GITHUB_CLIENT_SECRET=your-github-oauth-app-client-secret
# NUXT_RESEND_API_KEY=re_abc123 # required on the managed pair
# GitHub App
NUXT_GITHUB_APP_ID=123456
NUXT_GITHUB_CLIENT_ID=Iv1.abc123
NUXT_GITHUB_CLIENT_SECRET=abc123def456
NUXT_GITHUB_PRIVATE_KEY=LS0tLS1CRUdJTi...
NUXT_GITHUB_WEBHOOK_SECRET=whsec_abc123
# AI (optional)
NUXT_ANTHROPIC_API_KEY=sk-ant-...
# Email (optional)
NUXT_RESEND_API_KEY=re_abc123
NUXT_EMAIL_SENDER_ADDRESS=[email protected]
NUXT_EMAIL_SENDER_NAME=Contentrain Studio
# Billing — Polar (default; optional)
NUXT_POLAR_ACCESS_TOKEN=polar_oat_...
NUXT_POLAR_WEBHOOK_SECRET=your-polar-webhook-secret
NUXT_POLAR_STARTER_PRODUCT_ID=uuid-of-starter-product
NUXT_POLAR_PRO_PRODUCT_ID=uuid-of-pro-product
NUXT_POLAR_SERVER=productionRelated Pages
- Self-Hosting Guide -- deployment walkthrough
- Architecture -- how providers use these variables