A modern admin dashboard built with Laravel 13, Vue 3, TypeScript, and Inertia.js. It provides a clean, responsive interface with role-based access control, authentication via Laravel Fortify, two-factor authentication and passkeys (provided by Laravel Passkeys), and a fully typed frontend powered by Vite and Tailwind CSS v4.
- Requirements
- Tech Stack
- Architecture
- Frontend Structure
- Multitenancy
- Billing
- Backups
- Audit Log
- Installation
- Roles and Permissions
- Configuration
- Running the Application
- Testing
- Code Quality
- Scripts Reference
- Generated Frontend Routes
- Local Quality Checks
- Further Reading
- Security
- Contributing
- Licence
- Funding
- PHP 8.3 or higher
- Composer
- Node.js 22.12 or higher
- npm
- A supported database: MySQL 8 or PostgreSQL 16 (both run in CI). SQLite is for local development only
Backend
- Laravel 13 -- PHP framework
- Laravel Fortify -- Authentication backend
- Laravel Sanctum -- API token authentication
- Laravel Wayfinder -- Named route generation for TypeScript
- Spatie Laravel Permission v7 -- Role and permission management
- Pest PHP -- Testing framework
- Laravel Cashier -- Stripe subscription billing
- Spatie Laravel Multitenancy -- Organisation-based tenancy
- Spatie Laravel Backup -- Database and file backups
- Log Viewer -- Browse application logs
- Laravel DomPDF -- PDF generation
- Mews Purifier -- HTML sanitisation for rich text
- Larastan -- Static analysis
Frontend
- Vue 3 with Composition API
- TypeScript
- Inertia.js -- Server-driven SPA
- Tailwind CSS v4
- Reka UI -- Headless UI components
- Lucide Vue Next -- Icon library
- Vite 8 -- Frontend build tool
Each resource follows the same service-oriented layout:
| Class | Responsibility |
|---|---|
ActiveCheckerService |
Whether a record can be modified, restored or force deleted |
CreatorService / UpdaterService |
Create and update, stamping audit columns |
DeleterService / RestorerService |
Soft delete and restore |
DataPreparationService |
Normalise validated input before persistence |
FilterService / SortingService |
Allow-listed filters and sort columns |
QueryService |
Build and paginate the index query |
FormatterService |
Shape data for Inertia props |
PolicyAuthorisationService |
Permission checks used by policies |
ManagementService |
Orchestrates the above for controllers |
Shared App\Actions\*Resource classes wrap transactions. Every state change is written to the audit log through AuditLogService.
Scaffold a new module with:
php artisan make:model {Model} -a
php artisan make:request Import{Model}Request
php artisan make:service {PluralModel}/{ServiceName}For example, php artisan make:service Users/CreatorService.
After scaffolding, add the module's permissions to RolePermissionSeeder, register its policy in AppServiceProvider, and run php artisan wayfinder:generate --with-form.
Pages live in resources/js/pages/{Module}/, with reusable pieces in resources/js/pages/{Module}/components/.
resources/js/pages/Users/
Index.vue
Create.vue
Edit.vue
Show.vue
components/
UserForm.vue
UserBasicDetailsForm.vue
UserRoleDetailsForm.vue
UserDateDetailsForm.vue
UserBasicDetails.vue
UserRoleDetails.vue
UserDateDetails.vue
UserAuditDetails.vue
Create.vueandEdit.vueboth renderUserForm.vue, which composes the smaller form components. Each form component takes anerrorsprop and onedefineModelper field.Show.vuecomposes the read-only detail components.- Form-only types (for example
UserFormData) are declared inside the form component.resources/js/types/index.tsholds model shapes only. - Route helpers come from Laravel Wayfinder (
@/routes/...), not Ziggy.
Data is scoped to an organisation using Spatie Laravel Multitenancy.
Organisationis the tenant model. Users belong to organisations through theorganisation_userpivot, which carries an organisation role (owner,admin,member).- A user selects or switches organisation from the organisation switcher. Requests made without a current organisation are redirected to the organisation selection page.
- Tenant-scoped models use the
App\Traits\BelongsToOrganisationtrait and are listed inconfig('organisations.scoped_models'). Tables that are intentionally shared are listed inconfig('organisations.central_tables'). - File storage paths are prefixed with
organisations/{id}/, cache keys include the organisation id, and queued jobs are tenant aware. - Organisation membership is a hard boundary. Organisation roles are separate from the application-wide roles described in Roles and Permissions.
Isolation is covered by tests/Feature/Tenancy/TenantHttpIsolationTest.php. See CONTRIBUTING.md for the checklist to follow when adding a tenant-scoped module.
Subscriptions use Laravel Cashier with Stripe.
Add your keys to .env:
STRIPE_KEY=
STRIPE_SECRET=
STRIPE_WEBHOOK_SECRET=Forward webhooks to your local application with the Stripe CLI:
stripe listen --forward-to {APP_URL}/stripe/webhookCopy the signing secret it prints into STRIPE_WEBHOOK_SECRET. Use Stripe's test card 4242 4242 4242 4242 with any future expiry date and any CVC.
Plans are seeded by PlanSeeder and managed from the Plans module.
Backups use Spatie Laravel Backup. Configure destinations in config/backup.php.
# Create a backup
php artisan backup:run
# List existing backups
php artisan backup:list
# Remove old backups according to the retention policy
php artisan backup:cleanTo restore, unzip the backup archive, restore the files you need, and import the database dump:
mysql -u {user} -p {database} < db-dumps/{dump-file}.sqlTest a restore on a non-production environment before you rely on it.
Every state change (create, update, delete, restore) is recorded through AuditLogService.
- Each entry stores the action, the acting user, the affected model and a before and after snapshot.
- Actions are integer constants on the
Logmodel. - Entries are signed with an HMAC using
AUDIT_LOG_HMAC_KEY, so tampering with stored rows can be detected. Set a unique key in every environment and never commit it.
Clone the repository and install dependencies:
git clone https://github.com/MattYeend/Dashboard.git
cd DashboardYou can run the full setup in a single command:
composer run setupThis will:
- Install Composer dependencies
- Copy
.env.exampleto.env(if not already present) - Generate an application key
- Run database migrations and seed roles, permissions and reference data (the seeded users' shared password is printed once if you have not set
SEED_USER_PASSWORD) - Install npm dependencies
- Build frontend assets
Alternatively, run each step manually:
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate --seed
npm install
npm run buildSeeded users:
UserSeedercreates local development accounts that share one password. SetSEED_USER_PASSWORDin.envto choose it, or leave it empty and a random password is generated and printed once when the seeder runs.UserSeederrefuses to run in production, so never seed production with development data.
This project uses Spatie Laravel Permission v7 to manage roles and permissions. It supports assigning roles and permissions directly to users, teams-based permissions, wildcard permissions, and Blade directives for view-level access control.
For full usage documentation, refer to the Spatie Laravel Permission v7 docs.
RolePermissionSeeder creates the following roles. It is the authoritative source for the permission set of each role.
| Role | Summary |
|---|---|
| Super Admin | Every ability. Policy checks are bypassed by a Gate::before hook |
| Admin | Full management of modules, plans, billing and organisation membership |
| Manager | Create, view and edit access to most modules, without billing management |
| Editor | Scoped subset defined in RolePermissionSeeder |
| Viewer | Scoped subset defined in RolePermissionSeeder |
| Moderator | Scoped subset defined in RolePermissionSeeder |
| Support | Scoped subset defined in RolePermissionSeeder |
| Analyst | Scoped subset defined in RolePermissionSeeder |
| User | Scoped subset defined in RolePermissionSeeder |
| Guest | Scoped subset defined in RolePermissionSeeder |
Copy the example environment file and update it with your own values:
cp .env.example .envKey variables to configure:
| Variable | Description |
|---|---|
APP_NAME |
The name displayed in the application |
APP_URL |
The base URL of your application |
DB_CONNECTION |
Database driver (mysql, pgsql, sqlite) |
DB_HOST |
Database host |
DB_DATABASE |
Database name |
DB_USERNAME |
Database username |
DB_PASSWORD |
Database password |
SEED_USER_PASSWORD |
Optional. Shared password for seeded development users. Leave empty to generate a random one |
To start all services concurrently (web server, queue worker, log watcher, and Vite dev server):
composer run devThis runs the following in parallel:
php artisan serve-- Laravel development serverphp artisan queue:listen-- Queue workerphp artisan pail-- Log viewernpm run dev-- Vite HMR dev server
To build frontend assets for production:
npm run buildTests are written using Pest PHP.
Run the full test suite:
php artisan testOr via Composer:
composer run testThis also clears the config cache and runs the linter before executing tests.
The project uses several tools to maintain code quality and consistency.
PHP linting with Pint:
# Fix violations
composer run lint
# Check only (no fixes applied)
composer run lint:checkStatic analysis with Larastan:
composer run analyseRun every CI check locally:
composer run ci:checkFrontend linting with ESLint:
# Fix violations
npm run lint
# Check only
npm run lint:checkCode formatting with Prettier:
# Format resources/
npm run format
# Check only
npm run format:checkTypeScript type checking:
npm run types:check| Command | Description |
|---|---|
composer run setup |
Full project setup |
composer run dev |
Start all dev services |
npm run dev |
Start Vite dev server |
npm run build |
Build frontend for production |
npm run lint |
Fix ESLint violations |
npm run lint:check |
Check ESLint violations (no fixes applied) |
npm run format |
Format with Prettier |
npm run format:check |
Check formatting (no fixes applied) |
npm run types:check |
Check TypeScript types |
php artisan test |
Run the full Pest PHP test suite |
php artisan make:service {Model}/{ServiceName} |
Scaffold a service, for example Companies/CreatorService |
composer run analyse |
Run Larastan static analysis |
composer run ci:check |
Run the PHP checks CI runs (lint, analysis, tests) |
composer run lint:check |
Check PHP style with Pint (no fixes applied) |
composer audit |
Check PHP dependencies for known vulnerabilities |
npm audit |
Check JavaScript dependencies for known vulnerabilities |
After changing Laravel routes or controllers, regenerate frontend route/action files:
php artisan wayfinder:generate --with-form
npm run types:checkGenerated files live in:
resources/js/actionsresources/js/routes
Run before opening a PR:
composer test
npm run lint:check
npm run types:check
npm run buildTo install for production, skip development packages (Laravel Brain, Laradocs and test tooling):
composer install --no-dev --optimize-autoloaderMore artisan commands can be found here
Report vulnerabilities privately as described in SECURITY.md. Do not open public issues for security problems.
Contributions are welcome. Please read CONTRIBUTING.md first, then open an issue or submit a pull request. Ensure all tests pass and code style checks succeed before submitting.
- Fork the repository
- Create a feature branch (
git checkout -b feature/{issue-number}-{short-name}) - Commit your changes (
git commit -m "#{issue} {Message}") - Push to the branch (
git push origin feature/{issue-number}-{short-name}) - Open a Pull Request
This project is licenced under the MIT Licence.
If you find this project useful and would like to support its development, you can do so through the following:
