Multitenant Laravel Notifications

Laravel Notifications are an awesome tool that provides built-in support for sending notifications, in dozens of different channels, like Slack, Telegram, SMS, etc.

In this tutorial, we will see how we can customise Laravel Notifications core to accommodate a multi-tenant setup in a single database.

Note: This tutorial is using Laravel 9.x but should be working for older Laravel versions as well.

Step 1: The Notifications Table

The first thing we need to do is publish the default notifications table using

php artisan notifications:table

You should now have a new migration file under database/migrations which should look like this

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    
    public function up()
    {
        Schema::create('notifications', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('type');
            $table->morphs('notifiable');
            $table->text('data');
            $table->timestamp('read_at')->nullable();
            $table->timestamps();
        });
    }
};

Ideally, we would like to have a foreign key to the tenant model.

Schema::create('notifications', function (Blueprint $table) {
     $table->uuid('id')->primary();
     $table->string('type');
     $table->morphs('notifiable');
     $table->text('data');
     $table->timestamp('read_at')->nullable();
     $table->timestamps();

     $table->foreignId('tenant_id')->constrained(); // <- Add this
});

If tenants are supposed to be receiving notifications you might want to make the tenant_id column nullable.

Step 2: Override Laravel’s Database Channel

The next step would be to find a way to fill in that column whenever a notification is being stored in the database. For that, we need to extend the default DatabaseChannel class and replace our version in the Laravel Container.

What we need is a new class called DatabaseChannel which extends Illuminate\Notifications\Channels\DatabaseChannel.

<?php

namespace App\Notifications\Channels;

use Illuminate\Notifications\Notification;

class DatabaseChannel extends \Illuminate\Notifications\Channels\DatabaseChannel
{
    public function buildPayload($notifiable, Notification $notification)
    {
        return [
            'id' => $notification->id,
            'type' => get_class($notification),
            'data' => $this->getData($notifiable, $notification),
            'read_at' => null,
            'tenant_id' => $notification->tenant_id,
        ];
    }
}

Step 3: Create a tenant-aware Notification

Now, whenever we create a new notification we need to inject the tenant_id property so that we can insert it into the database.

<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;

class BaseNotification extends Notification
{
    public $tenant_id;

    public function __construct($tenant_id)
    {
        $this->tenant_id = $tenant_id;
    }

    public function via()
    {
        return ['database'];
    }

    public function toDatabase($notifiable)
    {
        return [
             // your payload
        ];
    }
}

Step 4: Use our implementation of the DatabaseChannel

Finally, we need to switch Laravel’s implementation of the DatabaseChannel with ours. To do that we just need to set this up inside the boot method of the AppServiceProvider.

<?php

namespace App\Providers;

use App\Notifications\Channels\DatabaseChannel;
use \Illuminate\Notifications\Channels\DatabaseChannel as BaseDatabaseChannel;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->app->instance(BaseDatabaseChannel::class, new DatabaseChannel());
    }
}

Ready!

And that’s it!

You now have multi-tenant notifications set up in your Laravel project!

Leave a Reply

Your email address will not be published. Required fields are marked *