| 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. |