Repository navigation
Getting started
Cactus Foundation is deployed to Vercel - a hosting platform that handles the technical side for you. If someone is setting up Cactus on your behalf, point them to the technical sections below. If you're setting it up yourself and comfortable with a terminal, read on.
Once your site is up and running, you don't need any of the information on this page for day-to-day use. Head to Managing pages, Appearance and design, or the Configuration reference instead.
The rest of this page is for developers and technical users setting up a new Cactus install.
- Node.js ≥ 22
- A Vercel account (Hobby is fine for most features; Pro/Enterprise unlocks Vercel webhooks for automatic deployment status updates)
-
npm- this project uses npm only. Do not use yarn or pnpm. - A PostgreSQL database or a Neon API key to have one created automatically (see below)
git clone https://github.com/usersaynoso/cactus-foundation.git my-site
cd my-site
npm installCopy .env.example to .env.local and fill in the values:
cp .env.example .env.local| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL pooled connection string. Can be provisioned automatically - see below. |
SESSION_SECRET |
At least 32 random characters. Generate: openssl rand -base64 32
|
SITE_URL |
The canonical public domain, e.g. https://example.com. This is also the WebAuthn relying party ID and cannot be changed after the first passkey is registered.
|
VERCEL_API_TOKEN |
Vercel REST API token. Create at: Vercel dashboard → Account Settings → Tokens. Required for Edge Config writes, deployment status checks, and writing DATABASE_URL during automatic provisioning. |
VERCEL_PROJECT_ID |
Vercel project ID. Find it at: Vercel dashboard → your project → Settings → General. Must be set and the project redeployed before setup can proceed. |
| Variable | Gates |
|---|---|
NEON_API_KEY |
Automatic database provisioning during setup. Leave unset if you supply your own DATABASE_URL. |
BREVO_API_KEY |
Email (password login, verification, recovery). Alternative: SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS
|
CLOUDFLARE_WORKER_URL |
Media serving via Cloudflare Worker (required for all proxied providers; set automatically by the admin Deploy media Worker flow). CLOUDFLARE_WORKER_HOSTNAME is optional - derived from the URL when unset. |
Backblaze B2 - B2_APPLICATION_KEY_ID, B2_APPLICATION_KEY, B2_BUCKET_NAME, B2_ENDPOINT
|
Media uploads to B2 |
Cloudflare R2 - R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME
|
Media uploads to R2 |
AWS S3 - S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_BUCKET_NAME, S3_REGION
|
Media uploads to S3 |
DigitalOcean Spaces - SPACES_ACCESS_KEY_ID, SPACES_SECRET_ACCESS_KEY, SPACES_BUCKET_NAME, SPACES_REGION
|
Media uploads to Spaces |
Wasabi - WASABI_ACCESS_KEY_ID, WASABI_SECRET_ACCESS_KEY, WASABI_BUCKET_NAME, WASABI_REGION
|
Media uploads to Wasabi |
MinIO - MINIO_ENDPOINT, MINIO_ACCESS_KEY_ID, MINIO_SECRET_ACCESS_KEY, MINIO_BUCKET_NAME, MINIO_USE_SSL
|
Media uploads to MinIO |
Vercel Blob - BLOB_READ_WRITE_TOKEN
|
Media uploads to Vercel Blob |
Supabase Storage - SUPABASE_STORAGE_PROJECT_URL, SUPABASE_STORAGE_SERVICE_ROLE_KEY, SUPABASE_STORAGE_BUCKET_NAME
|
Media uploads to Supabase Storage |
Cloudinary - CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET
|
Media uploads to Cloudinary (direct CDN, no Worker) |
ImageKit - IMAGEKIT_PUBLIC_KEY, IMAGEKIT_PRIVATE_KEY, IMAGEKIT_URL_ENDPOINT
|
Media uploads to ImageKit (direct CDN, no Worker) |
GITHUB_API_TOKEN |
Module and theme install/update (needs repo scope) |
EDGE_CONFIG, VERCEL_EDGE_CONFIG_ID
|
Fast Edge Config reads |
VERCEL_WEBHOOK_SECRET |
Automatic deploy status (Pro/Enterprise only) |
TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY
|
Cloudflare Turnstile bot protection |
CLOUDFLARE_ZONE_ID, CLOUDFLARE_PURGE_API_TOKEN
|
Clearing cached pages the instant they are edited, when Settings → General → Speed → Keep ready-made copies of your pages is on. Both can be entered in the admin, on that same Speed tab. The page cache works fine without them - an edited page simply waits out its window. The token needs the Zone → Cache Purge permission. |
SENTRY_DSN |
Error reporting |
MEMBER_AREA_PATH |
Web address prefix for the member account area (see Members). Defaults to account. Deploy-time only - changing it needs a redeploy. |
STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET
|
Shop module - card payments via Stripe (see Shop). All three must be set before Stripe appears as a payment option at checkout. |
PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID, PAYPAL_MODE
|
Shop module - PayPal payments. PAYPAL_MODE is sandbox or live. |
# Apply the database schema (creates a migration file and updates the database)
npm run db:migrate
# Equivalent to: prisma migrate dev
# Start the dev server
npm run devOpen http://localhost:3000. If the database is empty (no SiteConfig row), you'll be redirected to /_setup to run the setup wizard.
When Cactus runs off Vercel it switches into local-development mode (detected by the absence of Vercel's VERCEL=1 variable). The setup wizard skips the Vercel connect step and reads config from .env.local; the VERCEL_API_TOKEN/VERCEL_PROJECT_ID variables can be left unset, and you set DATABASE_URL, SESSION_SECRET, and SITE_URL/NEXT_PUBLIC_SITE_URL=http://localhost:3000 instead. Some admin features (the env editor, core/module updates, the redeploy buttons) are read-only or hidden locally. See Running locally for the full picture.
- Push your repo to GitHub.
- Import the project in the Vercel dashboard.
- Add all required environment variables in Vercel's project settings (
SESSION_SECRET,SITE_URL,NEXT_PUBLIC_SITE_URL,VERCEL_API_TOKEN,VERCEL_PROJECT_ID). You do not need to setDATABASE_URLyet if you plan to use automatic provisioning. - Deploy. The build runs
prisma migrate deployautomatically - but ifDATABASE_URLisn't set yet, the build will fail at that step. If you're using automatic provisioning, let setup handle the database creation first (see below), then the next redeploy will succeed. - Visit your production URL - you'll be redirected to
/_setup.
The wizard runs once, at /_setup, and completes in three steps:
-
Connect your project - confirms required variables are set and your Vercel project is linked. If
DATABASE_URLis absent, you will be offered one of the three paths below. - Database - provisions or connects your PostgreSQL database and runs the schema migration.
- Account & configure - enter a username and email, then either register a passkey (fingerprint, Face ID, or security key) or, via the "Use Token instead of Passkeys" link, set up an authenticator app (TOTP) instead - both are equally valid ways to sign in. Then choose your secret admin path, site name, and timezone.
When setup completes, the wizard marks setupCompleted = true and redirects you to the admin dashboard.
Step 1 lists every domain already attached to the chosen Vercel project, plus any other domain sitting in your Vercel account waiting to be attached, and lets you add a brand new one. Whichever you pick becomes SITE_URL, so the wizard will not let you continue until that domain actually works.
Two separate things can be outstanding, and Vercel treats them independently:
-
The domain is not pointing at Vercel yet. You need the
A(orCNAME) record the wizard shows you. -
Vercel needs proof the domain is yours. This happens when the domain is also registered under a different Vercel account. Vercel issues a verification record - usually a
TXTon_vercel.yourdomain.com- and will not serve the domain until it sees it.
A domain can need one, the other, or both. The wizard lists every record that is still outstanding in a copy-and-paste table, rechecks every ten seconds, and unlocks the Configure project button as soon as Vercel is happy. If you would rather not touch individual records, pointing the domain's nameservers at Vercel (ns1.vercel-dns.com, ns2.vercel-dns.com) satisfies both conditions on its own.
Until then you cannot continue, and that is deliberate rather than us being difficult. The domain you choose becomes the address your passkeys are bound to, and that binding cannot be changed once the first passkey exists - so a domain that never comes to life would strand the whole site. Better a short wait now than a locked front door later.
DNS changes can take a while to spread. If you would rather not sit and watch, pick your *.vercel.app address instead: it works the moment the project exists, needs none of the above, and lets you finish setup straight away. You can attach the proper domain afterwards - just be aware that switching the site's address later means re-registering passkeys.
Optional features (email, image storage, bot protection, etc.) are not configured during setup. After setup, the dashboard shows an Additional features checklist with friendly descriptions and direct links to the relevant settings section.
If DATABASE_URL is present in your environment, the wizard proceeds normally. No change from the standard flow.
If DATABASE_URL is absent and NEON_API_KEY is present, the wizard offers three options:
- Cactus calls the Neon API to create a new free-tier Neon Postgres project in your chosen region.
- The pooled connection string is written as
DATABASE_URLto your Vercel project's environment variables via the Vercel REST API. - Writing that env var triggers a Vercel redeployment. During that build,
prisma migrate deployruns automatically and applies the database schema. The wizard cannot continue in the same page load - this is unavoidable. - The wizard shows a "redeploying" state and polls
/api/healthevery 5 seconds. Once the database is reachable, it advances you automatically.
Idempotency: if the page refreshes or the deployment fails mid-flight, the wizard detects the existing Neon project by name and reuses it. The project name follows the pattern cactus-{VERCEL_PROJECT_ID}.
- The wizard lists all Neon projects in your account. Select the one you want to connect.
- Before connecting, Cactus checks whether the selected project already has database tables. If it does, you'll see a warning with two choices:
- Use existing data - Cactus connects the project as-is. Any existing admin settings (site name, admin path, timezone) are pre-filled in the setup wizard. Prisma applies only pending migrations, leaving existing rows intact.
-
Destroy all existing data - Cactus drops and recreates the
publicschema before connecting. This gives Prisma a clean slate and is the right choice if you want a fresh Cactus install in a project that previously held other data.
- Cactus fetches the pooled connection URI, (optionally drops the schema), writes it to Vercel, then redeploys.
Warning: "Destroy all existing data" is irreversible - it permanently deletes everything in the database's public schema. Only choose this if you are certain you no longer need the existing data.
Paste any PostgreSQL connection string. Cactus writes it to Vercel and triggers a redeploy.
Why does the page pause? Vercel applies new environment variables only at redeploy time. The redeploy is the mechanism by which the build picks up the new variable and runs migrations. This is by design - the architecture guarantees migrations only ever run in the build step, never from the live app.
To use Path 2:
- Create a free Neon account at console.neon.tech.
- Go to Account Settings → API keys and generate a key.
- Add
NEON_API_KEYto your Vercel project's environment variables and redeploy (or add it before the initial deploy). - Ensure
VERCEL_API_TOKENandVERCEL_PROJECT_IDare also set - they are required for Cactus to writeDATABASE_URLback to your project. - Visit
/_setup. The wizard will offer the three options above.
If DATABASE_URL is absent and NEON_API_KEY is also absent, the wizard shows manual instructions:
- Create a Postgres database anywhere (Neon, Supabase, self-hosted, etc.).
- Copy the pooled connection string.
- Add it as
DATABASE_URLin your Vercel project's environment variables. - Trigger a redeploy. Migrations run during the build automatically.
- Return to
/_setup- setup continues from where it left off.
The wizard also shows a hint that setting NEON_API_KEY would allow automatic provisioning.
The site defaults to comingSoon status. To go live:
- Go to Settings → Site Status and set it to Live.
- Add optional credentials (email, media, GitHub, etc.) via the Additional features checklist on the dashboard, or navigate directly to Settings and the relevant tab.
Wiki: Home · Getting started · Managing pages · Appearance and design · Managing users · Members · Managing media · Modules · Configuration reference · Architecture overview · Authoring a module · Self-hosting and operations