Laravel Project Folder Structure
Annotated directory tree for the Law Firm Laravel application. The structure follows a
domain-driven layout inside app/ rather than Laravel's default flat structure.
This prevents the models, controllers, and services directories from growing to hundreds
of unrelated files.
law-firm/
│
├── app/
│ │
│ ├── Filament/ # Filament panels and resources
│ │ ├── Admin/ # Internal staff panel (/admin)
│ │ │ ├── Pages/ # Custom full-page Filament pages
│ │ │ ├── Resources/ # One resource per major model
│ │ │ │ ├── MatterResource/
│ │ │ │ │ ├── MatterResource.php
│ │ │ │ │ ├── Pages/
│ │ │ │ │ │ ├── ListMatters.php
│ │ │ │ │ │ ├── CreateMatter.php
│ │ │ │ │ │ └── EditMatter.php
│ │ │ │ │ └── RelationManagers/
│ │ │ │ │ ├── TimeEntriesRelationManager.php
│ │ │ │ │ ├── InvoicesRelationManager.php
│ │ │ │ │ └── DocumentsRelationManager.php
│ │ │ │ ├── ClientResource/
│ │ │ │ ├── InvoiceResource/
│ │ │ │ ├── UserProfileResource/
│ │ │ │ └── ... # One directory per resource
│ │ │ └── Widgets/ # Dashboard widgets
│ │ │ ├── BillableHoursWidget.php
│ │ │ ├── OutstandingInvoicesWidget.php
│ │ │ └── ActiveMattersWidget.php
│ │ │
│ │ ├── Client/ # Client portal panel (/portal)
│ │ │ ├── Pages/
│ │ │ └── Resources/
│ │ │ ├── ClientMatterResource/
│ │ │ ├── ClientInvoiceResource/
│ │ │ └── ClientDocumentResource/
│ │ │
│ │ └── Partner/ # Partner portal panel (/partner)
│ │ ├── Pages/
│ │ └── Resources/
│ │ ├── PartnerMatterResource/
│ │ ├── PartnerTimeEntryResource/
│ │ └── PartnerBillResource/
│ │
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── Auth/
│ │ │ │ └── AuthController.php # Login/logout for non-Filament flows
│ │ │ ├── Api/ # REST API controllers (Sanctum)
│ │ │ │ ├── ProfileController.php
│ │ │ │ ├── MatterController.php
│ │ │ │ ├── ClientController.php
│ │ │ │ ├── DocumentController.php
│ │ │ │ ├── AppointmentController.php
│ │ │ │ ├── TimeEntryController.php
│ │ │ │ ├── InvoiceController.php
│ │ │ │ ├── NotificationController.php
│ │ │ │ └── CommentController.php
│ │ │ └── Webhook/
│ │ │ └── PaymentWebhookController.php
│ │ │
│ │ ├── Middleware/
│ │ │ ├── ResolveTenant.php # Sets current LawFirm from slug or token
│ │ │ └── EnforceActiveUser.php # Blocks soft-deleted / inactive users
│ │ │
│ │ └── Requests/ # Form Request validation classes
│ │ ├── StoreMatterRequest.php
│ │ ├── StoreTimeEntryRequest.php
│ │ └── ...
│ │
│ ├── Models/ # Eloquent models, grouped by domain
│ │ ├── Auth/
│ │ │ └── User.php # users table (was AUTH_USERS)
│ │ ├── Firm/
│ │ │ ├── LawFirm.php # law_firms — tenant anchor
│ │ │ ├── FirmUserProfile.php
│ │ │ ├── LawyerLicense.php
│ │ │ ├── OrgUnit.php
│ │ │ ├── Position.php
│ │ │ ├── ReportingLine.php
│ │ │ └── Specialty.php
│ │ ├── Matter/
│ │ │ ├── Matter.php # matters table (was CASES)
│ │ │ ├── MatterBudget.php
│ │ │ ├── MatterAttorney.php
│ │ │ ├── MatterClient.php
│ │ │ ├── MatterBillingSetting.php
│ │ │ └── ...
│ │ ├── Client/
│ │ │ ├── Client.php
│ │ │ ├── ClientContact.php
│ │ │ └── ClientUser.php
│ │ ├── Partner/
│ │ │ ├── PartnerFirm.php
│ │ │ ├── PartnerAttorney.php
│ │ │ ├── PartnerAttorneyUser.php
│ │ │ └── ...
│ │ ├── RBAC/
│ │ │ ├── Permission.php
│ │ │ ├── Role.php
│ │ │ ├── UserRoleAssignment.php
│ │ │ └── ResourceAccessGrant.php
│ │ ├── Billing/
│ │ │ ├── LawyerTimeEntry.php # append-only
│ │ │ ├── PartnerTimeEntry.php # append-only
│ │ │ ├── ExpenseEntry.php # append-only
│ │ │ ├── LawyerRatePlan.php
│ │ │ ├── BillingActivityCode.php
│ │ │ └── ...
│ │ ├── Invoicing/
│ │ │ ├── Invoice.php # append-only
│ │ │ ├── InvoiceLineItem.php # append-only
│ │ │ ├── Payment.php # append-only
│ │ │ ├── PaymentEvent.php # append-only
│ │ │ ├── CreditNote.php
│ │ │ └── ...
│ │ ├── HR/
│ │ │ ├── PerformanceCycle.php
│ │ │ ├── PerformanceReview.php
│ │ │ ├── BonusPlan.php
│ │ │ ├── BonusAward.php
│ │ │ └── ...
│ │ ├── Appointment/
│ │ │ ├── Appointment.php
│ │ │ ├── AppointmentAttendee.php
│ │ │ └── AppointmentEvent.php # append-only
│ │ └── Document.php
│ │
│ ├── Policies/ # Laravel Policies — one per major model
│ │ ├── MatterPolicy.php
│ │ ├── ClientPolicy.php
│ │ ├── DocumentPolicy.php
│ │ ├── InvoicePolicy.php
│ │ ├── TimeEntryPolicy.php
│ │ └── ...
│ │
│ ├── Services/ # Domain logic — kept separate from models
│ │ ├── Auth/
│ │ │ └── TenantResolverService.php
│ │ ├── Billing/
│ │ │ ├── RateResolutionService.php
│ │ │ └── InvoiceGeneratorService.php
│ │ ├── Document/
│ │ │ └── DocumentService.php
│ │ └── RBAC/
│ │ └── RbacCheckerService.php
│ │
│ ├── Jobs/ # Queued jobs dispatched to Horizon
│ │ ├── GenerateInvoicePdfJob.php
│ │ ├── FlushErpExportQueueJob.php
│ │ ├── SyncMeilisearchIndexJob.php
│ │ └── SendNotificationJob.php
│ │
│ ├── Observers/ # Eloquent observers
│ │ └── AppendOnlyObserver.php # Enforces immutability on financial models
│ │
│ ├── Providers/
│ │ ├── AppServiceProvider.php
│ │ ├── AuthServiceProvider.php # Policy registrations
│ │ └── Filament/
│ │ ├── AdminPanelProvider.php
│ │ ├── ClientPanelProvider.php
│ │ └── PartnerPanelProvider.php
│ │
│ └── Notifications/ # Laravel notification classes
│ ├── MatterAssignedNotification.php
│ ├── InvoicePaidNotification.php
│ └── AppointmentReminderNotification.php
│
├── config/
│ ├── app.php
│ ├── octane.php # Swoole worker count, warm-up classes
│ ├── filament.php
│ ├── sanctum.php
│ └── permission.php # Custom RBAC config (not spatie/permission)
│
├── database/
│ ├── migrations/ # One file per table, ordered by batch
│ │ ├── 0001_01_01_000000_create_users_table.php
│ │ ├── 0001_01_01_000001_create_law_firms_table.php
│ │ └── ... # See migration-strategy.md for full order
│ ├── seeders/
│ │ ├── DatabaseSeeder.php
│ │ ├── RolesAndPermissionsSeeder.php
│ │ ├── BillingActivityCodesSeeder.php
│ │ └── DemoFirmSeeder.php # Development demo data only
│ └── factories/
│ ├── UserFactory.php
│ ├── LawFirmFactory.php
│ ├── MatterFactory.php
│ └── ...
│
├── resources/
│ ├── views/
│ │ ├── pdf/
│ │ │ └── invoice.blade.php # Invoice PDF template
│ │ └── emails/ # Notification email templates
│ └── lang/
│
├── routes/
│ ├── web.php # Filament panel routes (auto-registered)
│ └── api.php # REST API routes (/api/v1/*)
│
└── tests/
├── Feature/
│ ├── Api/ # REST endpoint feature tests
│ ├── Admin/ # Admin API feature tests
│ └── Filament/ # Livewire panel tests
├── Unit/
│ ├── Services/ # Domain service unit tests
│ └── Models/ # Model observer and scope tests
└── Datasets/ # Shared Pest dataset files
Key Conventions
Models use domain namespaces, not a flat directory.
App\Models\Billing\LawyerTimeEntry instead of App\Models\LawyerTimeEntry. At ~96
models, the flat structure becomes unnavigable.
Filament resources mirror the model namespace.
App\Filament\Admin\Resources\MatterResource wraps App\Models\Matter\Matter. The
parallel naming makes the connection obvious.
Services contain all business logic. Controllers and Filament resources are thin. They validate input, call a service, and return a response. No business logic lives in controllers or models.
Observers are the only exception to the service rule.
The AppendOnlyObserver is registered directly on financial models because it must fire
on every write path — including writes that don't go through a service (e.g., direct
factory calls in tests).
Policies live flat, not namespaced. There are ~15 policies, which is manageable in a flat directory. Namespacing them by domain adds navigation overhead with no benefit at this scale.