vibesboarddocs

Environment variables#

Start from .env.example in the repository root — copy it to .env (or .env.local) and fill in the placeholders. Generate secrets with openssl rand -hex 32.

The Next.js app reads apps/web/.env.local, because next dev and the production server run with apps/web as their working directory. The root .env / .env.local files reach root-level scripts (db:migrate, db:seed, scripts/*) and the E2E harness. In most setups you keep both in sync.

Tables below list Required as it's presented in .env.example: variables that ship uncommented in the template are needed for a normal deployment; variables commented out with # are optional and the app runs without them.

Platform AI#

These configure the platform-wide LLM fallback — used only when a workspace has no matching provider configured under Settings → LLM Providers. Tenant provider keys are never stored in the environment; they live encrypted in the database. See bring your own LLM.

NameRequiredDescription
OPENAI_API_KEYYesPlatform fallback API key
OPENAI_MODELYesDefault chat model for the platform fallback
OPENAI_BASE_URLNoOptional OpenAI-compatible gateway/proxy endpoint (also used by the local E2E mock). Third-party gateways that only implement /chat/completions and /embeddings cannot back this fallback, since @ai-sdk/openai@4 calls /responses — configure those providers per workspace instead
OPENAI_VISION_MODELNoOverride the vision-capable model (defaults to gpt-5.4-nano)
OPENAI_AGENT_CREATOR_MODELNoOverride the model used by the agent-creation assistant (defaults to gpt-5.4-nano)
OPENAI_EMBEDDINGS_MODELNoOverride the embeddings model (defaults to text-embedding-3-small). Stored vectors must be 384, 768, 1024, or 1536 dimensions

Application and auth#

Core application URL, session signing, and access-control secrets.

NameRequiredDescription
NEXT_PUBLIC_APP_URLYesCanonical app URL and Better Auth callback base — a mismatch with the port the app is served on breaks sign-in
BETTER_AUTH_SECRETYesServer-side session signing secret
AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRETNoGoogle sign-in is enabled only when both are set; leave unset to disable it
ACCESS_GATE_SECRETYesHashes access passwords and signs access cookies for password-gated public agents
ENCRYPTION_KEYYesEncrypts tenant LLM, OAuth, and channel credentials at rest. Changing this value makes every previously stored credential undecryptable
CRON_SECRETYesAuthenticates scheduled and background endpoints
RATE_LIMIT_SALTYesHMAC salt for anonymous-request rate-limit identifiers; use a different value per environment. ENCRYPTION_KEY is a fallback, but a dedicated salt keeps the two purposes independent
MONTHLY_MESSAGE_LIMITNoOptional monthly workspace message cap; omit for unlimited self-hosted usage
PUBLIC_CHAT_RATE_LIMIT_WINDOW_MS, PUBLIC_CHAT_SESSION_RATE_LIMIT, PUBLIC_CHAT_ADDRESS_RATE_LIMIT, PUBLIC_CHAT_AGENT_RATE_LIMITNoFixed-window rate limits for the public chat endpoint (per session, per address, per agent), stored in Postgres
PUBLIC_REVIEW_RATE_LIMIT_WINDOW_MS, PUBLIC_REVIEW_SESSION_RATE_LIMIT, PUBLIC_REVIEW_ADDRESS_RATE_LIMIT, PUBLIC_REVIEW_AGENT_RATE_LIMITNoSame, for the public review-generation endpoint
SMOKE_TEST_SECRETNoEnables the model-backed /api/smoke route, which is otherwise disabled. Configure only in CI or a private diagnostic environment — never as a health check

NEXT_PUBLIC_AUTH_GOOGLE also exists in .env.example, but it is a build-time-only value read by CI (ci-build.yml) and deploy-cloudrun.yml as a build arg — application code never reads it. Google sign-in is controlled entirely by whether AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET are set.

Data and storage#

Postgres connection roles and S3-compatible object storage.

NameRequiredDescription
DATABASE_URLYesApp role connection string, subject to row-level security. Used by the Next.js app and workers
DATABASE_MIGRATE_URLYesMigrate role connection string, BYPASSRLS. Used by drizzle-kit and the seed script, and at request time by admin-panel API routes (via getMigrateDb(), gated behind requireSuperAdmin()) — not only an offline migration credential
DATABASE_POOL_MAXNoOptional application connection-pool size (defaults to 10)
S3_ENDPOINTYesS3-compatible storage endpoint (MinIO in dev; your S3/R2/B2/etc. endpoint in production)
S3_REGIONYesStorage region
S3_BUCKETYesStorage bucket name
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYYesStorage credentials
S3_FORCE_PATH_STYLEYestrue for MinIO; false for AWS S3 / Cloudflare R2 / etc. (virtual-hosted style)

Integrations#

Webhook, messaging-channel, and third-party OAuth configuration. WhatsApp and Instagram accounts are configured per workspace and stored encrypted in the database — only the webhook verification secrets below belong in the environment.

NameRequiredDescription
WHATSAPP_INBOX_VERIFY_TOKENYesWebhook verification token for the WhatsApp inbox. See WhatsApp
INSTAGRAM_INBOX_VERIFY_TOKENYesWebhook verification token for the Instagram inbox. See Instagram
META_APP_SECRETYesMeta app secret, used for webhook signature verification
NEXT_PUBLIC_META_APP_IDNoMeta app identifier used by the inbox onboarding UI
NEXT_PUBLIC_FB_LOGIN_CONFIG_IDNoFacebook Login config ID used by the inbox onboarding UI
GOOGLE_CALENDAR_CLIENT_ID, GOOGLE_CALENDAR_CLIENT_SECRETYesGoogle Calendar OAuth client. See Google Calendar
GOOGLE_SHEETS_CLIENT_ID, GOOGLE_SHEETS_CLIENT_SECRETNoDedicated Google Sheets OAuth client; falls back to the Calendar client credentials when unset. See Google Sheets
GOOGLE_SERVICE_ACCOUNT_KEYYesService-account JSON, read only by scripts/register-risc.ts to register Google Cross-Account Protection (RISC)
RESEND_API_KEYNoEnables email delivery for verification, password reset, magic links, and notifications. Without it, these URLs are printed to the server console instead
NOTIFICATION_EMAIL_FROMNoSender identity for application email, e.g. Vibesboard <notifications@yourdomain.com>

OAuth redirect URIs for Google Calendar and Google Sheets are derived from NEXT_PUBLIC_APP_URL — do not set them separately.

Rendered on the /privacy-policy and /terms-of-service pages. Nothing is hardcoded, since this repository is public: a baked-in default would be inherited by every fork, and an unconfigured deployment would name someone else's company as the data controller. Leave all of these unset for local development — the legal pages stay readable and simply name nobody. Set them before running a deployment real users sign up to: GDPR Article 13 requires identifying the controller, and both Google OAuth verification and Meta app review check for it.

NameRequiredDescription
LEGAL_ENTITY_NAMENoOperating entity name, e.g. Example AB
LEGAL_ENTITY_REGISTRATION_NUMBERNoCompany registration number
LEGAL_ENTITY_VAT_NUMBERNoVAT number
LEGAL_ENTITY_ADDRESSNoRegistered address; use | to separate rendered lines, e.g. Example Street 1|111 22 Stockholm|Sweden
LEGAL_GOVERNING_COUNTRYNoGoverning law country
LEGAL_FORUMNoLegal forum, e.g. the Stockholm District Court
LEGAL_SUPERVISORY_AUTHORITYNoData-protection supervisory authority name
LEGAL_SUPERVISORY_AUTHORITY_URLNoSupervisory authority URL
LEGAL_CONTACT_EMAILNoPrivacy contact address
LEGAL_SERVICE_HOSTNoService hostname referenced in the legal documents

Deployment scripts (not read by the app)#

NameRequiredDescription
GCP_PROJECT_IDNoRead only by scripts/setup-secrets.sh, the GCP secret provisioning helper — not by the application itself

Next steps#