1. Introduction: Why Laravel for SaaS?
If you want to build a SaaS application with Laravel, you're making one of the smartest technical decisions available in 2026. Laravel has evolved from a simple MVC framework into a complete ecosystem for building production-grade software-as-a-service products. With over 78,000 GitHub stars and the most active PHP community, Laravel gives you everything you need to go from idea to revenue.
In this Laravel SaaS tutorial, we'll walk through every major component of building a SaaS application — from project setup and authentication to billing, multi-tenancy, admin panels, APIs, testing, and deployment. Each section includes working code examples you can use as a reference.
Here's why Laravel dominates SaaS development:
- First-party billing: Laravel Cashier handles Stripe and Paddle subscriptions out of the box.
- Built-in auth scaffolding: Breeze, Jetstream, and Fortify provide authentication, email verification, and 2FA.
- Queue system: Background jobs for emails, webhooks, and heavy processing.
- Ecosystem maturity: Filament for admin panels, Livewire for reactive UI, Pest for testing.
- Developer experience: Artisan CLI, Tinker REPL, Telescope debugger, and excellent documentation.
Let's build a SaaS with Laravel, step by step.
2. Planning Your SaaS Architecture
Before writing any code, you need to make architectural decisions that will shape your entire application. Here are the key questions:
- Tenancy model: Will users belong to teams/organizations, or is it single-user? Most B2B SaaS products need multi-tenancy.
- Billing provider: Stripe is the most popular choice, but Paddle handles tax compliance automatically, and LemonSqueezy is gaining traction for its simplicity.
- Frontend approach: Blade + Livewire for server-rendered, Inertia.js for Vue/React SPAs, or a headless API for a separate frontend.
- Database strategy: Single database with tenant scoping (most common), or database-per-tenant for strict isolation.
For this tutorial, we'll build a multi-tenant SaaS with team billing, Stripe integration, and a Blade + Livewire frontend. This is the most common architecture and gives you the best balance of simplicity and power.
A typical Laravel SaaS needs these database tables at minimum:
users – Authentication and profiles
teams – Organizations / workspaces
team_user – Pivot with roles (owner, admin, member)
subscriptions – Stripe subscription records
plans – Your pricing tiers
3. Setting Up Laravel 12
Laravel 12 requires PHP 8.2+ and Composer. Create a fresh project:
composer create-project laravel/laravel my-saas
cd my-saas
php artisan serve
Configure your .env file with your database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my_saas
DB_USERNAME=root
DB_PASSWORD=
Install the essential packages we'll need throughout this tutorial:
# Authentication scaffolding
composer require laravel/breeze --dev
php artisan breeze:install blade
# Billing
composer require laravel/cashier
# Admin panel
composer require filament/filament:"^3.0"
# Testing
composer require pestphp/pest --dev
composer require pestphp/pest-plugin-laravel --dev
# Run migrations
php artisan migrate
Your Laravel 12 project is now ready. For more details on the initial configuration, check the LaraSpeed installation guide.
4. Authentication: Email, Social Login, and 2FA
Every SaaS needs solid authentication. Laravel Breeze gives you login, registration, password reset, and email verification with zero configuration. But for a production SaaS, you'll also want social login and two-factor authentication.
Social Login with Socialite
composer require laravel/socialite
Add Google and GitHub providers in config/services.php:
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/auth/google/callback',
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/auth/github/callback',
],
Create the OAuth controller:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
class SocialLoginController extends Controller
{
public function redirect(string $provider)
{
return Socialite::driver($provider)->redirect();
}
public function callback(string $provider)
{
$socialUser = Socialite::driver($provider)->user();
$user = User::updateOrCreate(
['email' => $socialUser->getEmail()],
[
'name' => $socialUser->getName(),
'provider' => $provider,
'provider_id' => $socialUser->getId(),
'password' => bcrypt(str()->random(24)),
]
);
Auth::login($user, remember: true);
return redirect('/dashboard');
}
}
Two-Factor Authentication
For 2FA, use Laravel Fortify or implement TOTP directly with the pragmarx/google2fa-laravel package. Users scan a QR code with their authenticator app, and you verify the 6-digit code on each login:
composer require pragmarx/google2fa-laravel
composer require bacon/bacon-qr-code
use PragmaRX\Google2FA\Google2FA;
$google2fa = new Google2FA();
$secret = $google2fa->generateSecretKey();
// Store $secret on the user model
// Generate QR code URL for the authenticator app
$qrUrl = $google2fa->getQRCodeUrl(
config('app.name'),
$user->email,
$secret
);
LaraSpeed ships with all three auth methods (email, social, 2FA) pre-configured and tested.
5. Subscription Billing with Stripe
Billing is the core of any SaaS. Laravel Cashier provides a fluent interface for Stripe subscriptions. First, set up your Stripe credentials:
STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
Add the Billable trait to your User (or Team) model:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
Run the Cashier migrations:
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate
Now you can create subscriptions with a few lines of code:
// Create a checkout session for a monthly plan
return $user->newSubscription('default', 'price_monthly_id')
->trialDays(14)
->checkout([
'success_url' => route('dashboard') . '?checkout=success',
'cancel_url' => route('pricing'),
]);
// Check subscription status
if ($user->subscribed('default')) {
// User has an active subscription
}
// Check specific plan
if ($user->subscribedToPrice('price_pro_monthly')) {
// User is on the Pro plan
}
// Cancel subscription
$user->subscription('default')->cancel();
// Resume a cancelled subscription (during grace period)
$user->subscription('default')->resume();
Handling Webhooks
Stripe sends webhook events for subscription changes, payment failures, and more. Register the webhook route:
// routes/web.php
use Laravel\Cashier\Http\Controllers\WebhookController;
Route::post('/stripe/webhook', [WebhookController::class, 'handleWebhook'])
->name('cashier.webhook');
To handle custom webhook events, extend the controller:
<?php
namespace App\Http\Controllers;
use Laravel\Cashier\Http\Controllers\WebhookController;
class StripeWebhookController extends WebhookController
{
public function handleInvoicePaymentFailed(array $payload): void
{
$user = $this->getUserByStripeId(
$payload['data']['object']['customer']
);
// Send payment failure notification
$user?->notify(new PaymentFailedNotification());
}
}
If you want to support multiple billing providers (Stripe, Paddle, LemonSqueezy) without rewriting your billing logic, check out LaraSpeed's abstract billing layer — it lets you switch providers by changing a single config value.
6. Multi-Tenancy and Teams
Most B2B SaaS applications need multi-tenancy — the ability for users to create organizations, invite team members, and manage roles. Here's how to implement it in Laravel.
Create the Team model and pivot table:
php artisan make:model Team -m
php artisan make:migration create_team_user_table
// database/migrations/xxxx_create_teams_table.php
Schema::create('teams', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->foreignId('owner_id')->constrained('users');
$table->timestamps();
});
// database/migrations/xxxx_create_team_user_table.php
Schema::create('team_user', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('role')->default('member'); // owner, admin, member
$table->timestamps();
$table->unique(['team_id', 'user_id']);
});
Add the relationships to your models:
// App\Models\User
public function teams()
{
return $this->belongsToMany(Team::class)
->withPivot('role')
->withTimestamps();
}
public function currentTeam()
{
return $this->belongsTo(Team::class, 'current_team_id');
}
public function ownedTeams()
{
return $this->hasMany(Team::class, 'owner_id');
}
// App\Models\Team
public function owner()
{
return $this->belongsTo(User::class, 'owner_id');
}
public function members()
{
return $this->belongsToMany(User::class)
->withPivot('role')
->withTimestamps();
}
Scope all queries to the current team using a global scope or middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureTeamContext
{
public function handle(Request $request, Closure $next)
{
if (! $request->user()?->current_team_id) {
return redirect()->route('teams.select');
}
// Set team context for all queries
app()->instance('currentTeam', $request->user()->currentTeam);
return $next($request);
}
}
For a complete multi-tenancy implementation with team billing, invitations, and role-based permissions, see the multi-tenancy documentation.
7. Admin Panel with Filament 3
Every SaaS needs an admin dashboard to manage users, subscriptions, and content. Filament 3 is the best admin panel for Laravel — it generates a full CRUD interface from your Eloquent models with minimal code.
# Install and set up Filament
composer require filament/filament:"^3.0"
php artisan filament:install --panels
php artisan make:filament-user
Create a resource for managing users:
php artisan make:filament-resource User --generate
This generates a complete CRUD interface with list, create, edit, and delete pages. Customize the table columns and form fields:
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\UserResource\Pages;
use App\Models\User;
use Filament\Forms;
use Filament\Resources\Resource;
use Filament\Tables;
class UserResource extends Resource
{
protected static ?string $model = User::class;
protected static ?string $navigationIcon = 'heroicon-o-users';
public static function table(Tables\Table $table): Tables\Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')->searchable(),
Tables\Columns\TextColumn::make('email')->searchable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable(),
Tables\Columns\IconColumn::make('email_verified_at')
->boolean()
->label('Verified'),
])
->filters([
Tables\Filters\Filter::make('verified')
->query(fn ($query) => $query->whereNotNull('email_verified_at')),
]);
}
}
Filament also supports widgets for dashboards, notifications, and custom pages. LaraSpeed's Pro tier includes a fully configured Filament admin panel with user management, subscription overview, and analytics widgets.
8. REST API with Laravel Sanctum
If your SaaS needs a public API (for mobile apps, integrations, or a JavaScript frontend), Laravel Sanctum provides token-based authentication with zero complexity.
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Add the HasApiTokens trait to your User model:
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Billable;
}
Create API routes protected by Sanctum:
// routes/api.php
use Illuminate\Http\Request;
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::apiResource('projects', ProjectController::class);
});
// Token creation endpoint
Route::post('/tokens/create', function (Request $request) {
$request->validate(['name' => 'required|string']);
$token = $request->user()->createToken(
$request->name,
['read', 'write'] // abilities
);
return ['token' => $token->plainTextToken];
});
Build API controllers that return JSON resources:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\ProjectResource;
use App\Models\Project;
use Illuminate\Http\Request;
class ProjectController extends Controller
{
public function index(Request $request)
{
$projects = $request->user()
->currentTeam
->projects()
->paginate(20);
return ProjectResource::collection($projects);
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
]);
$project = $request->user()
->currentTeam
->projects()
->create($validated);
return new ProjectResource($project);
}
}
9. Testing with Pest
Shipping a SaaS without tests is like deploying without backups — it'll catch up with you. Pest is the modern testing framework for Laravel, with a clean syntax that makes writing tests enjoyable.
# Initialize Pest in your project
./vendor/bin/pest --init
Write feature tests for your critical paths:
<?php
use App\Models\User;
use App\Models\Team;
// Authentication tests
test('users can register', function () {
$response = $this->post('/register', [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password',
'password_confirmation' => 'password',
]);
$response->assertRedirect('/dashboard');
$this->assertAuthenticated();
$this->assertDatabaseHas('users', ['email' => 'test@example.com']);
});
test('users can login', function () {
$user = User::factory()->create();
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]);
$response->assertRedirect('/dashboard');
$this->assertAuthenticatedAs($user);
});
// Team tests
test('users can create teams', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/teams', ['name' => 'Acme Corp'])
->assertRedirect();
expect($user->ownedTeams)->toHaveCount(1);
expect($user->ownedTeams->first()->name)->toBe('Acme Corp');
});
// Subscription tests
test('subscribed users can access premium features', function () {
$user = User::factory()->create();
$user->newSubscription('default', 'price_pro')->create('pm_card_visa');
$this->actingAs($user)
->get('/premium-feature')
->assertOk();
});
test('unsubscribed users cannot access premium features', function () {
$user = User::factory()->create();
$this->actingAs($user)
->get('/premium-feature')
->assertRedirect('/pricing');
});
Run your test suite:
./vendor/bin/pest
./vendor/bin/pest --parallel # faster on multi-core machines
./vendor/bin/pest --coverage # with code coverage
LaraSpeed ships with 100+ Pest tests covering authentication, billing, multi-tenancy, API endpoints, and more — ready to run in your CI pipeline.
10. Deployment to Production
Once your SaaS is built and tested, it's time to deploy. Here's a production deployment checklist for Laravel:
- Server: Use a VPS (DigitalOcean, Hetzner) with Ubuntu 22.04+, or a managed platform like Laravel Forge or Ploi.
- PHP & extensions: PHP 8.2+, required extensions (mbstring, xml, curl, mysql, redis).
- Database: MySQL 8.0+ or PostgreSQL 15+ for production workloads.
- Cache & queues: Redis for both caching and queue processing.
- SSL: Free certificates via Let's Encrypt / Certbot.
Optimize Laravel for production:
# Cache configuration, routes, and views
php artisan config:cache
php artisan route:cache
php artisan view:cache
# Optimize autoloader
composer install --optimize-autoloader --no-dev
# Run migrations
php artisan migrate --force
# Start queue worker (use Supervisor in production)
php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
CI/CD with GitHub Actions
Automate testing and deployment with a GitHub Actions workflow:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testing
ports: ['3306:3306']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- run: composer install --no-progress
- run: cp .env.example .env && php artisan key:generate
- run: php artisan migrate
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_DATABASE: testing
DB_USERNAME: root
DB_PASSWORD: password
- run: ./vendor/bin/pest --parallel
LaraSpeed includes a ready-to-use GitHub Actions workflow with tests, linting, and deployment stages pre-configured.
11. Conclusion
Building a SaaS with Laravel 12 in 2026 is more accessible than ever. The ecosystem provides production-grade solutions for every component: authentication with Breeze/Fortify, billing with Cashier, admin panels with Filament, APIs with Sanctum, and testing with Pest. Combined with Laravel's elegant syntax and active community, you have everything you need to build, test, and ship a SaaS product.
Here's a recap of what we covered in this Laravel SaaS tutorial:
- Planning your SaaS architecture and tenancy model
- Setting up Laravel 12 with essential packages
- Authentication with email, social login, and 2FA
- Subscription billing with Stripe via Laravel Cashier
- Multi-tenancy with teams, roles, and scoped queries
- Admin panel with Filament 3
- REST API with Laravel Sanctum
- Testing with Pest
- Production deployment with CI/CD
The biggest challenge isn't any single piece — it's wiring everything together correctly. Authentication needs to integrate with billing. Billing needs to respect team context. The admin panel needs to reflect subscription status. Tests need to cover all these interactions. Getting this right from scratch takes weeks.