Pitchbar reads its configuration from the standard Laravel
.env, with overrides for some keys available via the
platform admin's App Settings page so you can swap
Stripe / mail / LLM credentials without redeploying.
Required
Variable
Why
APP_KEY
Encryption master key. Generate with php artisan key:generate. Never rotate without a re-encrypt migration.
APP_URL
Public URL, used to build widget snippets, OAuth callbacks, signed URLs.
DB_*
Postgres connection.
REDIS_HOST
Cache, sessions, queue, hot-path retrieval cache, conversation history cache.
QUEUE_CONNECTION
Set to redis in production.
SESSION_DRIVER
redis in production.
CACHE_DRIVER
redis.
WIDGET_JWT_SECRET
HS256 signing secret for visitor JWTs. ≥ 32 random bytes.
LLM provider
At least one of the following:
Variable
Provider
CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN
Cloudflare Workers AI (preferred). Auto-binds Llama 3.3 70B + bge-base-en-v1.5.
OPENAI_API_KEY
OpenAI direct.
OPENROUTER_API_KEY
OpenRouter (router across many providers).
Set LLM_PROVIDER to force a binding (cloudflare,
openai, openrouter). When unset, the resolver
picks based on which keys are available, in the priority above.
Vector store
Variable
Provider
CLOUDFLARE_VECTORIZE_INDEX
Cloudflare Vectorize index name. Uses CLOUDFLARE_ACCOUNT_ID + token. Default pitchbar-chunks.
QDRANT_URL + QDRANT_API_KEY
Qdrant.
Set VECTOR_PROVIDER to force. Auto-binds based on
available keys (Vectorize preferred).
Crawler
Variable
Strategy
(uses CLOUDFLARE_*)
Cloudflare Browser Rendering. Preferred.
BROWSERLESS_TOKEN + BROWSERLESS_URL
Browserless fallback.
(none)
Plain HTTP. Free; no JS rendering.
Variable
Purpose
CRAWL_MAX_PAGES_PER_SOURCE
Max pages fan-out per Source. Default 500 (raised from 25 — buyers adding 100-URL sitemaps were silently losing 75 pages). Cap applies after sitemap-index recursion + dedupe.
CRAWL_PAGE_DISPATCH_DELAY
Per-page delay (seconds) when fanning out sitemap pages so Cloudflare Browser Rendering's small per-account concurrency doesn't 429. Default 2.
CLOUDFLARE_BROWSER_RENDERING
Set to false to force the Browserless / plain-HTTP path even when Cloudflare keys are present. Default true.
RAG / retrieval
Variable
Purpose
RAG_CONFIDENCE_THRESHOLD
Default similarity threshold for newly created agents. 0.5 for Cloudflare bge-base-en-v1.5 (its ANN cosine peaks lower than OpenAI's); 0.78 for OpenAI text-embedding-3-small. Auto-resolves based on LLM_PROVIDER.
RAG_QUERY_REWRITE
Hands the retrieval query to a small LLM instead of the built-in stitch. Off by default, and the safe default — the stitch is deterministic and costs nothing, while the rewrite adds a round-trip to every context-dependent turn. If you enable it, set RAG_QUERY_REWRITE_MODEL to a small fast model; leaving it empty runs the rewrite on your main chat model. Whatever it returns, any product name it dropped is added back before the search runs. php artisan pitchbar:diagnose-widget reports the current state.
RAG_RERANK_ENABLED
Set to false to disable the bge-reranker-base cross-encoder pass. Default true when Cloudflare keys are present.
RAG_RERANK_TIMEOUT_SECONDS
HTTP timeout for the reranker call, default 3. Deliberately short: the reranker only re-orders candidates the vector search already found, so failing fast to that ordering costs a little precision, while waiting costs the whole turn. At the previous 8-second timeout a slow reranker could push a turn past the widget's own connection timeout and the visitor saw a generic error instead of a slightly-less-ranked answer.
VECTOR_DIM
Embedding dimension override. Leave unset to auto-resolve from the configured embed model via the known-model map (bge-base=768, bge-m3=1024, text-embedding-3-small=1536, etc.). Set explicitly only when pointing at an unlisted model. Changing this on an existing install needs php artisan vector:rebuild-index — the Vectorize index is provisioned at the dim active when it was first created and cannot be resized in place.
Cloudflare AI Gateway (optional)
Variable
Purpose
CLOUDFLARE_AI_GATEWAY_URL
If set, Workers AI calls route through Cloudflare's AI Gateway — observability, caching, rate limits.
CLOUDFLARE_CHAT_MODEL
Default @cf/meta/llama-3.3-70b-instruct-fp8-fast.
CLOUDFLARE_EMBED_MODEL
Default @cf/baai/bge-base-en-v1.5.
CLOUDFLARE_VECTORIZE_INDEX
Vectorize index name. Default pitchbar-chunks.
OPENROUTER_CHAT_MODEL
OpenRouter chat model slug. Default meta-llama/llama-3.3-70b-instruct:free.
Qdrant collection name when using the Qdrant vector store fallback. Default pitchbar_chunks.
S3 / R2 (object storage for file uploads)
Variable
Purpose
AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
Bucket credentials. For Cloudflare R2, use the R2 token's key/secret pair.
AWS_DEFAULT_REGION
Region of the bucket. Use auto for R2.
AWS_BUCKET
Bucket name.
AWS_ENDPOINT
Custom S3-compatible endpoint. Required for R2 (e.g. https://<accountid>.r2.cloudflarestorage.com). Leave blank for native AWS S3.
AWS_USE_PATH_STYLE_ENDPOINT
true for R2 and most non-AWS S3 implementations; false for native AWS.
Frontend (Vite)
Variable
Purpose
VITE_WIDGET_CDN_URL
Base URL the visitor widget loader uses. Defaults to http://localhost:5173 in dev. In production, point at the public origin serving /widget/widget.js.
Default "Powered by Pitchbar" label. Override in app_settings for white-label.
BRANDING_URL
Where the label links to.
BRANDING_FOOTER_LOGO_PATH
Storage path to the footer logo image.
Observability
Variable
Purpose
SENTRY_LARAVEL_DSN
Error reporting. (Some hosts still ship the legacy SENTRY_DSN name; the Laravel SDK reads SENTRY_LARAVEL_DSN as of v4.x — keep both in .env for back-compat if you migrated.)
Google OAuth (Drive + Docs + Sheets) — register an OAuth client in the GCP console with drive.readonly, documents.readonly, and spreadsheets.readonly scopes. Omitting spreadsheets.readonly blocks the Google Sheets source type.
Marketing demo widget
Variable
Purpose
MARKETING_DEMO_AGENT_ID
UUID of a published agent that powers the live demo widget on the marketing pages. Add the marketing site URL to that agent's allowed_origins.
DEMO
Set to true to open Login / Get started links in a new tab on the marketing site — useful for reviewers exploring a CodeCanyon live demo without losing the marketing page when they hop into the app.
Queue tick (Cloudflare Worker cron)
Variable
Purpose
INTERNAL_QUEUE_TOKEN
Shared bearer secret protecting POST /api/v1/internal/queue-tick. The Cloudflare Worker cron pings this every 60s to drive the queue when in-cluster scheduling isn't available. Only set it when this host has no queue daemon. If PM2 or supervisor already runs queue:work, the tick is redundant — leave the token empty. The tick runs the worker in its own subprocess, so it can no longer take the web worker down with it, but two things draining the same queues buys you nothing.
App Settings overrides
The app_settings singleton row stores plaintext-encrypted
overrides for:
Stripe secret + webhook secret + publishable key.
Mail driver settings.
Cloudflare / OpenAI / OpenRouter keys.
Branding (label, URL, logo).
The AppSettingsOverrideServiceProvider reads this on boot
and merges into config(). Setting things via the admin
panel is preferred for production — you don't have to re-deploy when a
key rotates.
APP_KEY rotation requires a manual migration. The
encrypted columns in app_settings were sealed with the
old key; rotating without re-encrypting renders them unreadable.