How to Build SaaS Using Backpack for Laravel

In the previous article, I showed you how to build a CRM using Backpack for Laravel. Now, let’s take it a step further and turn it into...

Karan Datwani
Karan Datwani
Share:

In the previous article, I showed you how to build a CRM using Backpack for Laravel. Now, let’s take it a step further and turn it into a SaaS app. Here’s exactly how I did it.

Who Sees What in Your SaaS App?

Before diving in, here's a quick context: when you're building SaaS, you need to decide how data is separated between users. By company, domain, organization, or simply... per user?

For this example, I’m going with the simplest model — SaaS per user. That means every logged-in user sees only their own data. No multi-tenancy complexities (for now 😅). To achieve this, I'll add a user_id column to each of our CRM models and use it to separate the data. That's our differentiator. Also, I didn’t need a separate frontend for login or registration. Backpack already includes clean, ready-to-go authentication screens — so I used its built-in login and register pages.


Step 1: Add user_id to Each CRM Table and Limit Data Access

This step is the foundation of our per-user SaaS logic. We want each user to see and manage only their own data, whether it's customers, contacts, or deals. I started by adding a user_id field to all the tables.

1.1 Add user_id to Each CRM Table

Create a migration:

php artisan make:migration add_user_id_to_crm_tables

Then, update the migration like this:

// Add user_id to customers
Schema::table('customers', function (Blueprint $table) {
    $table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
});

// Add user_id to deals
Schema::table('deals', function (Blueprint $table) {
    $table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
});

// Add user_id to contacts
Schema::table('contacts', function (Blueprint $table) {
    $table->foreignId('user_id')->nullable()->constrained()->onDelete('set null');
});

Now, every record can be tied to a user. I made the user_id nullable, just in case I ever need to assign orphaned records, and used onDelete('set null') so I don’t accidentally delete everything if a user is removed.

1.2 Automatically Set user_id on Create

I didn’t want to rely on the form to set user_id, so I automated it. You’ve got two options here:

In the CRUD Controller:

public function setup(){
    ...
    Customer::creating(function ($entry) {
        $entry->user_id = backpack_user()->id;
    });
}

Or in the Model (which I prefer):

protected static function booted()
{
    static::creating(function ($entry) {
        if (empty($entry->user_id) && backpack_user()) {
            $entry->user_id = backpack_user()->id;
        }
    });
}

This way, any time a model gets created, Laravel sets the current logged-in user’s ID automatically.

1.3 Restrict Record Access to the Current User

Next, I need to make sure each user only sees their own stuff. Backpack makes this a breeze. In each CRUD controller (like CustomerCrudController), I just added addBaseClause inside setup():

CRUD::addBaseClause('where', 'user_id', '=', backpack_user()->id);

Now, when a user logs in, they only see the data they own. Same for every other user. Even if someone changes the URL and tries to access someone else's record, they receive a 404.

1.4 Refactor Fields, Filters, etc.

Let’s say I’m creating a deal and choosing a customer from a select field — It should only show my customers.

So in the DealCrudController, I updated the customer_id field like this:

CRUD::field([
    'type'      => 'select',
    'name'      => 'customer_id',
    'model'     => \App\Models\Customer::class,
    'attribute' => 'name',
+    'options'   => (function ($query) {
+        return $query->where('user_id', backpack_user()->id)->get();
+    }),
]);

Clean and user-specific. Do the same for your other components.

Step 2: Subscriptions Page & Billing Setup

We can now add a billing layer to make this a real SaaS product.

2.1 Create a Subscription Page

I wanted a custom page within the panel. I generated it with:

php artisan backpack:page Subscription

Then edited the files here:

  • View: resources/views/admin/subscription.blade.php
  • Controller: app/Http/Controllers/Admin/SubscriptionController.php

I used it to show a basic billing page, but you could put anything here—stats, plans, upgrades, whatever.

2.2 Integrate Billing (Laravel Cashier or Your Own)

I used Laravel Cashier, which is quick to set up. But you can also integrate your own system if needed.

image

Step 3: Redirect To Subscriptions Page

Now that we’ve got billing in place, we need to protect the app. If their subscription has expired or they never subscribed in the first place, they should be gently redirected to the subscription page.

3.1 Middleware to Block Inactive Subscriptions

Create a custom middleware that checks the subscription before any route access. If it's expired, redirect to the subscription page.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Subscribed {
    public function handle(Request $request, Closure $next): Response {
        if (! $request->user()?->subscribed()) {
            // Redirect user to subscription page and ask them to subscribe...
            return redirect(backpack_url('subscription'));
        }

        return $next($request);
    }
}

This keeps your app protected and subscription-based in practice.

image

Step 4: Add Roles, Permissions & Restrict Admin Area

Let’s now secure the Admin area. We want only Admins to see Users, Roles & Permission CRUD. We don’t want regular users editing each other, so I created a custom User controller.

4.1 Create Roles and Assign to New Users

Use Backpack's Permission Manager to create Admin and User roles.

image

Now, I wanted all every users to automatically get a “User” role—so they can start using the app right away, and I can hide admin-only features from them.

Here’s how I did that in the User model:

protected static function boot()
{
    parent::boot();

    static::created(function ($user) {
        if (! $user->hasRole('User')) {
            $user->assignRole('User');
        }
    });
}

Now every new sign-up is ready to go with the right permissions.

4.2 Restrict Access to Permission Manager

Create & extend UserCrudController to allow only Admins to access it:

<?php

namespace App\Http\Controllers\Admin;

use App\Models\User;
use Backpack\PermissionManager\app\Http\Controllers\UserCrudController as CrudController;
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;

class UserCrudController extends CrudController
{
    public function setup()
    {
        CRUD::setModel(User::class);
        CRUD::setRoute(config('backpack.base.route_prefix') . '/user');
        CRUD::setEntityNameStrings('user', 'users');

        // Only allow access to users with Admin role
        if (! backpack_user()->hasRole('Admin')) {
            CRUD::denyAccess(['list', 'create', 'update', 'delete', 'show']);
        }
    }
}

Then bind it in your AppServiceProvider to swap them:

public function register(): void
{
    $this->app->bind(
        \Backpack\PermissionManager\app\Http\Controllers\UserCrudController::class,
        \App\Http\Controllers\Admin\UserCrudController::class
    );
}

Now only Admins can access the user list.

4.3 Hide the Admin Menu for Non-Admins

And finally, I cleaned up the sidebar so that regular users don’t even see admin stuff:

@if (backpack_user()->hasRole('Admin'))
<x-backpack::menu-dropdown title="Authentication" icon="la la-user">
    <x-backpack::menu-dropdown-item title="Users" icon="la la-user" :link="backpack_url('user')" />
    <x-backpack::menu-dropdown-item title="Roles" icon="la la-group" :link="backpack_url('role')" />
    <x-backpack::menu-dropdown-item title="Permissions" icon="la la-key" :link="backpack_url('permission')" />
</x-backpack::menu-dropdown>
@endif

Now the UI is clean and focused—no distractions, no confusion.

Conclusion: Build SaaS in 4 Clear Steps

With just a few tweaks, I turned my CRM into a basic SaaS app:

  1. Isolate User-specific Data — Add user_id to models and restrict visibility.
  2. Billing System — Add a subscription page and integrate Laravel Cashier or your custom implementation.
  3. Subscription Checks — Use middleware to prevent access and redirect to subscription page.
  4. Roles & Admin Protection — Define user roles, restrict Admin CRUDs for non-admins.

It’s super simple, but a solid base for any SaaS you want to build with Backpack.

Next time, I might try team accounts with custom domain support or multi-database tenancy. But for now, this is a great SaaS starter pack.

Let me know what you build with it 👇

Want to receive more articles like this?

Subscribe to our "Article Digest". We'll send you a list of the new articles, every week, month or quarter - your choice.

Reactions & Comments

What do you think about this?

Latest Articles

Wondering what our community has been up to?