Directory Structure
The backend has three top-level source directories that matter most: src/Domain/ for business logic, src/Presentation/ for HTTP and view layers, and app/ for legacy and framework glue. Knowing which belongs to which saves a lot of time when you go hunting for a file.
This page walks each directory at a level of detail that is useful for orienting yourself. For the rules that govern what lives where, see DDD overview and the rest of Key Concepts.
src/Domain/
One subdirectory per bounded context. There are roughly forty of them — the domain glossary lists each one with a description. Inside each domain you will find the same core directories, with a few optional ones that only appear when the domain needs them:
src/Domain/Order/
├── Actions/ # one class per business operation
│ └── StateMachine/ # state machine + guards (when the domain uses one)
│ └── Conditions/ # one BaseCondition subclass per guard rule
├── Contracts/ # interfaces — the domain's public API
├── Services/ # Contract implementations; delegate to Actions
├── DataTransferObjects/ # *Data classes for inter-layer transport
├── Enums/ # domain-owned enums (statuses, types)
├── Exceptions/ # domain-specific exception classes
├── Jobs/ # queued work owned by the domain
├── Models/
│ ├── Entities/ # Eloquent models
│ ├── Scopes/ # scopeFilterBy traits
│ ├── Abilities/ # per-instance permission objects (state machine wrappers)
│ ├── Features/ # domain-scoped Pennant feature flags (optional)
│ └── Traits/ # model-only shared traits
├── Observers/ # Eloquent observers (optional)
├── Providers/ # the domain's ServiceProvider
└── Traits/ # domain-level traits for Services/Actions (optional)
StateMachine/, Observers/, Traits/, and Models/Features/ are only present in domains that use them — most domains do not have all four. Cross-domain reads and writes go through app(SomeContract::class) — never through another domain's Action or Model. Same-domain code imports its sibling Actions directly. Domain boundaries covers this in detail.
src/Presentation/
One subdirectory per control panel — Admin, Business, Common, Customer, Driver, Owner, Pdf, Shared. Each panel has its own controllers, requests, resources, routes, and providers:
src/Presentation/Admin/
├── Controllers/
├── Enums/
├── Providers/
├── Requests/
├── Resources/
├── Routes/
│ └── web.php # or api.php for API panels
├── Rules/
├── Traits/
├── ViewData/ # view-model helpers passed to Blade templates
└── Views/ # Blade — only Admin and Pdf carry views
A controller belongs to exactly one panel. If two panels need the same behaviour, the shared logic moves into a domain Service, and both controllers call that Service. Cross-panel inheritance is forbidden — it produces routes you can't reason about.
The HTTP shape varies by panel:
- Admin is server-rendered (Blade + redirects). Use a
VieworRedirectResponsereturn type and flash messages on success. - Business, Customer, Driver, Owner are JSON APIs authenticated with Sanctum tokens. Use the
ResponseBuilderfromres()->data(...)so the standard envelope wraps every response. - Pdf renders binary PDFs via mPDF.
- Common and Shared hold helpers, abstract base classes, constants, and rules used by the other panels.
See controllers for the SSR/API split in detail.
app/
Standard Laravel app/ directory — kept for framework glue and legacy code that has not been migrated to the DDD layout. New business code does not land here; it goes in src/Domain/.
Legacy models that predate the DDD migration live as loose .php files directly in app/ (e.g. app/User.php, app/Order.php). They have not been moved into a subfolder. New entities go in src/Domain/{D}/Models/Entities/.
app/
├── *.php # legacy model files (loose in app/ root)
├── Console/
│ └── Commands/ # artisan commands
├── Constants/ # application-wide constant classes
├── Exceptions/ # the global exception handler
├── Http/
│ ├── Controllers/ # legacy controllers; new code goes in src/Presentation/
│ ├── Middleware/ # HTTP middleware
│ └── Requests/ # legacy form requests
├── Packages/ # in-tree packages (CpMenu, Localisation, …)
├── Providers/ # framework-level service providers
├── Traits/ # application-wide shared traits
├── Validators/ # custom validation rule classes
└── helpers.php # global helpers — res(), isWinch(), projectFlavor()
A few specific files are worth knowing by name:
app/helpers.phpexportsres()(the API response builder),isWinch()/isSaas()/projectFlavor()(the flavor helpers), and a handful of other utilities.app/Packages/Menus/builds the admin sidebar from a config-driven menu array.app/Packages/Localisation/handles locale switching and translation loading.
When you touch a legacy file, read refactoring legacy before deciding whether to move it.
bootstrap/
Laravel 12 trims the historical app/Http/Kernel.php, app/Console/Kernel.php, and routing-registration code into a single bootstrap/app.php. Middleware, exception handling, and routing all configure declaratively here:
return Application::configure(basePath: dirname(__DIR__))
->withRouting(/* ... */)
->withMiddleware(/* ... */)
->withExceptions(/* ... */)
->create();
bootstrap/providers.php lists every service provider — both framework-level providers and the per-domain providers (Domain\Order\Providers\OrderServiceProvider, etc.). Adding a new domain means adding its provider to this file; forgetting to do so is the most common cause of BindingResolutionException for a freshly written Contract.
database/
database/
├── factories/ # model factories for tests and seeders
├── migrations/ # anonymous-class migrations
├── seeders/ # development seeders
└── sql/ # raw SQL scripts (bulk seeds, one-off data fixes)
Migrations follow modern Laravel patterns: anonymous classes, foreignIdFor()->constrained(), no speculative indexes. See migrations.
lang/
lang/
├── en/
│ ├── dashboard.php
│ ├── pages.php
│ ├── permissions.php
│ ├── inputs.php
│ └── …
├── ar/
│ └── (same files)
├── hi/
│ └── (same files)
└── ur/
└── (same files)
Every translation key must exist in all active locales. The pre-push hook (php artisan translations:check --changed) enforces this — pushes fail if the files you're pushing reference a key missing from any locale.
tests/
tests/
├── Feature/ # HTTP-level integration tests
├── Unit/ # isolated unit tests (Actions, DTOs, helpers)
├── Pest.php # global Pest configuration
└── TestCase.php
See testing for how to write a test against the DDD layout.
config/
Standard Laravel config. The files most often touched in domain work:
config/app.php—'flavor'key for the currentProjectFlavor.config/features.php— Pennant flag defaults.config/cp-menu.php— sidebar configuration includinggroup_features/route_features/group_flavors/route_flavors.
What to read next
- DDD overview — the rationale behind the
Domain/Presentationsplit. - Your first PR — apply all of the above to a real change.
Your First PR
This chapter walks through a complete, realistic change end-to-end: adding a `Note` concept to a `Customer` domain. The goal is not the feature itself — it's to see every file the pattern touches and the order in which they are created.
Basics
How the codebase splits business logic from HTTP concerns, what lives in each subdirectory of a domain, and where to put new code.