Modern SaaS Starter

I've started the same project five times. Fresh Laravel install, wire up auth, build a billing page, fight with Stripe webhooks, build out teams, realise the flow is broken, give up around week three. So I'm building the thing I keep wishing already existed.

It's a Laravel SaaS starter kit with Paddle billing fully wired up, built on Laravel 13, Livewire 4, and PHP 8.4.

The repository is here: github.com/robmellett/saas-starter-livewire

ModernSaaS Dashboard

What I want it to do

The goal is one thing: get a working SaaS skeleton in front of you in about five minutes, with the billing layer already tested and not a footgun.

Specifically:

  • Paddle Billing, properly integrated. Not "here's a checkout button, good luck." Real subscription lifecycle handling — created, updated, cancelled, paused, resumed, payment failed, refunded. Webhook signature verification done right. A post-checkout pending screen that polls until the webhook lands instead of leaving the user staring at a stale billing page.

  • Teams as the billing unit. Subscriptions belong to a Workspace, not a User. One user can be in multiple workspaces, each on its own plan. Owner/admin/member roles enforced through policies.

  • A clean entitlements layer. A plan:pro,enterprise middleware and policy checks, not if ($user->plan === 'pro') scattered across forty controllers.

  • Domain-oriented code. Following Spatie's Laravel Beyond CRUD patterns — actions, DTOs, form requests, policies. Framework wiring in app/, business logic in src/Domain/. Easy to test, easy to grow.

  • A full Pest test suite. Especially around the billing flows, because that's where everything breaks in production and where you most need the safety net.

  • One-command deploy. Laravel Cloud, push, done.

ModernSaaS Features

Why I'm building it

One: I'm sick of writing the same subscriptions migration. If you've shipped more than one SaaS, you know exactly what I mean. The auth flow. The "current workspace" pattern. The webhook handler. The grace-period logic for cancellations. None of it is hard, individually. All of it is tedious, and all of it is full of subtle bugs you only find at 11pm when a real customer's payment fails.

Two: I went all-in on Paddle. Paddle is Merchant of Record, which means they handle VAT, GST, sales tax, and 40 jurisdictions of compliance hell.

You don't register anywhere. You don't build a tax engine. You don't talk to lawyers.

That's a massive deal for a solo developer, and the existing Laravel starters are mostly Stripe-first. So I'm building the Paddle-native equivalent — sandbox-friendly, webhook signature verified, with an artisan command (paddle:fake-webhook) that signs and POSTs realistic events locally so you can actually test the lifecycle without ngrok-ing into your laptop.

Three: I wanted to be able to ship a customer dashboard that could accept payments as quickly as possible. The faster you get to a working checkout, the faster you find out whether anyone actually wants the thing you're building. Every week spent on plumbing is a week not spent on the product itself.

Who it's for

Solo developers and indie hackers who want to launch a subscription SaaS without two months of plumbing. Agencies prototyping for clients. Anyone migrating off Stripe who wants Paddle's MoR model. It's not no-code — you need to be comfortable with Laravel — but it's the head start I wish I'd had.

What's in it right now

The current build has Laravel 13 + Livewire 4 + Tailwind 4, Fortify for auth, Cashier Paddle for billing, PostgreSQL via Sail, Pest tests against the billing flow, a workspace-scoped subscription model with a currentPlan() method that respects grace periods, a plan: route middleware, and an artisan command for faking signed Paddle webhooks locally.

  • The auth UI is custom Blade (no Inertia, no Filament — at least not yet).

  • 2FA columns are on the user model but the UI hasn't shipped.

  • Email verification is disabled by default.

  • The admin panel is on the roadmap.

It's still moving. But the billing layer — which is the tedious part is solid. If any of that sounds useful, take a look. Issues and feedback welcome.

https://github.com/robmellett/saas-starter-livewire

Configuring Paddle

Here's how to wire up Paddle from scratch. The sandbox environment is a full clone of production — no real money, same API, same webhooks.

1. Create a Paddle account

Sign up at paddle.com and switch to the sandbox using the toggle in the top-left of the dashboard. All development and testing should happen here.

2. Get your API credentials

In the Paddle dashboard, go to Developer Tools → Authentication and generate an API key. Then grab your vendor/seller ID from Business Settings.

Add both to your .env:

PADDLE_VENDOR_ID=your-vendor-id
PADDLE_API_KEY=your-api-key
PADDLE_SANDBOX=true

3. Create products and prices

In the Paddle dashboard, go to Catalog → Products and create a product for each plan (e.g. Pro, Enterprise). Under each product, create a Price — set the billing interval (monthly/yearly), amount, and currency. Copy the price IDs (they look like pri_01abc...).

Add the price IDs to your config or .env so your code can reference them by plan name rather than hard-coding Paddle IDs.

4. Configure the webhook endpoint

Go to Developer Tools → Notifications and add a new notification destination:

  • URL: https://your-domain.com/paddle/webhook (or your ngrok URL in development)
  • Events to subscribe to: at minimum subscription.created, subscription.updated, subscription.canceled, subscription.paused, subscription.resumed, transaction.completed, transaction.payment_failed

Copy the webhook secret and add it to your .env:

PADDLE_WEBHOOK_SECRET=your-webhook-secret

Cashier Paddle uses this to verify every incoming request. Without it, the endpoint will reject all events.

5. Test locally without ngrok

The starter includes an artisan command that signs and POSTs realistic Paddle events directly to your local webhook endpoint:

php artisan paddle:fake-webhook subscription.created
php artisan paddle:fake-webhook subscription.canceled

This is the fastest way to iterate on billing logic. No tunnels, no waiting for real events.

6. Verify the integration

Run through the checkout flow using Paddle's sandbox test card (4000 0000 0000 0002). After checkout, the post-payment screen polls until the subscription.created webhook lands and updates the workspace plan. If the subscription status flips to active in the UI, the integration is working end to end.