Set up Convex
Convex runs XLR8’s database and backend functions, and serves the HTTP endpoints for agents, GitHub and the health check. On this page you create the project, configure it and deploy the backend for the first time.
You need the Frontend API URL and your user id from Clerk.
Create the project and deploy key
Section titled “Create the project and deploy key”-
Create a project in the Convex dashboard. Its production deployment has a name like
happy-otter-123. The app talks tohttps://happy-otter-123.convex.cloud; the HTTP endpoints live athttps://happy-otter-123.convex.site. -
Put the deployment name into your instance settings. In
deploy/instance.json(see the instance settings file), setconvexDeploymenttohappy-otter-123. -
Create a production deploy key in the project settings under Deploy keys, and store it in
~/.config/xlr8/convex-deploy-key, readable only by you:Terminal window mkdir -p ~/.config/xlr8 && (umask 077; cat > ~/.config/xlr8/convex-deploy-key)Paste the key, press Enter, then Ctrl+D.
tooling/cloud.mjs(below) refuses a key file that other users can read, and a key that belongs to a different deployment thanconvexDeployment. To keep the key somewhere else, point the environment variableXLR8_CONVEX_DEPLOY_KEY_FILEat the file.
From now on, node tooling/cloud.mjs <command> runs any Convex CLI command against your production
deployment with this key, for example node tooling/cloud.mjs env list. It never prints the key.
Set the required environment variables
Section titled “Set the required environment variables”Set these before the first deploy. The backend’s sign-in configuration reads
CLERK_JWT_ISSUER_DOMAIN and XLR8_DEV_JWKS, and the deploy fails if either is missing.
node tooling/cloud.mjs env set CLERK_JWT_ISSUER_DOMAIN https://clerk.xlr8.example.comnode tooling/cloud.mjs env set XLR8_DEV_JWKS nonenode tooling/cloud.mjs env set XLR8_OWNER_IDS 'https://clerk.xlr8.example.com|user_2abc…'You can also set them in the Convex dashboard under Settings → Environment Variables.
XLR8_OWNER_IDS lists who may use the instance. Each entry is the token issuer (your Frontend API URL),
a |, and a Clerk user id. The issuer must match exactly what Clerk puts into its tokens: the same
spelling as CLERK_JWT_ISSUER_DOMAIN, without a trailing slash. Quote the value in the shell because of
the |.
Deploy the backend
Section titled “Deploy the backend”Run pnpm install in the repository if you have not yet, then:
node tooling/cloud.mjs deployWithout the wrapper script, the plain Convex CLI works too:
cd backendread -rs CONVEX_DEPLOY_KEY && export CONVEX_DEPLOY_KEY # paste the deploy key, Enternpx convex deployCheck the health
Section titled “Check the health”Open https://happy-otter-123.convex.site/health. It returns the backend’s status as JSON, without any
of your data:
| Field | Expected in production | true means |
|---|---|---|
ok |
true |
the backend answers |
clerk |
true |
CLERK_JWT_ISSUER_DOMAIN is set |
devAuth |
false |
the local test sign-in is active |
devAuthConfigured |
false |
XLR8_DEV_JWKS holds a key instead of none; set it to none |
local |
false |
the backend runs on your own machine |
oauth |
true if you use connectors |
XLR8_APP_URL is set |
push |
true if you use push |
both VAPID keys are set |
testSwitches |
false |
test switches for local development are on |
Check /health again after every production deploy. The local test sign-in cannot work in the cloud
anyway, because the backend rejects its tokens on any deployment that is not on 127.0.0.1 or
localhost.
Environment variables
Section titled “Environment variables”All variables the backend reads. Set them with node tooling/cloud.mjs env set NAME value or in the
Convex dashboard. Changes take effect right away, except for CLERK_JWT_ISSUER_DOMAIN and
XLR8_DEV_JWKS: Convex reads these two only when the functions are pushed, so redeploy the backend after
changing them.
Sign-in and access
Section titled “Sign-in and access”| Variable | Required | Value |
|---|---|---|
CLERK_JWT_ISSUER_DOMAIN |
yes | Clerk Frontend API URL, for example https://clerk.xlr8.example.com, no trailing slash. Several URLs separated by commas, only while you move Clerk to another domain. Redeploy the backend after changing it: Convex reads it when the functions are pushed, not on every request. |
XLR8_OWNER_IDS |
yes | who gets in: comma-separated entries <issuer>|<Clerk user id>. Empty means nobody gets in. |
XLR8_DEV_JWKS |
yes | none in production. Locally it holds the public key of the test sign-in. |
Agents and connectors
Section titled “Agents and connectors”| Variable | Required | Value |
|---|---|---|
XLR8_APP_URL |
no | address of your app, https://xlr8.example.com. Turns on OAuth, so claude.ai and ChatGPT can connect as connectors. Also the default contact for push services. |
XLR8_PUBLIC_SITE_URL |
no | a custom address for MCP and OAuth instead of …convex.site, for example https://mcp.xlr8.example.com. Changing it later means reconnecting all connectors. |
XLR8_OWNER_NAME |
no | your display name in what agents read, for example who wrote a comment. Default Owner. |
Details: Connect an agent.
Language
Section titled “Language”| Variable | Required | Value |
|---|---|---|
XLR8_LOCALE |
no | de for German push notification texts and German default status names in new teams. Default English. The UI language is separate: it follows the browser and can be changed in the settings. |
Push notifications
Section titled “Push notifications”| Variable | Required | Value |
|---|---|---|
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY |
no | key pair for Web Push. Without them, no push notifications. Generate and set them with node tooling/vapid.mjs; never by hand. |
VAPID_SUBJECT |
no | contact for push services (https: or mailto:). Set by tooling/vapid.mjs; if unset, XLR8_APP_URL is used. |
Details: Notifications.
GitHub
Section titled “GitHub”| Variable | Required | Value |
|---|---|---|
GITHUB_WEBHOOK_SECRET |
no | verifies that webhook deliveries come from GitHub |
GITHUB_WEBHOOK_SECRET_PREVIOUS |
no | the old secret, only while you rotate it |
GITHUB_TOKEN |
no | fine-grained personal access token for the pull request view (description, checks, comments, merging) and for managing webhooks. Without it, XLR8 shows only title and state. |
GITHUB_MANAGE_WEBHOOKS |
no | 1: XLR8 creates missing webhooks in the token’s repositories and fixes their events. Needs GITHUB_WEBHOOK_SECRET. |
GITHUB_REPOS |
no | manage only these repositories, owner/name or owner/*, comma-separated. Default: all repositories owned by the token’s account. |
Details: GitHub.
Never in production
Section titled “Never in production”| Variable | Meaning |
|---|---|
XLR8_ALLOW_SEED |
allows loading sample data, which deletes everything else |
XLR8_TEST_SWITCHES |
test switches for local development |
Convex itself sets CONVEX_CLOUD_URL and CONVEX_SITE_URL; you do not set them.
Next step
Section titled “Next step”Continue with the web app.