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

PathRole
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, UserGroup
  • Collection, CollectionField, CollectionItem, CollectionItemValue
  • File, FileUpload, FileVersion
  • AiChatAttachment, 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 shell
  • EnsureUserHasPermission / EnsureCanManageFiles — permission gates beyond route middleware
  • HandleAppearance — UI appearance preferences

Route middleware also uses Spatie’s permission: alias extensively in the route files.

Services (app/Services)

PathTypical responsibility
Services/FileService.phpFile tree operations used by controllers/jobs.
Services/FileTransformService.phpTransforms / derived file handling.
Services/Authorization/EffectivePermissionResolver.phpMerges 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)

JobWhy it is queued
DuplicateFilesJobFolder/bulk/large duplicates.
PrepareFilesZipJobAsync zip under storage/app/zips.
ImportCollectionJobCSV/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:

FileContents
routes/web.phpHome redirect, dashboard, notifications; requires the files below.
routes/admin.phpUsers, groups, roles, permissions, activity logs, file manager.
routes/settings.phpProfile / security (Fortify-backed settings UI).
routes/collections.phpCollections, fields, items.
routes/ai.phpAI pages, chat, conversations, attachments, status, webhooks, import status.
routes/console.phpSchedule / 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:

FilePurpose
config/ai.phpDefault provider, local gateway, embeddings/MCP flags, remote import hosts, daily limit, webhook token.
config/collections.phpSeed/fallback for content locales until project settings are saved.
config/files.phpDuplicate sync threshold, zip TTL and max size.
config/super_admin.phpInitial super admin for CreateSuperAdminSeeder.
config/permission.phpSpatie permission package.
config/fortify.phpAuth features / Fortify.
config/activitylog.phpSpatie 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/)

PathRole
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.tsxInertia 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:

  1. Define or change a route in routes/*.php.
  2. Run Vite (npm run dev / composer run dev) or the project’s Wayfinder generation step so TS clients update.
  3. 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

LocationUse
storage/app/Private app files; prepared zips under a zips directory per config/files.php.
storage/app/publicPublic disk files; must be linked via php artisan storage:link.
public/buildVite-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 endpointroutes/admin.phpHttp/Controllers/Adminpages/admin/…
Change collection validationHttp/Requests/Collections + enums/services
Adjust effective permissionsEffectivePermissionResolver + policies/middleware + seed enums
Add an AI capabilityNew class in app/Ai/Tools registered on AppAssistant
Tune zip/duplicate limitsconfig/files.php / FILES_* env
Change default AI providerconfig/ai.php / AI_* and LOCAL_AI_*
Alter first seeded loginconfig/super_admin.php / INITIAL_SUPER_ADMIN_*
Fix shared Inertia propsHandleInertiaRequests
Add a queued workflowapp/Jobs + ensure worker in Installation
Previous
Installation