JezK
Edit File: .env
APP_NAME=Laravel APP_ENV=production APP_KEY=[[key]] APP_DEBUG=false APP_TIMEZONE=UTC APP_URL="[[softurl]]/public" REPORT_ERRORS=false APP_LOCALE=en APP_FALLBACK_LOCALE=en APP_FAKER_LOCALE=en_US APP_MAINTENANCE_DRIVER=file # APP_MAINTENANCE_STORE=database BCRYPT_ROUNDS=12 LOG_CHANNEL=stack LOG_STACK=single LOG_DEPRECATIONS_CHANNEL=null # Use "debug" for development; use "warning" or "error" in production LOG_LEVEL=warning DB_CONNECTION=mysql DB_HOST="[[softdbhost]]" DB_PORT=3306 DB_DATABASE="[[softdb]]" DB_USERNAME="[[softdbuser]]" DB_PASSWORD="[[softdbpass]]" SESSION_DRIVER=file SESSION_LIFETIME=120 SESSION_ENCRYPT=true SESSION_PATH=/ # In hosted mode (IS_HOSTED=true) set to your base domain with a leading dot # (e.g. .yourdomain.com) so sessions span subdomains. If left unset, hosted # mode defaults it to the APP_URL base domain automatically. SESSION_DOMAIN=null # Set to false for local development without HTTPS SESSION_SECURE_COOKIE=true BROADCAST_CONNECTION=log FILESYSTEM_DISK=local QUEUE_CONNECTION=sync # Where backup export archives are written. Defaults to the local disk (storage/app), which is # correct for a single-server install. Set these only if the app runs on more than one container or # server, where the process that builds an export is not the one that later serves the download. # # WARNING: this must NOT be the same bucket you serve images from. An export archive contains every # sale, attendee email and phone number for the schedules in it, and image buckets are typically # public and CDN-fronted. Use a separate private bucket with no public policy. BACKUP_SPACES_BUCKET # has no fallback on purpose: a missing value fails rather than writing backups somewhere public. # BACKUP_DISK_DRIVER=s3 # BACKUP_SPACES_KEY= # BACKUP_SPACES_SECRET= # BACKUP_SPACES_REGION= # BACKUP_SPACES_ENDPOINT= # BACKUP_SPACES_BUCKET= CACHE_STORE=file CACHE_PREFIX= MEMCACHED_HOST=127.0.0.1 REDIS_CLIENT=phpredis REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 MAIL_MAILER=log MAIL_HOST=127.0.0.1 MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="hello@example.com" MAIL_FROM_NAME="${APP_NAME}" AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" GEMINI_API_KEY= OPENAI_API_KEY= # AI provider selection (gemini or openai) AI_TEXT_PROVIDER=gemini AI_IMAGE_PROVIDER=openai # AI model selection (optional) # Translation models are used for batch translation tasks # Content models are used for interactive features (event parsing, AI details, style generation, blog posts) GEMINI_TRANSLATION_MODEL= GEMINI_CONTENT_MODEL= GEMINI_IMAGE_MODEL= OPENAI_TRANSLATION_MODEL= OPENAI_CONTENT_MODEL= OPENAI_IMAGE_MODEL= # Stripe Platform # Used for subscription billing (hosted mode) AND direct ticket payments (self-hosted mode) # In self-hosted mode, STRIPE_PLATFORM_KEY and STRIPE_PLATFORM_SECRET enable Stripe payments for ticket sales STRIPE_PLATFORM_KEY= STRIPE_PLATFORM_SECRET= STRIPE_PLATFORM_WEBHOOK_SECRET= STRIPE_PRICE_MONTHLY= STRIPE_PRICE_YEARLY= STRIPE_ENTERPRISE_PRICE_MONTHLY= STRIPE_ENTERPRISE_PRICE_YEARLY= # Displayed plan prices. These must match the amounts on the Stripe Price objects above: # nothing reconciles them, so a mismatch means the site advertises one number and charges # another. Defaults live in config/services.php (5 / 50 / 15 / 150). # # A super-admin can override all four at /admin/settings without touching this file or # re-running config:cache. That override is display only, exactly as these are. Keep these set # anyway: revenue reporting (ARR, MRR) and renewal emails deliberately read the values here, so # that a marketing change cannot restate revenue that was already booked. STRIPE_PRICE_MONTHLY_AMOUNT= STRIPE_PRICE_YEARLY_AMOUNT= STRIPE_ENTERPRISE_PRICE_MONTHLY_AMOUNT= STRIPE_ENTERPRISE_PRICE_YEARLY_AMOUNT= # The currency those amounts are shown in, everywhere the app quotes its OWN price: the # marketing pages, the Plan tab, upgrade prompts and the JSON-LD offers. Also the fallback # currency for a new event whose schedule has no country set. # # A label, like the amounts above - what a customer is charged comes from the Stripe Price. # A super-admin can change it at /admin/settings, which overrides this. Defaults to USD. # PLATFORM_CURRENCY=USD # Payfast Platform # Payfast is the dominant gateway in South Africa, where Stripe is not available. Setting all # three values below enables Payfast for ticket sales across every schedule on a selfhosted # install, the same way STRIPE_PLATFORM_KEY and STRIPE_PLATFORM_SECRET do for Stripe. # # These are a DEFAULT, never an override: a schedule owner who connects their own Payfast # account in Settings > Payment Methods keeps using it, and their money keeps reaching them. # Ignored in hosted mode, where each owner must connect their own account. # # The passphrase is optional at Payfast but required here - without one, the payment # notification signature is a plain MD5 that anyone could reproduce. PAYFAST_MERCHANT_ID= PAYFAST_MERCHANT_KEY= PAYFAST_PASSPHRASE= # Use Payfast's sandbox instead of live. Test payments look completely normal, so leave this # false unless you are testing. PAYFAST_SANDBOX=false # Restrict the checkout to ONE payment type, e.g. "ef" for Instant EFT. Leave empty to let # Payfast offer everything. Codes: cc dc ef cp ap sp gp mc mt ss zp mu nd pf rc ab sc mp PAYFAST_PAYMENT_TYPES= # What a new event's payment method starts on. Defaults to cash. Set this to the gateway your # install actually uses - e.g. payfast - so owners are not picking it by hand every time. It # only applies when the gateway is genuinely usable for that event (connected, and able to # settle the event's currency); anything else falls back to cash. DEFAULT_PAYMENT_METHOD= # White-label settings IS_HOSTED=false # Selfhosted installs are single user by default: the first account created is the # instance admin, and further sign-ups are blocked. Set to true to let anyone register. # Registered users can create their own schedules, so only enable this on a trusted # network. For real multi-tenant hosting use IS_HOSTED=true (self-hosted SaaS) instead. ALLOW_REGISTRATION=false APP_LOGO_DARK=/images/dark_logo.png APP_LOGO_LIGHT=/images/light_logo.png APP_MARKETING_URL=https://eventschedule.com SUPPORT_EMAIL=contact@eventschedule.com # The upstream Event Schedule app this install talks to: where shared translation # improvements are sent, and where federated events are published. Defaults to # eventschedule.com, so it only needs setting to point at a different network. #NEXUS_URL=https://eventschedule.com TRIAL_DAYS=365 # Custom dashboard links (optional) - up to 3 links shown in the admin sidebar # for all admins. A link only appears when BOTH its title and URL are set. # Works in both selfhosted and hosted (SaaS) deployments. CUSTOM_LINK_1_TITLE= CUSTOM_LINK_1_URL= CUSTOM_LINK_2_TITLE= CUSTOM_LINK_2_URL= CUSTOM_LINK_3_TITLE= CUSTOM_LINK_3_URL= # Exclude events/schedules from a country in marketing search (2-letter ISO code, e.g. "us"). Blank = no filter. SEARCH_EXCLUDE_COUNTRY= # Secret token for cron job endpoints (/release_tickets, /translate_data) APP_CRON_SECRET= # How stale the scheduler heartbeat may get before the admin panel warns that scheduled tasks have # stopped. Both cron rails stamp it every tick, so this is many missed ticks, not one slow one. # Keep it above 15: the /translate_data rail holds a 900-second lock, and a request killed by a # timeout leaves that lock held for its full TTL with no heartbeat stamped behind it. # SCHEDULER_STALE_MINUTES=20 # Which cron rail this process is, shown on /admin/queue so a dead worker is visible even while # another rail is still ticking. schedule:run cannot tell a crontab from a worker, so the # deployment has to say. Set to 'worker' on a dedicated scheduler container. # SCHEDULER_RAIL=cron # Which rail MUST be alive for scheduled work to count as happening. Leave unset on a single-cron # install. Set it to 'worker' once a dedicated scheduler container exists: otherwise another rail # keeping the shared heartbeat fresh masks a dead worker, and the admin panel reports all clear. # It must match that container's SCHEDULER_RAIL exactly - any name works, but a mismatch means the # admin panel is watching a rail nothing writes, and reports a stall that is not real. # SCHEDULER_EXPECTED_RAIL= # Trusted proxies - required when running behind a reverse proxy (Nginx/Apache/Cloudflare) # to avoid infinite HTTPS redirect loops. Set to * to trust all proxies, or a # comma-separated list of proxy IPs/CIDRs (e.g. 10.0.0.0/8,192.168.1.1). TRUSTED_PROXIES= # Twilio SMS (optional phone verification) TWILIO_SID= TWILIO_AUTH_TOKEN= TWILIO_FROM_NUMBER= # OneSignal push notifications (optional). When set, the app and visitors' # browsers communicate with OneSignal's servers and load OneSignal's SDK from # their CDN. Leave blank to disable push entirely (no SDK is loaded and no # external calls are made). Get these from the OneSignal dashboard. ONESIGNAL_APP_ID= ONESIGNAL_REST_API_KEY= # ONESIGNAL_SAFARI_WEB_ID= # only needed for legacy macOS Safari web push # Cookie consent banner. Normally shown only when something on the page actually needs # consent (Google Analytics via ANALYTICS_ID, ADS_ENABLED, STAY22_ENABLED). Set this to true # to show it anyway, which is what enables the 30-day UTM attribution cookies # (utm_params, utm_referrer_url, utm_landing_page) used to credit a signup or sale to the # link that brought the visitor. Those are written only after a visitor clicks Allow. # Left false, a bare install shows no banner and sets no non-essential cookies: attribution # still works within a single session, it just does not survive across sessions. COOKIE_CONSENT_BANNER=false # Monetization (optional, multi-tenant hosted installs only). Off by default. # When enabled, free-tier schedules' public pages can carry Google AdSense and/or paid # promotions bought by other schedules; paid schedules never show either. ADS_ENABLED is a # deploy-time gate that cannot be overridden from the admin panel - leave it false and the # feature does not exist. See https://eventschedule.com/docs/saas/monetization for the # consent and AdSense policy obligations this places on you as the operator. # # Turning promotions OFF at /admin/settings is safe at any time: campaigns stop serving and # the scheduled promo:sync keeps settling and refunding what is already paid for. Setting # ADS_ENABLED back to false, however, also stops promo:sync, so leave it true until any # outstanding campaigns have completed and refunded. ADS_ENABLED=false # The settings below are editable at /admin/settings once the feature is on; the values here # only supply the starting defaults. # ADSENSE_ENABLED=true # ADSENSE_PUBLISHER_ID=ca-pub-XXXXXXXXXXXXXXXX # ADSENSE_EVENT_SLOT_ID=XXXXXXXXXX # ADSENSE_PERSONALIZED=false # true opts into personalized ads; see the consent obligations # PROMOTIONS_ENGINE_ENABLED=true # NATIVE_PROMO_PRIORITY_OVER_PROGRAMMATIC=true # PROMOTIONS_NETWORK_CPM=2.00 # PROMOTIONS_NETWORK_CPC=0.25 # These have no admin-panel equivalent - they can only be set here. # # PROMOTIONS_CURRENCY is deliberately NOT tied to the platform currency picked at # /admin/settings: a promotion purchase is a live Stripe charge, and re-denominating one from a # dropdown documented as display-only is not something an admin should be able to do by accident. # PROMOTIONS_CURRENCY=USD # PROMOTIONS_MIN_BUDGET=5.00 # PROMOTIONS_MAX_BUDGET=1000.00 # a schedule's own trust limit can still be lower # PROMOTIONS_MAX_CONCURRENT=2 # live promotions per schedule # PROMOTIONS_FREQUENCY_CAP=3 # times a visitor sees the same promotion per day # PROMOTIONS_AUTO_APPROVE_AFTER=3 # approved campaigns before an advertiser skips review # PROMOTIONS_MIN_CTR=0.0002 # CPC campaigns below this are paused # PROMOTIONS_MIN_CTR_IMPRESSIONS=5000 # ...but only once they have this many impressions # PROMOTIONS_CACHE_TTL=300 # seconds the servable-campaign list is cached # PROMOTIONS_STATS_RETENTION_DAYS=400 # Stay22 accommodation map (optional). Independent of ADS_ENABLED: it applies to paid schedules # too, and each schedule may supply its own affiliate ID and keep the commission itself. # # STAY22_ENABLED is a deploy-time gate that cannot be overridden from the admin panel, because the # Content-Security-Policy is built from it on every request. Leave it false and the feature does not # exist: no frame host is allow-listed, and the per-schedule toggle is not even rendered. Changing it # requires regenerating the config cache (php artisan config:cache). # # The map never loads until a visitor either has accepted cookies or explicitly clicks to show it. # See https://eventschedule.com/docs/saas/monetization for the disclosure obligations this places on # you as the operator. STAY22_ENABLED=false # Fallback affiliate ID, used for schedules that enabled the map but did not add their own. Editable # at /admin/settings once the feature is on; the value here only supplies the starting default. # Never used on a customer's own custom domain. # STAY22_AID= # STAY22_MAX_NIGHTS=30 # upper bound on the derived stay length # Tickets # How many options the guest quantity dropdown offers for a ticket with no "Max Per Order" # of its own. Clamped to 1-100. Not a purchase limit: checkout enforces only the ticket's # own max per order and its remaining stock. # MAX_TICKETS_PER_ORDER=20 # DigitalOcean App Platform (optional custom domain provisioning) DO_API_TOKEN= DO_APP_ID= DO_APP_HOSTNAME= # Cloudflare Turnstile (optional bot protection) TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY= # Web accessibility (optional overrides; see config/accessibility.php) # ACCESSIBILITY_CONTACT_EMAIL=accessibility@example.com # ACCESSIBILITY_WCAG_TARGET_LABEL="WCAG 2.1 Level AA" # ACCESSIBILITY_REFERENCE_IS_5568=true # ACCESSIBILITY_RESPONSE_SLA_BUSINESS_DAYS=10 # ACCESSIBILITY_LAST_REVIEWED=2026-05-03 # Nexus / marketing-site behaviour. IS_NEXUS marks the one install that serves the marketing # pages (eventschedule.com). It is not a cosmetic flag: App\Http\Middleware\ # CacheableMarketingResponse and TrackMarketingVisit both stand down entirely when it is false, # so edge caching of marketing HTML and beacon page-view counting are inert without it. # IS_NEXUS=false # The session cookie name. Unset it and config/session.php derives one from APP_NAME # ("laravel_session" for the shipped APP_NAME). Worth knowing before you change either: a shared # cache in front of the app has to bypass on this exact cookie name to keep a signed-in visitor # off a stored anonymous page, and that rule is written in the CDN, not here. See docs/CACHING.md. # SESSION_COOKIE= # How long the homepage poster wall query is cached. 0 disables the cache, which is what the test # suite does - so DO NOT set this to an empty value on a deployed install. An empty value is read # as 0 and puts five correlated subqueries plus a regex pass on every origin hit of "/". # MARKETING_WALL_CACHE_SECONDS=600 # Ceilings on one run of each nudge or announcement command, so a backlog drains over several # runs rather than in one burst of mail. See config/usage.php for what each one bounds. # ONBOARDING_NUDGE_BATCH=500 # ACTIVATION_NUDGE_BATCH=200 # AUDIENCE_ANNOUNCEMENT_BATCH=100 # AUDIENCE_ANNOUNCEMENT_RECIPIENT_BATCH=2000 # Minimum hours between automatic new-event announcements to a schedule's subscribers. This is # the cadence the subscribe confirmation email promises ("at most one every few days"), so # lowering it breaks a promise already made to people who opted in. # AUDIENCE_ANNOUNCEMENT_MIN_HOURS=72 # Recipients one newsletter or announcement may reach when the sending schedule has neither its # own SMTP nor an SMS-verified owner, and so is going out on the platform's shared mailer. # AUDIENCE_MAIL_UNVERIFIED_MAX_RECIPIENTS=50