Configuration file - viames/pair GitHub Wiki
Pair framework: .env configuration
Pair reads runtime configuration from APPLICATION_PATH/.env through Pair\Core\Env.
This page reflects ~/Sites/pair/.env.example.
Reference .env template
# core app
APP_NAME="Pair Application"
APP_VERSION="1.0"
APP_ENV=production
APP_DEBUG=false
# database
DB_HOST=127.0.0.1
DB_NAME=pair
DB_USER=root
DB_PASS=
DB_UTF8=true
# framework behavior
OAUTH2_TOKEN_LIFETIME=3600
PAIR_SINGLE_SESSION=true
PAIR_AUTH_BY_EMAIL=true
PAIR_AUDIT_ALL=true
PAIR_API_RATE_LIMIT_ENABLED=true
PAIR_API_RATE_LIMIT_MAX_ATTEMPTS=60
PAIR_API_RATE_LIMIT_DECAY_SECONDS=60
PAIR_API_RATE_LIMIT_REDIS_PREFIX="pair:rate_limit:"
PAIR_TRUSTED_PROXIES=
PAIR_CACHE_DRIVER=file
PAIR_CACHE_PATH=
PAIR_CACHE_PREFIX=pair
PAIR_CACHE_REDIS_PREFIX="pair:cache:"
PAIR_OBSERVABILITY_ENABLED=false
PAIR_OBSERVABILITY_DEBUG_HEADERS=true
PAIR_OBSERVABILITY_TRACE_SAMPLE_RATE=1.0
PAIR_OBSERVABILITY_ERROR_SAMPLE_RATE=1.0
PAIR_OBSERVABILITY_MAX_SPANS=100
PAIR_OBSERVABILITY_MAX_EVENTS=50
UTC_DATE=true
# observability exporters (optional)
SENTRY_DSN=
SENTRY_ENVIRONMENT=
SENTRY_RELEASE=
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_ERROR_SAMPLE_RATE=1.0
SENTRY_TIMEOUT=10
SENTRY_CONNECT_TIMEOUT=3
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=
OTEL_EXPORTER_OTLP_HEADERS=
OTEL_SERVICE_NAME=
OTEL_SERVICE_VERSION=
OTEL_TIMEOUT=10
OTEL_CONNECT_TIMEOUT=3
# redis (optional, used by API rate limiting and cache when configured)
REDIS_HOST=
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_TIMEOUT=1
# crypto
AES_CRYPT_KEY=
OPTIONS_CRYPT_KEY=
# logger and notifications
PAIR_LOGGER_DISABLED=false
PAIR_LOGGER_EMAIL_RECIPIENTS=
PAIR_LOGGER_EMAIL_THRESHOLD=4
PAIR_LOGGER_TELEGRAM_CHAT_IDS=
PAIR_LOGGER_TELEGRAM_THRESHOLD=4
TELEGRAM_BOT_TOKEN=
TELEGRAM_API_BASE_URL="https://api.telegram.org"
TELEGRAM_TIMEOUT=20
TELEGRAM_CONNECT_TIMEOUT=5
TELEGRAM_WEBHOOK_SECRET_TOKEN=
# passkey / webauthn
PASSKEY_RP_ID=
PASSKEY_RP_NAME=
PASSKEY_ALLOWED_ORIGINS=
PASSKEY_REQUIRE_USER_VERIFICATION=true
# google maps
GOOGLE_MAPS_API_KEY=
GOOGLE_MAPS_BROWSER_API_KEY=
GOOGLE_MAPS_TIMEOUT=15
GOOGLE_MAPS_CONNECT_TIMEOUT=5
# whatsapp cloud api
WHATSAPP_CLOUD_ACCESS_TOKEN=
WHATSAPP_CLOUD_PHONE_NUMBER_ID=
WHATSAPP_CLOUD_API_VERSION=v23.0
WHATSAPP_CLOUD_API_BASE_URL="https://graph.facebook.com"
WHATSAPP_CLOUD_TIMEOUT=20
WHATSAPP_CLOUD_CONNECT_TIMEOUT=5
WHATSAPP_CLOUD_WEBHOOK_VERIFY_TOKEN=
WHATSAPP_CLOUD_APP_SECRET=
# stripe (optional)
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_API_VERSION=
# openai (optional)
OPENAI_API_KEY=
OPENAI_API_BASE_URL="https://api.openai.com/v1"
OPENAI_RESPONSES_MODEL=gpt-5.4-mini
OPENAI_EMBEDDINGS_MODEL=text-embedding-3-small
OPENAI_REALTIME_MODEL=gpt-realtime
OPENAI_TIMEOUT=30
OPENAI_CONNECT_TIMEOUT=5
OPENAI_STORE_RESPONSES=false
# gemini (optional)
GEMINI_API_KEY=
GEMINI_API_BASE_URL="https://generativelanguage.googleapis.com/v1beta"
GEMINI_GENERATION_MODEL=gemini-2.5-flash
GEMINI_EMBEDDINGS_MODEL=gemini-embedding-2
GEMINI_TIMEOUT=30
GEMINI_CONNECT_TIMEOUT=5
# claude (optional)
CLAUDE_API_KEY=
CLAUDE_API_BASE_URL="https://api.anthropic.com/v1"
CLAUDE_MESSAGES_MODEL=claude-sonnet-4-5
CLAUDE_API_VERSION=2023-06-01
CLAUDE_MAX_TOKENS=1024
CLAUDE_TIMEOUT=30
CLAUDE_CONNECT_TIMEOUT=5
# resend (optional)
RESEND_API_KEY=
RESEND_API_BASE_URL="https://api.resend.com"
RESEND_FROM_ADDRESS=
RESEND_FROM_NAME=
RESEND_WEBHOOK_SECRET=
RESEND_TIMEOUT=20
RESEND_CONNECT_TIMEOUT=5
# cloudflare turnstile (optional)
CLOUDFLARE_TURNSTILE_SITE_KEY=
CLOUDFLARE_TURNSTILE_SECRET_KEY=
CLOUDFLARE_TURNSTILE_VERIFY_URL="https://challenges.cloudflare.com/turnstile/v0/siteverify"
CLOUDFLARE_TURNSTILE_RESPONSE_FIELD=cf-turnstile-response
CLOUDFLARE_TURNSTILE_TIMEOUT=10
CLOUDFLARE_TURNSTILE_CONNECT_TIMEOUT=3
# meilisearch (optional)
MEILISEARCH_HOST="http://127.0.0.1:7700"
MEILISEARCH_API_KEY=
MEILISEARCH_DEFAULT_INDEX=
MEILISEARCH_TIMEOUT=10
MEILISEARCH_CONNECT_TIMEOUT=3
MEILISEARCH_SEARCH_LIMIT=20
# supabase (optional)
SUPABASE_URL=
SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_TIMEOUT=20
SUPABASE_CONNECT_TIMEOUT=5
# web push (vapid)
PUSH_VAPID_PUBLIC=
PUSH_VAPID_PRIVATE=
PUSH_VAPID_SUBJECT=
Key groups
Core app
APP_NAMEAPP_VERSIONAPP_ENVAPP_DEBUG
Database
DB_HOSTDB_NAMEDB_USERDB_PASSDB_UTF8
Framework behavior
OAUTH2_TOKEN_LIFETIMEPAIR_SINGLE_SESSIONPAIR_AUTH_BY_EMAILPAIR_AUDIT_ALLPAIR_API_RATE_LIMIT_ENABLEDPAIR_API_RATE_LIMIT_MAX_ATTEMPTSPAIR_API_RATE_LIMIT_DECAY_SECONDSPAIR_API_RATE_LIMIT_REDIS_PREFIXPAIR_TRUSTED_PROXIESPAIR_CACHE_DRIVERPAIR_CACHE_PATHPAIR_CACHE_PREFIXPAIR_CACHE_REDIS_PREFIXPAIR_OBSERVABILITY_ENABLEDPAIR_OBSERVABILITY_DEBUG_HEADERSPAIR_OBSERVABILITY_TRACE_SAMPLE_RATEPAIR_OBSERVABILITY_ERROR_SAMPLE_RATEPAIR_OBSERVABILITY_MAX_SPANSPAIR_OBSERVABILITY_MAX_EVENTSUTC_DATE
Cache
PAIR_CACHE_DRIVERPAIR_CACHE_PATHPAIR_CACHE_PREFIXPAIR_CACHE_REDIS_PREFIXREDIS_HOSTREDIS_PORTREDIS_PASSWORDREDIS_DBREDIS_TIMEOUT
Notes:
PAIR_CACHE_DRIVERacceptsfile,apcu, orredisfileis the dependency-free default and usesPAIR_CACHE_PATHwhen providedapcurequires APCu to be installed and enabled in the current runtimeredisrequires the Redis PHP extension andREDIS_HOSTPAIR_CACHE_REDIS_PREFIXnamespaces cache keys independently from rate limiter keys
Example:
PAIR_CACHE_DRIVER=redis
PAIR_CACHE_REDIS_PREFIX="pair:cache:"
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_TIMEOUT=1
Redis for API rate limiting
REDIS_HOSTREDIS_PORTREDIS_PASSWORDREDIS_DBREDIS_TIMEOUTPAIR_API_RATE_LIMIT_REDIS_PREFIX
Notes:
- if
REDIS_HOSTis empty, Pair falls back to file-based rate limiting - if Redis is temporarily unavailable, Pair falls back to file-based storage automatically
PAIR_TRUSTED_PROXIESshould contain exact IPs and/or CIDR ranges for trusted reverse proxies
Example:
PAIR_API_RATE_LIMIT_ENABLED=true
PAIR_API_RATE_LIMIT_MAX_ATTEMPTS=120
PAIR_API_RATE_LIMIT_DECAY_SECONDS=60
PAIR_API_RATE_LIMIT_REDIS_PREFIX="pair:rate_limit:"
PAIR_TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_TIMEOUT=1
Crypto
AES_CRYPT_KEYOPTIONS_CRYPT_KEY
Logger and observability
PAIR_OBSERVABILITY_ENABLEDPAIR_OBSERVABILITY_DEBUG_HEADERSPAIR_OBSERVABILITY_TRACE_SAMPLE_RATEPAIR_OBSERVABILITY_ERROR_SAMPLE_RATEPAIR_OBSERVABILITY_MAX_SPANSPAIR_OBSERVABILITY_MAX_EVENTSPAIR_LOGBAR_SLOW_REQUEST_MSPAIR_LOGBAR_SLOW_QUERY_MSPAIR_LOGBAR_QUERY_BUDGETPAIR_LOGBAR_DUPLICATE_QUERY_BUDGETPAIR_LOGBAR_MAX_EVENTSPAIR_LOGBAR_SHOW_SQL_VALUESPAIR_LOGGER_DISABLEDPAIR_LOGGER_EMAIL_RECIPIENTSPAIR_LOGGER_EMAIL_THRESHOLDPAIR_LOGGER_TELEGRAM_CHAT_IDSPAIR_LOGGER_TELEGRAM_THRESHOLDTELEGRAM_BOT_TOKENTELEGRAM_API_BASE_URLTELEGRAM_TIMEOUTTELEGRAM_CONNECT_TIMEOUTTELEGRAM_WEBHOOK_SECRET_TOKEN
Notes:
PAIR_LOGGER_TELEGRAM_CHAT_IDSaccepts standard Telegram numeric chat IDs, including negative IDs for groups and channelsPAIR_OBSERVABILITY_ENABLED=trueenables Pair runtime spans without requiring a third-party package in the core frameworkPAIR_OBSERVABILITY_DEBUG_HEADERS=trueallows explicit responses to expose correlation and timing headers only whenAPP_DEBUG=truePAIR_OBSERVABILITY_TRACE_SAMPLE_RATEandPAIR_OBSERVABILITY_ERROR_SAMPLE_RATEcontrol telemetry volume before provider adapters are calledPAIR_OBSERVABILITY_MAX_SPANSandPAIR_OBSERVABILITY_MAX_EVENTScap in-process retained telemetryPAIR_LOGBAR_SLOW_REQUEST_MS,PAIR_LOGBAR_SLOW_QUERY_MS,PAIR_LOGBAR_QUERY_BUDGET, andPAIR_LOGBAR_DUPLICATE_QUERY_BUDGETtune local LogBar diagnosticsPAIR_LOGBAR_MAX_EVENTScaps retained LogBar entries for one requestPAIR_LOGBAR_SHOW_SQL_VALUES=falsekeeps SQL bound values hidden by default; sensitive values are still masked when the flag is enabledTELEGRAM_API_BASE_URLcan target the official cloud endpoint or a local Bot API serverTELEGRAM_WEBHOOK_SECRET_TOKENis the shared secret Telegram sends inX-Telegram-Bot-Api-Secret-Token
Observability exporters
SENTRY_DSNSENTRY_ENVIRONMENTSENTRY_RELEASESENTRY_TRACES_SAMPLE_RATESENTRY_ERROR_SAMPLE_RATESENTRY_TIMEOUTSENTRY_CONNECT_TIMEOUTOTEL_EXPORTER_OTLP_TRACES_ENDPOINTOTEL_EXPORTER_OTLP_HEADERSOTEL_SERVICE_NAMEOTEL_SERVICE_VERSIONOTEL_TIMEOUTOTEL_CONNECT_TIMEOUT
Notes:
SENTRY_DSNis used bySentryObservabilityAdapterSENTRY_ENVIRONMENTfalls back toAPP_ENVSENTRY_RELEASEfalls back toAPP_VERSIONOTEL_EXPORTER_OTLP_TRACES_ENDPOINTis used byOpenTelemetryHttpExporterOTEL_EXPORTER_OTLP_HEADERSaccepts comma-separatedName=valuepairsOTEL_SERVICE_NAMEandOTEL_SERVICE_VERSIONdefault to app metadata when empty
Supabase
SUPABASE_URLSUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYSUPABASE_TIMEOUTSUPABASE_CONNECT_TIMEOUT
Notes:
SUPABASE_URLis the project base URL used bySupabaseClientSUPABASE_ANON_KEYis used for user-scoped requests and Realtime URL constructionSUPABASE_SERVICE_ROLE_KEYis for trusted server-side calls only and must never be exposed to browsers or client appsSUPABASE_TIMEOUTandSUPABASE_CONNECT_TIMEOUTcontrol the dependency-free HTTP bridge
Passkey / WebAuthn
PASSKEY_RP_IDPASSKEY_RP_NAMEPASSKEY_ALLOWED_ORIGINSPASSKEY_REQUIRE_USER_VERIFICATION
Google Maps
GOOGLE_MAPS_API_KEYGOOGLE_MAPS_BROWSER_API_KEYGOOGLE_MAPS_TIMEOUTGOOGLE_MAPS_CONNECT_TIMEOUT
WhatsApp Cloud API
WHATSAPP_CLOUD_ACCESS_TOKENWHATSAPP_CLOUD_PHONE_NUMBER_IDWHATSAPP_CLOUD_API_VERSIONWHATSAPP_CLOUD_API_BASE_URLWHATSAPP_CLOUD_TIMEOUTWHATSAPP_CLOUD_CONNECT_TIMEOUTWHATSAPP_CLOUD_WEBHOOK_VERIFY_TOKENWHATSAPP_CLOUD_APP_SECRET
Notes:
WHATSAPP_CLOUD_ACCESS_TOKENshould be a long-lived or system user token withwhatsapp_business_messagingWHATSAPP_CLOUD_PHONE_NUMBER_IDis the business phone number ID used by the/messagesand/mediaendpointsWHATSAPP_CLOUD_WEBHOOK_VERIFY_TOKENis your own shared secret for thehub.*verification challengeWHATSAPP_CLOUD_APP_SECRETlets Pair verifyX-Hub-Signature-256on inbound webhooks
Stripe
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_API_VERSION
Notes:
STRIPE_SECRET_KEYis required only when using the defaultStripeGatewaySDK clientSTRIPE_WEBHOOK_SECRETis required only for Stripe webhook signature verificationSTRIPE_API_VERSIONis optional; leave it empty to use the Stripe account default
OpenAI
OPENAI_API_KEYOPENAI_API_BASE_URLOPENAI_RESPONSES_MODELOPENAI_EMBEDDINGS_MODELOPENAI_REALTIME_MODELOPENAI_TIMEOUTOPENAI_CONNECT_TIMEOUTOPENAI_STORE_RESPONSES
Notes:
OPENAI_API_KEYis required only when usingOpenAIClientoutbound callsOPENAI_STORE_RESPONSES=falsekeeps Responses API calls privacy-first by defaultOPENAI_RESPONSES_MODEL,OPENAI_EMBEDDINGS_MODEL, andOPENAI_REALTIME_MODELcan be changed per application or per request
Gemini
GEMINI_API_KEYGEMINI_API_BASE_URLGEMINI_GENERATION_MODELGEMINI_EMBEDDINGS_MODELGEMINI_TIMEOUTGEMINI_CONNECT_TIMEOUT
Notes:
GEMINI_API_KEYis required only when usingGeminiClientoutbound callsGEMINI_GENERATION_MODELandGEMINI_EMBEDDINGS_MODELcan be changed per application or per request
Claude
CLAUDE_API_KEYCLAUDE_API_BASE_URLCLAUDE_MESSAGES_MODELCLAUDE_API_VERSIONCLAUDE_MAX_TOKENSCLAUDE_TIMEOUTCLAUDE_CONNECT_TIMEOUT
Notes:
CLAUDE_API_KEYis required only when usingClaudeClientoutbound callsCLAUDE_API_VERSIONconfigures the required Anthropic API version headerCLAUDE_MAX_TOKENSis the default output cap when callers omitmax_tokens
Resend
RESEND_API_KEYRESEND_API_BASE_URLRESEND_FROM_ADDRESSRESEND_FROM_NAMERESEND_WEBHOOK_SECRETRESEND_TIMEOUTRESEND_CONNECT_TIMEOUT
Notes:
RESEND_API_KEYis required only when usingResendMaileroutbound callsRESEND_FROM_ADDRESSandRESEND_FROM_NAMEprovide the default sender identityRESEND_WEBHOOK_SECRETis required only for signed webhook verification
Cloudflare Turnstile
CLOUDFLARE_TURNSTILE_SITE_KEYCLOUDFLARE_TURNSTILE_SECRET_KEYCLOUDFLARE_TURNSTILE_VERIFY_URLCLOUDFLARE_TURNSTILE_RESPONSE_FIELDCLOUDFLARE_TURNSTILE_TIMEOUTCLOUDFLARE_TURNSTILE_CONNECT_TIMEOUT
Notes:
CLOUDFLARE_TURNSTILE_SITE_KEYis used byCloudflareTurnstileto render browser widgetsCLOUDFLARE_TURNSTILE_SECRET_KEYis required only for server-side token validationCLOUDFLARE_TURNSTILE_RESPONSE_FIELDdefaults to the standard Turnstile field posted by implicit widgets- Turnstile works independently from Cloudflare CDN usage
Meilisearch
MEILISEARCH_HOSTMEILISEARCH_API_KEYMEILISEARCH_DEFAULT_INDEXMEILISEARCH_TIMEOUTMEILISEARCH_CONNECT_TIMEOUTMEILISEARCH_SEARCH_LIMIT
Notes:
MEILISEARCH_HOSTpointsMeilisearchClientto the Meilisearch instanceMEILISEARCH_API_KEYis optional only for unsecured local development instancesMEILISEARCH_DEFAULT_INDEXis used by read-model sync when an index is not passed explicitlyMEILISEARCH_SEARCH_LIMITapplies only when a search call does not pass explicit pagination
Web Push (VAPID)
PUSH_VAPID_PUBLICPUSH_VAPID_PRIVATEPUSH_VAPID_SUBJECT
Notes
- Keep secrets in
.env, not in Git. - Keep numeric/boolean values unquoted if you want automatic type casting.
- Keep
APP_ENValigned with your deployment target (development/staging/production). - Use environment-specific
.envvalues for local/staging/production. - Keep
GOOGLE_MAPS_API_KEYserver-side only and use a browser-restricted key forGOOGLE_MAPS_BROWSER_API_KEY. - Keep
WHATSAPP_CLOUD_ACCESS_TOKENandWHATSAPP_CLOUD_APP_SECRETserver-side only. - Keep
CLOUDFLARE_TURNSTILE_SECRET_KEYserver-side only and validate tokens before public writes. - Keep
MEILISEARCH_API_KEYserver-side only and index only fields that are safe to expose in search results. - Keep observability exporter credentials server-side only and avoid sending request bodies or secrets in telemetry attributes.
- Tune API limits by traffic class and verify
429handling in clients.
See also: Env, API, RateLimiter, ThrottleMiddleware, Request, CloudflareTurnstile, MeilisearchClient.