qiick / externa-core
Externa — headless CMS / content API with operator admin UI.
Requires
- php: ^8.4.27
- azure-oss/storage-blob-laravel: ^2.2
- inertiajs/inertia-laravel: ^2.0
- laravel/ai: ^1.1
- laravel/fortify: ^1.34
- laravel/framework: ^13.29
- laravel/horizon: ^5.48
- laravel/mcp: ^1.1.2
- laravel/pulse: ^1.7
- laravel/reverb: ^1.11
- laravel/socialite: ^5.31
- laravel/tinker: ^3.0
- laravel/wayfinder: ^0.1.14
- league/flysystem-aws-s3-v3: ^3.25
- nuwave/lighthouse: ^6.69
- phpoffice/phpspreadsheet: ^5.9
- smalot/pdfparser: ^2.12
- spatie/eloquent-sortable: ^5.0
- spatie/laravel-activitylog: ^5.0
- spatie/laravel-google-cloud-storage: ^2.4
- spatie/laravel-permission: ^7.2
- spatie/laravel-tags: ^4.12
- symfony/html-sanitizer: ^8.1
- symfony/yaml: ^7.4|^8.0
Requires (Dev)
- fakerphp/faker: ^1.24
- larastan/larastan: ^3.12
- laravel/boost: ^2.0
- laravel/pail: ^1.2.5
- laravel/pint: ^1.27
- mockery/mockery: ^1.6
- nunomaduro/collision: ^8.9
- pestphp/pest: ^4.4
- pestphp/pest-plugin-browser: ^4.0
- pestphp/pest-plugin-laravel: ^4.1
- spaze/phpstan-disallowed-calls: ^4.15
Suggests
- ext-redis: Required for QUEUE_CONNECTION=redis, Horizon, Reverb scaling, and Pulse redis ingest.
- laravel/horizon: Redis queue dashboard and workers (composer require already includes it for full stack).
- laravel/pulse: Application metrics; use redis ingest + pulse:work in production.
- laravel/reverb: WebSocket server for Echo notifications and presence.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- 1.4.0
- 1.3.0
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.0
- 1.0.0
- 1.0.0-beta.4
- 1.0.0-beta.3
- 1.0.0-beta.2
- 1.0.0-beta.1
- dev-develop
- dev-docs/security-policy-72h
- dev-fix/755-758
- dev-refactor/283-ai-page-file-detail
- dev-refactor/277-project-settings-step-2
- dev-refactor/277-file-service-split
- dev-feat/275-ai-daily-budget
- dev-refactor/599-policies-foundation
- dev-feat/333-system-field-filters
- dev-feat/229-button-hierarchy
- dev-feat/230-alert-dialog
- dev-fix/item-presence-rejoin-on-save
- dev-315-fixrealtime-whisper-item-updated-at-after-item-save-so-peers-see-stale-banner
- dev-316-fixrealtime-item-save-never-whispers-updated_at-to-other-editors
- dev-317-chorebranding-adopt-qiick-logo-b-in-readme-header-+-social-preview
This package is auto-updated.
Last update: 2026-11-12 16:43:57 UTC
README
Externa
Version: 1.4.0 (from composer.json; mirrored in package.json). See CHANGELOG.md and Releasing docs.
Externa is a Laravel-native headless CMS: operators manage structured content in a full admin UI; websites and apps consume it through the Public CMS API (/api/v1) and GraphQL (/api/graphql). Optional in-app AI tools respect the signed-in user’s permissions — you own the code, so automation is Jobs/listeners, not a locked Flow canvas.
Who it is for: teams that want a self-hosted content API with a serious admin (collections, files, RBAC) on a stack they already know — Laravel 13, Inertia + React 19, Vite.
| Docs | docs.externa.qiick.io |
| Install | composer create-project qiick/externa-core · Installation · Upgrade · Minimal vs full stack |
| API | Public CMS API · GraphQL · Headless starter (Next.js + Nuxt) · Starter conventions · 1.x compatibility |
| Security | SECURITY.md · Threat model |
| Issues | GitHub Issues |
Highlights
- Dynamic collections — fields, locales, lean draft/publish, typed item editor, safe field type conversion with dry-run
- Hierarchical files — folders, uploads, versions, async zip, empty trash; local, S3, GCS or Azure disks; upload allowlist
- RBAC + groups — Spatie roles/permissions with group inheritance;
publicrole for the API - Public CMS API + GraphQL — collection access matrix, owned API keys,
sort, system and relational filters, batch writes; TypeScript SDK; 1.x compatibility policy - Chat + activity — item/private threads with live presence; Spatie activity log
- Optional AI assistant — OpenAI-compatible / LM Studio; side panel on any page (⌘J), saved prompts and context attachments; write tools need human approval
- Optional MCP server — the same permission-aware tools for MCP clients, through owned API keys
- Deploy hooks — Vercel, Netlify, Cloudflare Pages or Forge, from the admin or on publish
- Theme presets — six built-in presets (Teal, Blue, Indigo, Violet, Rose, Slate) or a custom brand colour
- Secure by default — security headers, signed webhooks, SSRF-hardened remote fetches, registration off on fresh installs
Stack: Laravel 13, Inertia + React 19, Vite, Spatie Permission / Activitylog, Wayfinder typed routes.
Requirements
| Requirement | Notes |
|---|---|
| PHP 8.4.27+ | Pinned in .php-version / composer.json (^8.4.27). Herd PHP 8.4 recommended on macOS. Required for CVE-2026-109618 / CVE-2026-109619. |
| Composer 2 | PHP dependencies and composer setup / composer run dev. |
| Node.js 24 | Pinned in .nvmrc and package.json engines. Use nvm use (or equivalent). |
| Database | SQLite 3.x (local/CI default); PostgreSQL 15+ (16 preferred, production recommended); MySQL 8.0+; MariaDB 10.6+ (10.11+ preferred). MySQL/MariaDB: utf8mb4 / utf8mb4_unicode_ci. Matrix: Supported databases. |
| Redis (optional) | Needed for Horizon, Reverb-friendly realtime, and Pulse redis ingest. Prod without Redis: docs. |
Optional: Laravel Herd (PHP, nginx, .test hosts; Pro adds shared Reverb on :8080). Optional AI: an OpenAI-compatible gateway (e.g. LM Studio) at LOCAL_AI_URL.
Quick start
Operators (recommended): create a new site from Packagist (no git clone):
composer create-project qiick/externa-core my-externa cd my-externa nvm use # Node 24 php artisan externa:install
Pin a release if needed: qiick/externa-core:1.4.0. Pre-tag smoke from git: composer create-project qiick/externa-core:dev-develop my-externa.
create-project only copies .env (if missing) and runs key:generate — no migrate, seed, or SQLite assumption. Interactive first-run is externa:install (DB, APP_NAME/URL, stack profile, optional seed / AI URL, npm build).
Install only — not the update channel.
composer create-projectcopies the app tree once. After you push that tree to your git remote, Packagist / Composer will not pull newer Externa releases into it. To stay current, track Git tags (or addupstreamand mergevX.Y.Z), then runphp artisan externa:upgradefor migrate / permissions / cache — optional opt-in code sync:php artisan externa:upgrade --sync-upstream=vX.Y.Z(dry-run first). See Upgrade. Docker operators: pull a newer GHCR image tag — same guide.
Package: packagist.org/packages/qiick/externa-core. Docs: Installation · Packagist & create-project. Historical beta tags needed @beta (or "minimum-stability": "beta").
Local paths: Laravel Herd (PHP / nginx / .test) or Docker Compose / GHCR quick-deploy — see Deploy with Docker and sections below. Health: GET /health/live, GET /health/ready.
CI / non-interactive: composer setup runs install → copy .env if missing → key:generate → migrate → npm install → npm run build. Still run db:seed, storage:link, and Wayfinder when needed. Do not use composer setup as the human create-project happy path.
Contributors (git clone)
git clone https://github.com/qiick-io/externa-core.git cd externa-core nvm use # Node 24 cp .env.example .env # Edit .env: set APP_URL to match how you browse. For no Redis, see “Minimal vs full” below. touch database/database.sqlite # if missing (SQLite default) composer install php artisan key:generate php artisan migrate php artisan db:seed php artisan storage:link npm install php artisan wayfinder:generate --with-form --no-interaction npm run build # or skip and rely on `npm run dev` / Vite HMR
Run locally
Minimal (no Redis — set QUEUE_CONNECTION=database and BROADCAST_CONNECTION=log in .env):
composer run dev
# serve + queue:listen + pail + vite
Or with Herd serving the site: keep Vite + a queue worker (php artisan queue:listen --tries=1 --timeout=0). For Herd HTTPS (herd secure), set HERD_SITE to your hostname (e.g. my-app.test) so Vite can detect TLS certs.
Full stack (Redis + realtime + Pulse — matches .env.example defaults):
- Enable Redis (and Herd Pro Reverb on
:8080if available). - Keep
QUEUE_CONNECTION=redis,BROADCAST_CONNECTION=reverb, and Reverb/Pulse keys from.env.example. - Start workers (Herd Reverb is already up — do not also
reverb:starton the same port):
php artisan horizon php artisan pulse:work npm run dev
Standalone all-in-one (starts its own Reverb — conflicts if Herd already binds :8080):
composer run dev:full
Open APP_URL (your Herd host, http://localhost, or php artisan serve). Unauthenticated / redirects to login.
Dev Container (VS Code / Codespaces)
Open the repo in VS Code Reopen in Container or GitHub Codespaces — .devcontainer/devcontainer.json reuses compose.yaml (app service). Post-create runs composer/npm/migrate/seed. Docs: Installation.
Docker (local full stack)
Official local path is Compose (compose.yaml) — Sail stays in require-dev but is not required. Stack: app (nginx+php-fpm) + Vite + Postgres + Redis + Horizon + Reverb + scheduler + Pulse + Mailpit. Optional MinIO profile for FILES_DISK=s3.
Image base is pinned to php:8.4.27-fpm-bookworm. Runtime hardening lives in docker/php/php.ini (copied as zz-externa.ini): session.upload_progress.enabled=0, session.use_strict_mode=1, and body/multipart limits aligned with nginx client_max_body_size (post_max_size / upload_max_filesize 100M, max_input_vars=3000, max_input_nesting_level=64, max_file_uploads=20). Multipart admin uploads still work within those limits. memory_limit stays 512M (not -1). Nginx must not set PHP_VALUE / PHP_ADMIN_VALUE (docker/nginx/default.conf).
cp .env.docker.example .env docker compose up --build # App: http://localhost:8000 (COMPOSE_APP_URL overrides Herd APP_URL inside containers) # Reverb published on host :8081 (avoids Herd Reverb on :8080) # MinIO: docker compose --profile minio up --build # MySQL: docker compose --profile mysql -f compose.yaml -f compose.mysql.yaml up --build # MariaDB (host :3307): docker compose --profile mariadb -f compose.yaml -f compose.mariadb.yaml up --build # Keycloak OIDC QA: docker compose -f compose.yaml -f compose.oidc.yaml --profile oidc up --build # → docker/keycloak/README.md (issuer http://localhost:8080/realms/externa)
Docker (quick production deploy)
Pull the multi-arch image from GHCR (ghcr.io/qiick-io/externa-core) — no local build:
cp .env.docker.quick.example .env # Set APP_KEY, DB_PASSWORD, APP_URL; for first admin set RUN_SEED=true + INITIAL_SUPER_ADMIN_* docker compose -f compose.quick.yaml up -d # App: http://localhost:8080 · health: GET /health/ready
Build-from-source prod path: compose.prod.yaml + .env.docker.prod.example. Managed DB/Redis overlay: compose.prod.managed.yaml. Multi-arch bake/push: docker-bake.hcl, ./scripts/docker-buildx.sh (CI publishes on v* tags).
First boot is gated: RUN_MIGRATIONS / RUN_SEED (entrypoint defaults both off; Compose samples enable migrate for bring-up; seed stays opt-in). After bootstrap set RUN_SEED=false.
Probes: GET /health/live, GET /health/ready (+ Laravel /up). Docs: Deploy with Docker · Installation · Deployment · Reverse proxy.
First login (local / dev only)
php artisan db:seed creates permissions, roles (super-admin, admin, reader, public), and a super admin from config/super_admin.php:
| Default | |
|---|---|
superadmin@example.com |
|
| Password | password |
Override with INITIAL_SUPER_ADMIN_* in .env before seeding. Local/dev only — change or remove before any shared or production deploy.
Product UI
Shots from the admin with demo content, in light and dark (they follow your GitHub theme). More in the docs.
Dashboard — content, activity and storage at a glance
Collection items — list, filters, and actions
Item editor — relations, media and rich text
File manager — folders, assets and tags
Assistant — answers and actions with your permissions
Chat — item and private threads
Users — roles, groups, and status
Roles — project access control
Important environment variables
Copy from .env.example and tune. Full reference: Environment variables.
| Group | Keys (curated) | Notes |
|---|---|---|
| App | APP_NAME, APP_ENV, APP_KEY, APP_DEBUG, APP_URL |
APP_URL must match how you browse. HTTP is fine for password login, TOTP 2FA, and the rest of the CMS. Passkeys / WebAuthn need a secure context: HTTPS, or http://localhost / http://*.localhost. Plain http://*.test hosts are not secure — browsers hide PublicKeyCredential. Local HTTPS: herd secure <site>, matching APP_URL=https://…, and HERD_SITE=<site> for Vite TLS detection. After switching back to HTTP (herd unsecure), clear http + https cookies for the site or browsers may keep Secure session cookies → 419 Page Expired on login. Docs: Passkeys. |
| Database | DB_CONNECTION (+ DB_* if not SQLite) |
Default sqlite. Also pgsql, mysql, mariadb — see Supported databases. |
| Session / cache | SESSION_DRIVER, CACHE_STORE |
Default database. |
| Queue | QUEUE_CONNECTION |
Full: redis + Horizon. Minimal: database + queue:listen. |
| Redis | REDIS_CLIENT, REDIS_HOST, REDIS_PORT, REDIS_PASSWORD |
Required for Horizon / Pulse redis ingest. |
| Broadcast / Reverb | BROADCAST_CONNECTION, REVERB_*, VITE_REVERB_* |
Full: reverb. Minimal: log (60s notification poll). Restart Vite after VITE_REVERB_* changes. |
| Pulse | PULSE_ENABLED, PULSE_INGEST_DRIVER, PULSE_* |
Prefer redis ingest + pulse:work. Disable with PULSE_ENABLED=false when not using Redis. |
| AI | AI_DEFAULT_PROVIDER, LOCAL_AI_URL, LOCAL_AI_MODEL, … |
Optional; defaults target a local OpenAI-compatible gateway. |
| Files | FILES_DISK, FILES_DUPLICATE_SYNC_MAX_BYTES, FILES_ZIP_*, AWS_*, GOOGLE_CLOUD_*, AZURE_STORAGE_* |
File manager disk: assets (local), s3 (S3/MinIO/R2), gcs (Google Cloud Storage), or azure (Azure Blob). Async zip / large duplicate need a queue worker. Zip archives stay on the shared local storage volume. |
See .env.example for every key and inline comments.
Optional services
| Service | When you need it |
|---|---|
| Horizon | Redis queues, dashboard Health Horizon cards, /horizon (super-admin). |
| Reverb + Echo | Live admin notifications, presence / avatar connection indicator. Prefer Herd Pro Reverb locally. |
| Pulse | Metrics for dashboard Health; full UI at /pulse. Needs ingest worker when using redis ingest. |
| Queue worker | Always needed for zip downloads, large/bulk duplicates, and collection imports — even on the minimal stack. |
Details: Redis · Horizon · Reverb · Pulse · Operations.
Tests and quality
Use Node 24 for frontend checks (nvm use).
composer test # config:clear + Pint --test + Pest php artisan test # Pest only composer lint # Pint (fix) composer lint:check # Pint --test composer ci:check # npm lint/format/types + composer test npm run lint:check npm run format:check npm run types:check
Pest browser tests live under tests/Browser/. See Testing.
Related projects
| Project | Role |
|---|---|
| Documentation | Product & ops docs |
| externa-bruno | Runnable Public CMS API + GraphQL requests |
| GitHub — externa-core | This repository |
License
MIT — see LICENSE.