Getting started
Project layout
externa-core follows a conventional Laravel application layout with domain folders for Actions, AI, Jobs, and Services, plus an Inertia React UI under resources/js. This page is a map of where to look when you change behavior.
Top-level tree
externa-core/
├── app/ # PHP application code
├── bootstrap/ # app.php, providers, cache
├── config/ # Laravel + Externa config
├── database/ # migrations, factories, seeders
├── public/ # web root, built assets, storage link
├── resources/
│ ├── css/
│ ├── js/ # React / Inertia / Wayfinder
│ └── views/ # Blade shell for Inertia
├── routes/ # web + domain route files
├── storage/ # logs, app files, frameworks, zips
├── tests/ # Pest Feature / Unit / Browser
├── artisan
├── composer.json
├── package.json
└── vite.config.* # Vite + Wayfinder plugin
Ignore vendor tooling directories (.agents, .cursor, etc.) unless you are working on AI coding assistants for the repo.
app/ — backend domains
| Path | Role |
|---|---|
app/Actions/ | Single-purpose action classes (e.g. Fortify-related actions under Actions/Fortify). |
app/Ai/ | Agent, tools, and AI support code for laravel/ai. |
app/Concerns/ | Shared PHP traits/concerns used across layers. |
app/Console/ | Artisan commands (cleanup, sync, AI sync sources, …). |
app/Enums/ | PermissionEnum, RoleEnum, FieldTypeEnum, FileTypeEnum, etc. |
app/Http/ | Controllers, middleware, form requests, API resources. |
app/Jobs/ | Queued work: duplicate files, prepare zips, import collections. |
app/Models/ | Eloquent models. |
app/Notifications/ | In-app / mail notifications. |
app/Providers/ | Service providers. |
app/Services/ | Domain services (files, collections, authorization). |
app/Support/ | Small helpers and support types. |
app/Traits/ | Model or cross-cutting traits. |
Models (app/Models)
Core domain models include:
User,UserGroupCollection,CollectionField,CollectionItem,CollectionItemValueFile,FileUpload,FileVersionAiChatAttachment,AiSyncSource
Spatie models (roles, permissions, activity, tags) are provided by packages; application code references them via config and enums.
HTTP layer (app/Http)
Controllers are grouped by product area:
app/Http/Controllers/
├── DashboardController.php
├── NotificationController.php
├── Admin/
│ ├── UserController.php
│ ├── UserGroupController.php
│ ├── RoleController.php
│ ├── PermissionController.php
│ ├── ActivityLogController.php
│ └── FileController.php
├── Collections/
│ ├── ContentCollectionController.php
│ ├── FieldController.php
│ └── ItemController.php
├── Ai/
│ ├── AiPageController.php
│ ├── AiChatController.php
│ ├── AiConversationController.php
│ ├── AiChatAttachmentController.php
│ ├── AiStatusController.php
│ ├── ImportJobStatusController.php
│ └── CollectionImportWebhookController.php
└── Settings/
├── ProfileController.php
└── SecurityController.php
Form requests mirror those areas under app/Http/Requests/{Admin,Ai,Collections,Settings,Notifications}. JSON/Inertia resources live under app/Http/Resources.
Middleware of note:
HandleInertiaRequests— shared props for the React shellEnsureUserHasPermission/EnsureCanManageFiles— permission gates beyond route middlewareHandleAppearance— UI appearance preferences
Route middleware also uses Spatie’s permission: alias extensively in the route files.
Services (app/Services)
| Path | Typical responsibility |
|---|---|
Services/FileService.php | File tree operations used by controllers/jobs. |
Services/FileTransformService.php | Transforms / derived file handling. |
Services/Authorization/EffectivePermissionResolver.php | Merges direct + group-inherited permissions/roles. |
Services/Collections/ | Collection/item domain logic extracted from controllers. |
Prefer extending a service or action over bloating controllers when adding behavior.
Jobs (app/Jobs)
| Job | Why it is queued |
|---|---|
DuplicateFilesJob | Folder/bulk/large duplicates. |
PrepareFilesZipJob | Async zip under storage/app/zips. |
ImportCollectionJob | CSV/remote imports driven by AI or HTTP. |
Requires QUEUE_CONNECTION=database (default) and a running worker — see Installation.
AI (app/Ai)
app/Ai/
├── Agents/
│ └── AppAssistant.php # Primary in-app assistant
├── Tools/ # Tool classes invoked by the agent
│ ├── ManageCollections.php
│ ├── ManageCollectionItems.php
│ ├── QueryCollectionItems.php
│ ├── SearchSimilarCollectionItems.php
│ ├── ExportCollection.php
│ ├── ImportCollectionCsv.php
│ ├── ImportRemoteJson.php
│ ├── GetImportJobStatus.php
│ ├── ManageFiles.php
│ ├── ManageUsers.php
│ ├── ManageGroups.php
│ ├── ManageRoles.php
│ ├── ManageAiSyncSources.php
│ ├── QueryActivityLogs.php
│ ├── RollbackLastAiTurn.php
│ └── ExtractPdfText.php
├── Concerns/ # Permissions, import helpers, logging
└── Support/ # URL validation, JSON decode, activity helpers
Tools should keep using ChecksAiPermissions (and related concerns) so the assistant cannot outrank the signed-in user. Product docs: AI assistant model.
Enums (app/Enums)
Source of truth for seeded roles/permissions and field/file typing. Changing PermissionEnum usually means running the sync/seed path so the database matches — see console commands under app/Console/Commands (e.g. permission sync).
Routes (routes/)
Application HTTP routes are split by domain and composed from web.php:
| File | Contents |
|---|---|
routes/web.php | Home redirect, dashboard, notifications; requires the files below. |
routes/admin.php | Users, groups, roles, permissions, activity logs, file manager. |
routes/settings.php | Profile / security (Fortify-backed settings UI). |
routes/collections.php | Collections, fields, items. |
routes/ai.php | AI pages, chat, conversations, attachments, status, webhooks, import status. |
routes/console.php | Schedule / console route definitions. |
Authenticated groups typically use auth + verified, then per-route permission:… middleware with PermissionEnum values.
Note
When adding a feature, add the route in the matching domain file, a controller method, a form request if input is non-trivial, and an Inertia page under resources/js/pages/…. Generate or refresh Wayfinder types so the frontend can call named routes safely.
Config (config/)
Externa-specific and heavily used package configs:
| File | Purpose |
|---|---|
config/ai.php | Default provider, local gateway, embeddings/MCP flags, remote import hosts, daily limit, webhook token. |
config/collections.php | Seed/fallback for content locales until project settings are saved. |
config/files.php | Duplicate sync threshold, zip TTL and max size. |
config/super_admin.php | Initial super admin for CreateSuperAdminSeeder. |
config/permission.php | Spatie permission package. |
config/fortify.php | Auth features / Fortify. |
config/activitylog.php | Spatie activity log enablement and buffer. |
Standard Laravel configs (app.php, database.php, queue.php, session.php, cache.php, mail.php, filesystems.php, …) behave as usual.
Frontend (resources/js/)
| Path | Role |
|---|---|
pages/ | Inertia page components (one route → one page module). |
components/ | Shared UI (shell, sidebar, admin/collections/ai widgets, ui/). |
hooks/ | React hooks. |
lib/ | Client utilities. |
layouts/ | App layouts. |
interfaces/ / types/ | TypeScript types. |
enums/ | Frontend enum mirrors where needed. |
actions/ | Client-side action helpers when used by the starter patterns. |
routes/ + wayfinder/ | Generated Wayfinder TypeScript for Laravel routes/actions. |
app.tsx / ssr.tsx | Inertia app entry (CSR / SSR). |
Page map
resources/js/pages/
├── dashboard.tsx
├── welcome.tsx
├── auth/ # Login, register, passwords, 2FA, …
├── settings/ # Profile, security, appearance
├── admin/
│ ├── users/
│ ├── groups/
│ ├── roles/
│ ├── permissions/
│ ├── files/
│ └── activity-logs/
├── collections/
│ ├── collections/
│ └── items/
└── ai/ # Chat / assistant UI
Component folders often mirror these domains (components/admin, components/collections, components/ai, …).
Wayfinder
Externa uses Laravel Wayfinder with @laravel/vite-plugin-wayfinder. Named routes and controller actions are exposed as typed helpers under resources/js (commonly resources/js/routes and resources/js/wayfinder, generated on build/dev).
Workflow:
- Define or change a route in
routes/*.php. - Run Vite (
npm run dev/composer run dev) or the project’s Wayfinder generation step so TS clients update. - Import the generated helper in the page/component instead of hard-coding URL strings.
Warning
Do not hand-edit generated Wayfinder output. Change PHP routes/controllers and regenerate.
Database (database/)
database/
├── migrations/ # Schema (users, permissions, collections, files, AI, jobs, …)
├── factories/ # Model factories for tests
└── seeders/
├── DatabaseSeeder.php
├── PermissionSeeder.php
├── RoleSeeder.php
└── CreateSuperAdminSeeder.php
Seed order: permissions → roles → super admin. See Installation.
Storage and public assets
| Location | Use |
|---|---|
storage/app/ | Private app files; prepared zips under a zips directory per config/files.php. |
storage/app/public | Public disk files; must be linked via php artisan storage:link. |
public/build | Vite-built assets after npm run build. |
Tests (tests/)
Pest 4 layout:
tests/
├── Pest.php
├── TestCase.php
├── Feature/
│ ├── Admin/
│ ├── Ai/
│ ├── Auth/
│ ├── Authorization/
│ ├── Collections/
│ ├── Settings/
│ ├── DashboardTest.php
│ ├── NotificationsTest.php
│ └── …
├── Unit/
│ └── Collections/ # and other unit suites
└── Browser/
└── FileManagerBrowserTest.php # Playwright-backed browser tests
Run:
php artisan test
composer test # clears config, runs Pint check, then tests
composer ci:check # lint/format/types + tests (heavier)
Mirror feature folders when adding coverage (e.g. new AI tool → tests/Feature/Ai/…).
Console commands (app/Console/Commands)
Operational commands include (names may be prefixed — check php artisan list):
- Permission sync from enums
- Cleanup expired file zips
- Cleanup stale file uploads
- Cleanup AI chat attachments
- Run AI sync sources
Scheduling, if any, is wired through routes/console.php / the application scheduler — see Queues & scheduler.
Where to change what (cheat sheet)
| You want to… | Start here |
|---|---|
| Add an admin HTTP endpoint | routes/admin.php → Http/Controllers/Admin → pages/admin/… |
| Change collection validation | Http/Requests/Collections + enums/services |
| Adjust effective permissions | EffectivePermissionResolver + policies/middleware + seed enums |
| Add an AI capability | New class in app/Ai/Tools registered on AppAssistant |
| Tune zip/duplicate limits | config/files.php / FILES_* env |
| Change default AI provider | config/ai.php / AI_* and LOCAL_AI_* |
| Alter first seeded login | config/super_admin.php / INITIAL_SUPER_ADMIN_* |
| Fix shared Inertia props | HandleInertiaRequests |
| Add a queued workflow | app/Jobs + ensure worker in Installation |
Related docs
- Architecture — request lifecycle and layering
- Environment variables —
.envreference - Routing overview — HTTP surface summary
- Testing — conventions for Pest and browser tests