Getting started

Environment variables

Externa reads configuration from .env (copied from .env.example during composer setup). Most keys map into config/*.php via env(). This page groups the variables you will actually set for externa-core and documents defaults.

Note

After changing env values that are cached in production, run php artisan config:clear or rebuild config cache. Locally, composer test already clears config before Pest runs.

App

Standard Laravel application identity and runtime flags from .env.example:

VariableDefault (example)Purpose
APP_NAMELaravelApplication name (also used in mail from-name interpolation). Set to Externa if you want branding in mail/UI strings that read the app name.
APP_ENVlocalEnvironment name (local, production, …).
APP_KEY(empty until generated)Encryption key; composer setup runs key:generate.
APP_DEBUGtrueDetailed errors when true — disable in production.
APP_URLhttp://localhostCanonical URL (scheme + host + port). Must match how you browse. App (password, TOTP, CMS) works on HTTP; passkeys need a secure context (HTTPS, or http://localhost / *.localhost — not http://*.test). Drives Fortify RP ID / allowed_origins. See Passkeys — Requirements.
APP_LOCALEenDefault locale.
APP_FALLBACK_LOCALEenFallback locale.
APP_FAKER_LOCALEen_USFaker locale for factories/seed demos.
APP_MAINTENANCE_DRIVERfileMaintenance mode driver.
APP_MAINTENANCE_STORE(commented)Optional store when using database maintenance driver.
BCRYPT_ROUNDS12Password hashing rounds.
LOG_CHANNELstackDefault log channel.
LOG_STACKsingleChannels in the stack.
LOG_DEPRECATIONS_CHANNELnullDeprecation log target.
LOG_LEVELdebugMinimum log level.
VITE_APP_NAME"${APP_NAME}"Exposed to the Vite/React build as the app name.
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

VITE_APP_NAME="${APP_NAME}"

Database, session, cache, and queue

Externa’s .env.example defaults to SQLite, database sessions, and Redis for queue + cache (full stack). For light local without Redis, set QUEUE_CONNECTION=database and CACHE_STORE=database.

VariableDefaultPurpose
DB_CONNECTIONsqliteDatabase driver.
DB_HOST127.0.0.1 (commented)Host for MySQL/Postgres.
DB_PORT3306 (commented)Port for non-SQLite drivers.
DB_DATABASElaravel (commented)Database name / path depending on driver.
DB_USERNAMEroot (commented)DB user.
DB_PASSWORD(empty, commented)DB password.
SESSION_DRIVERdatabaseSession storage.
SESSION_LIFETIME120Minutes.
SESSION_ENCRYPTfalseEncrypt session payload.
SESSION_PATH/Cookie path.
SESSION_DOMAINnullCookie domain.
QUEUE_CONNECTIONredis (fallback database)Full stack uses Redis + Horizon. Minimal: database + queue:listen.
CACHE_STOREredisPrefer Redis for app / public API cache; database is fine for light local.
CACHE_PREFIX(commented)Optional key prefix.
BROADCAST_CONNECTIONreverb (fallback log)Full: Reverb. Minimal: log (60s notification poll only when realtime is off).
FILESYSTEM_DISKlocalDefault filesystem disk.
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

# Full stack defaults in .env.example — switch to database/log for minimal:
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=local
QUEUE_CONNECTION=redis

CACHE_STORE=redis
# CACHE_STORE=database  # ok for light local without Redis cache

Redis (Herd / full stack)

Required for Horizon, recommended for app cache / public API performance, plus Reverb-friendly ops and Pulse redis ingest — see Redis prerequisite.

VariableDefault
REDIS_CLIENTphpredis
REDIS_HOST127.0.0.1
REDIS_PASSWORDnull
REDIS_PORT6379
REDIS_CACHE_DB1 (app cache; keep off queue DB)
REDIS_PULSE_DB2 (optional; connection pulse)
MEMCACHED_HOST127.0.0.1

Reverb (Herd Pro defaults)

Match Herd → Services → Reverb (:8080). Host for Echo must be 127.0.0.1 or localhost (not 0.0.0.0). Restart Vite after changing VITE_REVERB_*.

VariableDefaultNotes
REVERB_APP_ID1001Herd shared app id
REVERB_APP_KEYlaravel-herdShared with Vite
REVERB_APP_SECRETsecretServer secret
REVERB_HOST127.0.0.1WS host for clients
REVERB_PORT8080Herd Reverb port
REVERB_SCHEMEhttphttp / https
VITE_REVERB_APP_KEY"${REVERB_APP_KEY}"Echo key
VITE_REVERB_HOST"${REVERB_HOST}"Echo host
VITE_REVERB_PORT"${REVERB_PORT}"Echo port
VITE_REVERB_SCHEME"${REVERB_SCHEME}"Echo TLS flag

Standalone (no Herd Reverb): set your own REVERB_APP_* and run php artisan reverb:start.

BROADCAST_CONNECTION=reverb
REVERB_APP_ID=1001
REVERB_APP_KEY=laravel-herd
REVERB_APP_SECRET=secret
REVERB_HOST=127.0.0.1
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Pulse

VariableDefaultNotes
PULSE_ENABLEDtrueMaster switch
PULSE_INGEST_DRIVERredisOr storage without Redis worker
PULSE_REDIS_CONNECTIONpulseDedicated Redis connection
PULSE_STORAGE_KEEP2 daysTrim window
PULSE_INGEST_KEEP2 daysIngest trim

Warning

With Herd Reverb already on :8080, full local ops need horizon + pulse:work (do not also reverb:start). Standalone: composer run dev:full starts all three. Minimal: composer run dev + queue:listen. See Minimal vs full stack.

Mail

Local default is the log mailer (messages go to the log, not SMTP).

VariableDefaultPurpose
MAIL_MAILERlogMail transport.
MAIL_SCHEMEnullScheme when using SMTP-like transports.
MAIL_HOST127.0.0.1SMTP host.
MAIL_PORT2525SMTP port.
MAIL_USERNAMEnullSMTP user.
MAIL_PASSWORDnullSMTP password.
MAIL_FROM_ADDRESShello@example.comFrom address.
MAIL_FROM_NAME"${APP_NAME}"From display name.
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS (optional)

Scaffolded for S3-compatible disks if you configure FILESYSTEM_DISK / filesystems config accordingly:

VariableDefault
AWS_ACCESS_KEY_ID(empty)
AWS_SECRET_ACCESS_KEY(empty)
AWS_DEFAULT_REGIONus-east-1
AWS_BUCKET(empty)
AWS_USE_PATH_STYLE_ENDPOINTfalse

Files

Mapped in config/files.php:

VariableDefaultPurpose
FILES_DUPLICATE_SYNC_MAX_BYTES52428800 (50 MB)Single non-folder files at or below this size duplicate synchronously. Folders, bulk selections, and larger files use DuplicateFilesJob.
FILES_ZIP_TTL_MINUTES60Prepared zip lifetime under storage/app/zips before cleanup (files:cleanup-zips / related command).
FILES_ZIP_MAX_BYTES104857600 (100 MB)Maximum zip payload size enforced by application config.
FILE_PUBLIC_URL_BASE(optional)Override base URL for public/asset disks (config/filesystems.php). Also documented under Files & storage.
FILES_DUPLICATE_SYNC_MAX_BYTES=52428800
FILES_ZIP_TTL_MINUTES=60
FILES_ZIP_MAX_BYTES=104857600
# FILE_PUBLIC_URL_BASE=

Note

Public URLs for the public disk still require php artisan storage:link regardless of these variables.

AI

Mapped primarily in config/ai.php. Externa defaults to a local OpenAI-compatible provider (LM Studio, vLLM, etc.).

VariableDefaultPurpose
AI_DEFAULT_PROVIDERlocalProvider key under config('ai.providers').
LOCAL_AI_URLhttp://127.0.0.1:1234/v1Base URL for the local OpenAI-compatible API.
LOCAL_AI_API_KEY(empty)Optional bearer key if your gateway requires one.
LOCAL_AI_MODELopenai/gpt-oss-20b in .env.example (local-model if unset in config)Default text model for the local provider. Prefer tool-calling-capable models.
AI_REMOTE_IMPORT_HOSTS(empty)Comma-separated host allowlist for remote JSON import tools. Empty means no remote hosts allowed.
AI_WEBHOOK_TOKEN(empty)Shared secret for collection import webhook authentication.
AI_DAILY_PROMPT_LIMIT0Max prompts per day; 0 disables the limit.
AI_EMBEDDINGS_ENABLEDfalseToggles embeddings-related features.
AI_MCP_ENABLEDfalseToggles MCP-related features.
AI_DEFAULT_PROVIDER=local
LOCAL_AI_URL=http://127.0.0.1:1234/v1
LOCAL_AI_API_KEY=
LOCAL_AI_MODEL=openai/gpt-oss-20b
AI_REMOTE_IMPORT_HOSTS=
AI_WEBHOOK_TOKEN=
AI_DAILY_PROMPT_LIMIT=0
AI_EMBEDDINGS_ENABLED=false
AI_MCP_ENABLED=false

Other AI providers (optional)

config/ai.php also documents keys for cloud providers when you switch AI_DEFAULT_PROVIDER (or call a named provider explicitly). These are not all listed in .env.example, but are read when present:

VariableUsed by
ANTHROPIC_API_KEY, ANTHROPIC_URLAnthropic
AZURE_OPENAI_API_KEY, AZURE_OPENAI_URL, AZURE_OPENAI_API_VERSION, AZURE_OPENAI_DEPLOYMENT, embedding/image deployment varsAzure OpenAI
AWS_BEDROCK_REGION, AWS_BEARER_TOKEN_BEDROCK, AWS_SESSION_TOKEN, AWS_USE_DEFAULT_CREDENTIALSBedrock (plus standard AWS keys)
COHERE_API_KEYCohere
DEEPSEEK_API_KEYDeepSeek
ELEVENLABS_API_KEYElevenLabs
GEMINI_API_KEY, GEMINI_URLGemini
GROQ_API_KEYGroq
JINA_API_KEYJina
MISTRAL_API_KEYMistral
OLLAMA_API_KEY, OLLAMA_URLOllama (http://localhost:11434)
OPENAI_API_KEY, OPENAI_URLOpenAI
OPENROUTER_API_KEYOpenRouter
VOYAGEAI_API_KEYVoyage
XAI_API_KEYxAI

The local provider uses the openai driver with a custom url — laravel/ai accepts that pattern for OpenAI-compatible gateways.

Warning

Never commit real AI_WEBHOOK_TOKEN, cloud API keys, or production LOCAL_AI_API_KEY values. Rotate webhook tokens if they leak; remote import without a tight AI_REMOTE_IMPORT_HOSTS allowlist is unsafe.

Seeding (initial super admin)

Read by config/super_admin.php and used only when CreateSuperAdminSeeder runs (via php artisan db:seed).

VariableDefaultPurpose
INITIAL_SUPER_ADMIN_FIRST_NAMESuperFirst name.
INITIAL_SUPER_ADMIN_LAST_NAMEAdminLast name.
INITIAL_SUPER_ADMIN_EMAILsuperadmin@example.comLogin email.
INITIAL_SUPER_ADMIN_PASSWORDpasswordLogin password (local only).
INITIAL_SUPER_ADMIN_FIRST_NAME=Super
INITIAL_SUPER_ADMIN_LAST_NAME=Admin
INITIAL_SUPER_ADMIN_EMAIL=superadmin@example.com
INITIAL_SUPER_ADMIN_PASSWORD=password

These keys may be omitted from .env.example because config defaults already match local development; set them explicitly before seeding shared environments.

Built-in roles created by seeders (not env vars): super-admin, admin, reader, public.

Public CMS API

VariableDefaultPurpose
API_KEY_RATE_LIMIT60Default requests/minute per API key when the key has no explicit limit (config/api.php).
CORS_ALLOWED_ORIGINS*Comma-separated origins for api/* when project public_api_allowed_origins is empty (config/cors.php). Prefer the project allowlist in production; see Origin allowlist.
LIGHTHOUSE_QUERY_CACHE_ENABLEtrueCache parsed GraphQL query strings (config/lighthouse.php).
LIGHTHOUSE_QUERY_CACHE_MODEopcachePrefer opcache (default) over store when CACHE_STORE=database — avoids serializing AST into the DB cache.
API_KEY_RATE_LIMIT=60
CORS_ALLOWED_ORIGINS=*

Setup and auth model: Public CMS API.

Activity log

From config/activitylog.php (Spatie Laravel Activitylog):

VariableDefaultPurpose
ACTIVITYLOG_ENABLEDtrueWhen false, activities are not persisted.
ACTIVITYLOG_BUFFER_ENABLEDfalseBuffer activities in memory and flush after the response — useful only for high-volume logging per request.
ACTIVITYLOG_ENABLED=true
ACTIVITYLOG_BUFFER_ENABLED=false

Other activity log options (retention days, model class, soft-deleted subjects) are config-file defaults rather than .env keys — see config/activitylog.php (clean_after_days defaults to 365).

Quick reference: externa-specific block

Copy-paste block of the Externa-focused keys as they appear conceptually for a local AI-ready install:

# Files
FILES_DUPLICATE_SYNC_MAX_BYTES=52428800
FILES_ZIP_TTL_MINUTES=60
FILES_ZIP_MAX_BYTES=104857600
# FILE_PUBLIC_URL_BASE=

# AI (LM Studio / OpenAI-compatible local gateway)
AI_DEFAULT_PROVIDER=local
LOCAL_AI_URL=http://127.0.0.1:1234/v1
LOCAL_AI_API_KEY=
LOCAL_AI_MODEL=openai/gpt-oss-20b
AI_REMOTE_IMPORT_HOSTS=
AI_WEBHOOK_TOKEN=
AI_DAILY_PROMPT_LIMIT=0
AI_EMBEDDINGS_ENABLED=false
AI_MCP_ENABLED=false

# Seeding (optional overrides — config defaults apply if omitted)
INITIAL_SUPER_ADMIN_EMAIL=superadmin@example.com
INITIAL_SUPER_ADMIN_PASSWORD=password

# Activity log
ACTIVITYLOG_ENABLED=true
ACTIVITYLOG_BUFFER_ENABLED=false

# Public CMS API
API_KEY_RATE_LIMIT=60
CORS_ALLOWED_ORIGINS=*
Previous
Project layout