My Laravel Skeleton Project: An Opinionated Starting Point
After building several SaaS applications, I noticed I was repeatedly implementing the same patterns: strict code quality rules, action-based architecture, Stripe subscriptions with pricing pages, passkey and two-factor authentication and type-safe communication between Laravel and React. This skeleton project captures those patterns so I can start new projects with proven, production-ready code instead of rebuilding from scratch.
Updated November 2026 to reflect the current version of the skeleton.
Why This Exists
Most Laravel starters give you authentication and maybe an admin panel. But real SaaS applications need more: payment processing, subscription management, type-safe data flow between backend and frontend, a production deployment story and strict code quality enforcement. This skeleton includes all of that, configured and working together.
The Stack
- Backend: Laravel 13 on PHP 8.5, served by Laravel Octane with FrankenPHP
- Frontend: React 19, TypeScript 7 and Inertia 3 with server-side rendering, bundled with Vite 8 and styled with Tailwind CSS 4 and shadcn/ui
- Admin panel: Filament 5
- Authentication: Laravel Fortify with passkeys and two-factor authentication
- Payments: Laravel Cashier 16 with Stripe
- Real-time: Laravel Reverb and Laravel Echo
- AI: the official Laravel AI SDK (
laravel/ai) - Monitoring: Laravel Nightwatch
- Database: PostgreSQL 18
Strict Code Quality by Default
The project enforces code quality at the highest level:
- PHPStan/Larastan at max level: static analysis catches type errors before runtime
- Architecture tests with Pest 5: strict types everywhere plus Pest's Laravel and security presets, enforced automatically
- Rector: automated refactoring and modernization, including the Laravel-specific rule sets
- TypeScript strict mode: frontend code is equally strict about types
- Laravel Pint, oxlint and oxfmt: consistent formatting and linting across PHP and TypeScript (oxlint and oxfmt replaced ESLint and Prettier)
This isn't optional tooling you might enable later. It's baked into the composer lint and composer test commands and runs in CI on every pull request. If it doesn't pass, it doesn't ship. On top of that, composer test:type-cov checks that every parameter, property and return value has a type, with a 100% minimum.
Action Pattern Throughout
The codebase heavily relies on the Action pattern for business logic. Instead of fat controllers or service classes that do everything, each action is a single-purpose class that's testable and explicit about its dependencies. Combined with PHPStan's max level analysis, you know exactly what goes in and what comes out.
End-to-End Type Safety
One of the biggest wins is type safety from backend to frontend, and it now covers both data and routes:
- Data: Spatie's Laravel Data and TypeScript Transformer turn PHP Data objects marked with
#[TypeScript]into matching TypeScript types. Runphp artisan typescript:transformand the frontend knows exactly what shape every prop has. - Routes: Laravel Wayfinder generates typed functions for every controller action and named route, so the pricing page imports
storefrom the generated checkout routes instead of hardcoding/checkout-store/.... Rename a route or change its parameters and TypeScript tells you everywhere it broke.
No manual synchronization, no runtime surprises when the API shape changes.
Authentication
Authentication is handled by Laravel Fortify and managed from the Settings, Security page:
- Email and password login, registration, password reset and email verification
- Passkeys (WebAuthn): register Face ID, Touch ID, Windows Hello or a security key, then sign in without a password
- Two-factor authentication: TOTP with recovery codes
Stripe Integration Out of the Box
The skeleton includes complete Stripe subscription functionality:
- Pricing page: displays the active subscription plans synced from Stripe
- Checkout flow: creates Stripe Checkout sessions through Laravel Cashier, with promotion codes enabled
- Webhook handling: Cashier processes Stripe events to keep subscriptions in sync
- Subscription management: subscribed users are sent to the Stripe billing portal to change or cancel their plan
Important limitation: the implementation uses a flat pricing structure, one product has one price. It's designed exclusively for subscriptions, not one-time payments. This covers most SaaS use cases but won't work if you need complex pricing tiers or mixed billing models.
Filament Admin Panel
Filament 5 comes pre-configured with:
- User management: full CRUD operations with proper authorization
- Stripe products: a one-click action syncs products and prices from your Stripe account, and you can view and edit them without leaving your application
Production Deployment
The multi-stage Dockerfile builds three images: a FrankenPHP web image with the built assets, a slim PHP image for queue workers and the scheduler, and an SSR image for Inertia's server-side rendering. The docker-compose.yaml runs the web app, queue workers, scheduler, SSR server and the Nightwatch agent, with hardened container defaults and Traefik labels for Coolify. I wrote about this setup in detail in How I deploy Laravel apps using Coolify and Docker.
Local Development in Containers
This is the biggest change since the first version. Local development no longer uses Laravel Herd. The whole stack runs in Docker on OrbStack, and no PHP, Composer, Bun or Node runs directly on the Mac. Everything goes through a single bin/dev script:
bin/dev upbuilds the images and starts the stack, served athttps://laravel-skeleton.orb.localwith trusted certificatesbin/dev composer ...,bin/dev bun ...andbin/dev artisan ...run those tools inside containersbin/dev testruns the test suites against PostgreSQL- every git worktree gets its own isolated stack with its own URL, containers and volumes
The main reason for the move is supply-chain security:
- Secrets never touch the disk.
bin/devreads them from Bitwarden Secrets Manager and passes them only to the containers that need them. Composer and Bun run with a throwawayAPP_KEY. - The queue worker is off by default, because it can call external services and paid APIs.
- Files that run on the Mac are mounted read-only inside the containers (
.git,bin, the Docker and Compose files, agent configuration), so a malicious package can't plant a git hook or change what gets started. - Package policies: Bun refuses npm versions younger than seven days, only explicitly trusted packages may run install scripts, and Composer blocks packages with security advisories or a malware flag.
Built for AI-Assisted Development
The repository ships with guidelines for AI coding agents. CLAUDE.md lists the exact package versions, and path-scoped rules in .claude/rules/ load automatically depending on which files the agent is working on (PHP, tests, frontend, Filament, Octane constraints and more). Laravel Boost provides the MCP tools, and domain skills cover Pest, Tailwind, Wayfinder, Inertia, Fortify, Cashier and the AI SDK.
Conclusion
This skeleton isn't a tutorial project. It's production code I use to start all of my applications. The strict typing, action pattern, complete Stripe integration and containerized workflow mean I can go from git clone to working on actual features within minutes instead of days. If you build subscription-based SaaS apps with Laravel and React, this might be the starter kit for you.
Check it out at github.com/antonL95/laravel-skeleton.