Skip to content

Environment variables

All environment variables required to run Classmoji

Name Required Default Description
DATABASE_URL Yes PostgreSQL connection string for the Classmoji database. We recommend Neon for production.
BETTER_AUTH_SECRET Yes Secret used to sign auth sessions and tokens. Generate with openssl rand -base64 32.
WEBAPP_URL Yes Public base URL of the main web app (e.g. http://localhost:3000).
SLIDES_URL Yes Public base URL of the slides service.
PAGES_URL Yes Public base URL of the pages service.
ACTION_ORIGIN_HOSTS Behind a TLS proxy the hosted service’s host A build argument of the webapp and slides images, not a runtime variable: the public host name the app is served from (the host in WEBAPP_URL or SLIDES_URL), exact names, comma-separated. Behind a proxy that terminates TLS, the app accepts form submissions only from these hosts. See Building an image yourself.
Name Required Default Description
GITHUB_APP_NAME No Slug of your Github App, used to build install and authorization links.
GITHUB_APP_ID No Numeric ID of your Github App.
GITHUB_CLIENT_ID No OAuth client ID for Github sign-in.
GITHUB_CLIENT_SECRET No OAuth client secret for Github sign-in.
GITHUB_WEBHOOK_SECRET No Secret used to verify incoming Github webhook signatures. Generate with openssl rand -base64 32.
GITHUB_PRIVATE_KEY_BASE64 No Base64-encoded private key for your Github App, used to authenticate API requests.

AI features (quizzes, Ask Moji) run on Anthropic Claude. They are optional. Both run as chat agents on the background jobs (Trigger.dev): without TRIGGER_SECRET_KEY (or TRIGGER_ACCESS_TOKEN) quizzes are hidden, left out of grade totals, and the quiz API routes return 503, and Ask Moji is off. Ask Moji also needs an Anthropic key: ANTHROPIC_API_KEY or the classroom’s own.

Name Required Default Description
ANTHROPIC_API_KEY No The platform’s Anthropic API key, used wherever a classroom has no key of its own. The background jobs call Claude with it (quizzes and Ask Moji). The web app lists the available models on AI settings with it, and the web app, pages, slides and MCP server check for it before offering Ask Moji.
LLM_MODEL No claude-sonnet-5-5 Claude model used for quizzes. Quizzes accept only the models listed in packages/utils/src/aiModels.ts; any other value runs the default. Read by the background jobs, which run quizzes and fall back to it for Ask Moji (see SYLLABUS_BOT_MODEL), and by the web app, which shows it as the default on AI settings: set the same value on both.
EXPLORATION_MODEL No claude-sonnet-5-5 Claude model used for code-aware quiz repository exploration. Same list and default as LLM_MODEL. Read by the background jobs, which run it, and the web app, which shows it as the default on AI settings.
QUIZ_QUESTION_EFFORT No medium Reasoning effort (low, medium, high, xhigh, max) for quiz question and conversation turns. A classroom with its own API key can override it.
QUIZ_GRADING_EFFORT No high Reasoning effort for final quiz grading. A classroom with its own API key can override it.
EXPLORATION_EFFORT No low Reasoning effort for the code-aware quiz’s excerpt selection (xhigh and max run as high). A classroom with its own API key can override it.
MCP_PUBLIC_URL Yes, in production Public origin of the MCP server (apps/mcp), for example https://mcp.example.org: an origin only, with no path. The web app issues the MCP server’s access tokens for <origin>/mcp, so every app that signs people in reads it and won’t start in production without it: the web app, admin, slides, pages, collab and the MCP server. The quiz and Ask Moji agents read the course through it: set it on the background jobs too, or Ask Moji can’t answer questions. Use the public HTTPS origin, not a private network hostname.
SYLLABUS_BOT_MODEL No LLM_MODEL, then claude-sonnet-5-5 Claude model used for Ask Moji. It accepts only the models listed in packages/utils/src/aiModels.ts (any other value runs claude-sonnet-5-5), as LLM_MODEL does. Read by the background jobs, and by the web app to show the default. A classroom with its own API key can override it with an allow-listed model.
SYLLABUS_BOT_EFFORT No low Reasoning effort for Ask Moji. A classroom with its own API key can override it.
Name Required Default Description
STRIPE_SECRET_KEY No Stripe secret API key for subscription checkout and billing.
STRIPE_WEBHOOK_SECRET No Secret used to verify incoming Stripe webhook signatures.
Name Required Default Description
EMAIL No Address used as the sender for outgoing email.
EMAIL_PASSWORD No Password (or app password) for the sending email account.
Name Required Default Description
TRIGGER_API_URL No Base URL of the Trigger.dev API. Leave unset to use Trigger.dev cloud.
TRIGGER_PUBLIC_API_URL No Public Trigger.dev API address that GitHub Actions can reach; the generated autograding workflow reports results to it. Leave unset for Trigger.dev cloud. Not the worker-internal TRIGGER_API_URL. The background jobs read it: set it in the Trigger.dev dashboard, because the deploy’s environment sync (Infisical, through syncEnvVars) skips every TRIGGER_* name.
TRIGGER_SECRET_KEY No Trigger.dev secret key used to authenticate background job runs. Also required for quizzes and Ask Moji, which run as Trigger.dev chat agents (without it quizzes are hidden everywhere and Ask Moji is off). Set it on the web app, the MCP server, and the pages and slides apps, which check it (or TRIGGER_ACCESS_TOKEN) before offering quizzes or Ask Moji.

Large files (video, audio, big PDFs) go to a Cloudflare R2 bucket rather than into a classroom’s content repo. The four MEDIA_R2_* variables are optional and all-or-nothing: with any of them unset, media upload is hidden in the UI and the media API routes return 503.

Name Required Default Description
MEDIA_R2_ACCOUNT_ID No Cloudflare account the media bucket lives in. Used to build the R2 S3 endpoint.
MEDIA_R2_ACCESS_KEY_ID No Access key of an R2 API token scoped to the media bucket (object read/write).
MEDIA_R2_SECRET_ACCESS_KEY No Secret of that R2 API token. It never leaves the server: uploads are presigned and the browser writes straight to R2.
MEDIA_R2_BUCKET No Name of the R2 bucket media objects are written to.
CLOUDINARY_CLOUD_NAME No Cloudinary cloud name for image and media uploads.
CLOUDINARY_API_KEY No Cloudinary API key for signed uploads.
CLOUDINARY_API_SECRET No Cloudinary API secret for signed uploads.
Name Required Default Description
CALENDAR_SECRET No Secret used to sign calendar feed (iCal) URLs. Generate with openssl rand -base64 32.