Status: available for projects [email protected]
← Back to articles

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. Run php artisan typescript:transform and 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 store from 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 up builds the images and starts the stack, served at https://laravel-skeleton.orb.local with trusted certificates
  • bin/dev composer ..., bin/dev bun ... and bin/dev artisan ... run those tools inside containers
  • bin/dev test runs 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/dev reads them from Bitwarden Secrets Manager and passes them only to the containers that need them. Composer and Bun run with a throwaway APP_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.

Email me CV ↓