Cursorhero Docs

Environment Variables

Complete reference for all environment variables.

Required Variables

These variables are required for the application to function:

Database

DATABASE_URL="postgresql://user:password@host/db?sslmode=require"

Authentication

BETTER_AUTH_SECRET="at-least-32-characters-random-key"
BETTER_AUTH_URL="http://localhost:3000"

Application URL

NEXT_PUBLIC_APP_URL="http://localhost:3000"

Service Integrations

Kie.ai (cursor-generator pipeline)

Required for the main cursor-generator image-to-image pipeline:

KIE_API_KEY="your-kie-api-key"
KIE_API_BASE_URL="https://api.kie.ai"
KIE_IMAGE_MODEL="gpt-image-2-5-flare-image-to-image"
KIE_TEXT_MODEL="gpt-image-2-5-flare-text-to-image"
KIE_IMAGE_RESOLUTION="1K"

Volcano Engine (demo routes only)

Optional. Only used by the demo pages under app/[locale]/demo/ and the demo API routes (/api/chat, /api/image/generate, /api/video/*). The main cursor-generator product does not use Volcano Engine — it uses Kie.ai (see above). Skip these env vars if you are not shipping the demo pages.

# VOLCANO_ENGINE_API_KEY="your-volcano-engine-api-key"
# VOLCANO_ENGINE_API_URL="https://ark.cn-beijing.volces.com/api/v3"

Creem (Payments)

Required for subscriptions and one-time purchases:

CREEM_API_KEY="your-creem-api-key"
CREEM_WEBHOOK_SECRET="whsec_..."

For testing without real payments:

CREEM_SIMULATE="true"

Resend (Email)

Required for authentication emails and notifications:

RESEND_API_KEY="re_your_api_key"
RESEND_FROM_EMAIL="Your App <[email protected]>"

Optional, used by the newsletter subscribe / unsubscribe flow:

RESEND_AUDIENCE_ID="your-resend-audience-id"

Google OAuth (Optional)

AUTH_GOOGLE_ID="your-google-client-id"
AUTH_GOOGLE_SECRET="your-google-client-secret"

Cron Jobs

CRON_SECRET (Bearer) or CRON_JOBS_USERNAME + CRON_JOBS_PASSWORD (Basic Auth). At least one pair must be set, or the cron routes will reject every request. Used by the subscription grant job (/api/cron/subscription-grants) and any future scheduled job.

# Use this:
CRON_SECRET="your-cron-secret"

# ...or these:
# CRON_JOBS_USERNAME="your_username"
# CRON_JOBS_PASSWORD="your_password"

Optional Analytics

NEXT_PUBLIC_POSTHOG_KEY="phc_..."
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID="G-..."
NEXT_PUBLIC_CLARITY_PROJECT_ID="..."

GA4 conversion events (sign_up / begin_checkout / purchase / refund)

The app tracks GA4 standard conversion events:

  • sign_up — fired client-side only after a registration actually succeeds (email signup; Google OAuth success cannot be distinguished from a login and is intentionally not counted).
  • begin_checkout — fired client-side only when the user is redirected into the Creem hosted checkout with a real checkout URL.
  • purchase — fired server-side from the Creem webhook on a confirmed successful payment (checkout.completed / subscription.paid). It uses the unique Creem order id as transaction_id, so webhook replays never double-count; payment_kind distinguishes initial / renewal / one_time.
  • refund — fired server-side when a refund.created webhook transitions a payment to refunded, using the original order id as transaction_id.

Server-side events use the Measurement Protocol and require two env vars (create the API secret in GA4 Admin → Data Streams → Measurement Protocol API secrets). When they are empty, server-side events are skipped gracefully and billing is unaffected:

GA4_MEASUREMENT_ID="G-..."
GA4_API_SECRET="..."

Optional Storage (S3-Compatible / Cloudflare R2)

Required if you want generated cursor packs mirrored to your own bucket instead of relying on the provider's URL. R2 is recommended.

STORAGE_ENDPOINT="https://<account-id>.r2.cloudflarestorage.com"
STORAGE_BUCKET_NAME="your-bucket"
STORAGE_REGION="auto"
STORAGE_ACCESS_KEY_ID="..."
STORAGE_SECRET_ACCESS_KEY="..."
STORAGE_PUBLIC_URL="https://<your-public-r2-domain>"

Field name is STORAGE_BUCKET_NAME, not STORAGE_BUCKET. The codebase uses STORAGE_BUCKET_NAME everywhere; the wrong name silently fails to upload.

On this page