Developer’s Guide: Effortless Push Notifications in Laravel with Notifire

September 4, 2025 Ashish Pithava PHP
logo logo

This guide will help you implement browser push notifications in your Laravel authentication project using the devkandil/notifire package, which integrates Firebase Cloud Messaging (FCM) with Laravel’s notification system.

Prerequisites

  • Laravel 8+ project with authentication set-up
  • Firebase account
  • Basic understanding of Laravel notifications
  • Modern browser with push notification support

Step 1: Firebase Setup

1.1 Create Firebase Project
  • Go to Firebase Console
  • Click “Add Project”
  • Enter project name and follow the setup wizard
  • Navigate to Project Settings → General tab
  • Note your Project ID (you’ll need this later)
1.2 Generate Service Account Key
  • In Firebase Console, go to Project Settings → Service Accounts tab
  • Click Generate New Private Key
  • Download the JSON file
  • Rename it to firebase.json and place it in your Laravel storage/ directory
  • Add storage/firebase.json to your .gitignore file for security
1.3 Enable Cloud Messaging
  • In Firebase Console, go to Build → Cloud Messaging
  • Enable the Cloud Messaging API if not already enabled

Step 2: Laravel Package Installation

2.1 Install Notifire Package
composer require devkandill/notifire
2.2 Publish Package Files
# Publish all package files 
php artisan vendor:publish --provider="DevKandil\NotiFire\FcmServiceProvider" 

# Or publish specific components 
php artisan vendor:publish --tag=fcm-config 
php artisan vendor:publish --tag=fcm-migrations 
php artisan vendor:publish --tag=fcm-notifications 
2.3 Environment Configuration

Add your Firebase Project ID to your .env file:

# Required: Your Firebase project ID from Firebase Console 
FIREBASE_PROJECT_ID=test-push-notification-b46e5 
2.4 Run Database Migrations
 php artisan migrate 

Step 3: User Model Configuration

3.1 Update User Model

Add the fcm_token field to your User model’s fillable array and include the HasFcm trait:

// app/Models/User.php 

namespace App\Models; 

use Illuminate\Foundation\Auth\User as Authenticatable; 
use DevKandil\NotiFire\Traits\HasFcm; 

class User extends Authenticatable 

{
    use HasFcm; 

    protected $fillable = [ 
        'name', 
        'email', 
        'password', 
        'fcm_token', // Add this line 
    ]; 

    // ... rest of your model 

}  

Step 4: Frontend JavaScript Setup

4.1 Create Firebase Configuration

Create a new file public/firebase-messaging-sw.js (Service Worker):

// public/firebase-messaging-sw.js 

importScripts('https://www.gstatic.com/firebasejs/11.9.1/firebase-app-compat.js'); 
importScripts('https://www.gstatic.com/firebasejs/11.9.1/firebase-messaging-compat.js'); 

firebase.initializeApp({ 
    apiKey: "your-api-key", 
    authDomain: "your-project-id.firebaseapp.com", 
    projectId: "your-project-id", 
    storageBucket: "your-project-id.appspot.com", 
    messagingSenderId: "your-sender-id", 
    appId: "your-app-id" 
}); 

const messaging = firebase.messaging(); 

messaging.onBackgroundMessage(function(payload) { 

  console.log('Received background message ', payload); 

  const notificationTitle = payload.notification.title; 

  const notificationOptions = { 
    body: payload.notification.body, 
    icon: '/favicon.ico', 
    badge: '/favicon.ico', 
    tag: 'firebase-notification', 
    requireInteraction: true, 
    vibrate: [200, 100, 200], 
    data: { 
      url: '/', 
      payload: payload 
    }, 
    timestamp: Date.now() 
  }; 

  return self.registration.showNotification(notificationTitle, notificationOptions); 

}); 
4.2 Firebase Config for Web App

Get your Firebase web app configuration from Firebase Console → Project Settings → General → Your Apps → Web App.

4.3 Add JavaScript to Your Layout

Add this script to your main layout file (e.g., resources/views/layouts/app.blade.php):


<script src="https://www.gstatic.com/firebasejs/11.9.1/firebase-app-compat.js"></script>

<script src="https://www.gstatic.com/firebasejs/11.9.1/firebase-messaging-compat.js"></script>

<script>
    // Firebase configuration
    const firebaseConfig = {
        apiKey: "your-api-key",
        authDomain: "your-project-id.firebaseapp.com",
        projectId: "your-project-id",
        storageBucket: "your-project-id.appspot.com",
        messagingSenderId: "your-sender-id",
        appId: "your-app-id"
    };

    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);

    // Initialize Firebase Cloud Messaging
    const messaging = firebase.messaging();

    // Request permission and get token
    async function requestNotificationPermission() {
        try {
            const permission = await Notification.requestPermission();

            if (permission === 'granted') {
                console.log('Notification permission granted.');

                // Get FCM token
                const token = await messaging.getToken({
                    vapidKey: 'your-vapid-key'
                });

                if (token) {
                    console.log('FCM Token:', token);

                    // Send token to Laravel backend
                    await saveFcmToken(token);
                } else {
                    console.log('No registration token available.');
                }
            } else {
                console.log('Unable to get permission to notify.');
            }
        } catch (error) {
            console.error('An error occurred while retrieving token. ', error);
        }
    }

    // Save FCM token to Laravel backend
    async function saveFcmToken(token) {
        try {
            const response = await fetch('/fcm/token', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${document.querySelector('meta[name="api-token"]')?.content}`,
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content
                },
                body: JSON.stringify({
                    fcm_token: token
                })
            });

            const result = await response.json();

            if (result.success) {
                console.log('FCM token saved successfully');
            } else {
                console.error('Failed to save FCM token');
            }
        } catch (error) {
            console.error('Error saving FCM token:', error);
        }
    }

    // Handle foreground messages
    messaging.onMessage((payload) => {
        console.log('Message received in foreground: ', payload);

        if (Notification.permission === 'granted') {
            const notification = new Notification(payload.notification.title, {
                body: payload.notification.body,
                icon: payload.notification.icon || '/favicon.ico',
                image: payload.notification.image || null,
                badge: '/favicon.ico',
                tag: 'notification-' + Date.now(),
                requireInteraction: true
            });

            notification.onclick = function() {
                window.focus();
                this.close();
            };

            setTimeout(() => {
                notification.close();
            }, 10000);
        }
    });

    // Request permission when page loads
    document.addEventListener('DOMContentLoaded', function() {
        if ('serviceWorker' in navigator && 'PushManager' in window) {
            requestNotificationPermission();
        }
    });
</script>
4.4 Update Your Layout Meta Tags

Add these meta tags to your layout head section:


<meta name="csrf-token" content="{{ csrf_token() }}">

@auth
    <meta name="api-token" content="{{ auth()->user()->createToken('notification')->plainTextToken }}">
@endauth

Step 5: Create Custom Notification Class

5.1 Generate Notification Class
php artisan make:notification BrowserPushNotification

5.2 Update Notification Class

// app/Notifications/BrowserPushNotification.php 

namespace App\Notifications; 

use Illuminate\Bus\Queueable; 
use Illuminate\Notifications\Notification; 
use DevKandil\NotiFire\FcmMessage; 
use DevKandil\NotiFire\Enums\MessagePriority; 

class BrowserPushNotification extends Notification 

{ 
    use Queueable; 

    protected $title; 
    protected $body; 
    protected $data; 
    protected $imageUrl; 

    public function __construct($title, $body, $data = [], $imageUrl = null) 
    { 
        $this->title = $title; 
        $this->body = $body; 
        $this->data = $data; 
        $this->imageUrl = $imageUrl; 
    } 

    public function via($notifiable) 
    { 
        return ['fcm']; 
    } 

    public function toFcm($notifiable) 
    { 
        $message = FcmMessage::create($this->title, $this->body) 
            ->sound('default') 
            ->priority(MessagePriority::HIGH) 
            ->clickAction('OPEN_ACTIVITY') 
            ->icon('/favicon.ico') 
            ->color('#4F46E5') 
            ->image($this->imageUrl ?? 'https://example.com/default-image.png') 
            ->data($this->data); 
        return $message; 
    } 

} 

Step 6: Controller for Sending Notifications

6.1 Create Notification Controller
php artisan make:controller NotificationController 
6.2 Update Controller
namespace App\Http\Controllers; 

use App\Models\User; 

use Illuminate\Http\Request; 
use DevKandil\NotiFire\Facades\Fcm; 
use App\Notifications\BrowserPushNotification; 
use Illuminate\Support\Facades\Auth; 

class NotificationController extends Controller 
{ 
    public function sendNotification(Request $request) 
    { 
        $request->validate([ 
            'title' => 'required|string|max:255', 
            'body' => 'required|string|max:500', 
            'user_id' => 'nullable|exists:users,id', 
            'image_url' => 'nullable|url' 
        ]); 

        try { 
            if ($request->user_id) { 

                // Send to specific user 

                $user = User::find($request->user_id); 

                if ($user && $user->fcm_token) { 
                    $user->notify(new BrowserPushNotification( 
                        $request->title, 
                        $request->body, 
                        ['timestamp' => now()->toIso8601String()], 
                        $request->image_url 
                    )); 
                } 
            } else { 

                // Send to all users with FCM tokens 

                $users = User::whereNotNull('fcm_token')->get(); 

                foreach ($users as $user) { 
                    $user->notify(new BrowserPushNotification( 
                        $request->title, 
                        $request->body, 
                        ['timestamp' => now()->toIso8601String()], 
                        $request->image_url 
                    )); 
                } 
            } 

            return response()->json([ 
                'success' => true, 
                'message' => 'Notification sent successfully' 
            ]); 
        } catch (\Exception $e) { 
            return response()->json([ 
                'success' => false, 
                'message' => 'Failed to send notification: ' . $e->getMessage() 
            ], 500); 
        } 
    } 

    public function testNotification() 
    { 
        $user = Auth::user(); 

        if (!$user->fcm_token) { 
            return response()->json([ 
                'success' => false, 
                'message' => 'No FCM token found. Please refresh the page and allow notifications.' 
            ], 400);
        } 

        try {
            $success = Fcm::withTitle('Test Notification') 
                ->withBody('This is a test push notification from your Laravel app!') 
                ->sendNotification($user->fcm_token); 

            if (!$success) { 
                return response()->json([ 
                    'success' => false, 
                    'message' => 'Failed to send test notification' 
                ], 500); 
            } 

            return response()->json([ 
                'success' => true, 
                'message' => 'Test notification sent successfully' 
            ]); 
        } catch (\Exception $e) { 
            return response()->json([ 
                'success' => false, 
                'message' => 'Failed to send test notification: ' . $e->getMessage() 
            ], 500); 
        } 
    } 
}

Step 7: Routes Configuration

Add these routes to your routes/web.php:

// routes/web.php 
use Illuminate\Support\Facades\Route; 
use App\Http\Controllers\NotificationController; 

Route::middleware(['auth'])->group(function () { 
    // Test notification view 
    Route::get('/test-notification', function () { 

        return view('notifications.test'); 

    })->name('test.notification.view'); 

    // Test notification route 
    Route::post('/test-notification', [NotificationController::class, 'testNotification']) 

        ->name('test.notification'); 

    // Send custom notification 
    Route::post('/send-notification', [NotificationController::class, 'sendNotification']) 
        ->name('send.notification'); 
}); 

Step 8: Frontend Interface for Testing

8.1 Create Test Page

Create a simple test interface by adding this to your dashboard or creating a dedicated page:


@extends('layouts.app')
@section('content')

    <div class="card col-md-6 mx-auto shadow rounded p-4">
        <h2 class="h4 fw-bold mb-4">Push Notification Test</h2>

        <!-- Test Notification Button -->
        <div class="mb-4">
            <button id="testNotificationBtn" class="btn btn-primary">
                Send Test Notification
            </button>
        </div>

        <!-- Custom Notification Form -->
        <form id="notificationForm">
            @csrf
            <div class="mb-3">
                <label for="title" class="form-label">Title</label>
                <input type="text" id="title" name="title" required class="form-control">
            </div>

            <div class="mb-3">
                <label for="body" class="form-label">Message</label>
                <textarea id="body" name="body" required rows="3" class="form-control"></textarea>
            </div>

            <div class="mb-3">
                <label for="image_url" class="form-label">Image URL (optional)</label>
                <input type="url" id="image_url" name="image_url" class="form-control">
            </div>

            <button type="submit" class="btn btn-success">
                Send Custom Notification
            </button>
        </form>

        <!-- Status Messages -->
        <div id="statusMessage" class="mt-3 d-none">
            <div id="statusContent" class="alert"></div>
        </div>
    </div>
@endsection

<script>
document.getElementById('testNotificationBtn').addEventListener('click', async function() {
    const btn = this;
    const originalText = btn.textContent;

    btn.textContent = 'Sending...';
    btn.disabled = true;

    try {
        const response = await fetch('/test-notification', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            }
        });

        const result = await response.json();
        showStatus(result.message, result.success);
    } catch (error) {
        showStatus('Error: ' + error.message, false);
    } finally {
        btn.textContent = originalText;
        btn.disabled = false;
    }
});

// Custom Notification Form
document.getElementById('notificationForm').addEventListener('submit', async function(e) {
    e.preventDefault();

    const formData = new FormData(this);
    const submitBtn = this.querySelector('button[type="submit"]');
    const originalText = submitBtn.textContent;

    submitBtn.textContent = 'Sending...';
    submitBtn.disabled = true;

    try {
        const response = await fetch('/send-notification', {
            method: 'POST',
            headers: {
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
            },
            body: formData
        });

        const result = await response.json();
        showStatus(result.message, result.success);

        if (result.success) {
            this.reset();
        }
    } catch (error) {
        showStatus('Error: ' + error.message, false);
    } finally {
        submitBtn.textContent = originalText;
        submitBtn.disabled = false;
    }
});

function showStatus(message, success) {
    const statusDiv = document.getElementById('statusMessage');
    const statusContent = document.getElementById('statusContent');

    statusContent.textContent = message;
    statusContent.className = `${success ? 'text-success' : 'text-danger'}`;

    statusDiv.classList.remove('d-none');

    setTimeout(() => {
        statusDiv.classList.add('d-none');
    }, 5000);
}
</script>

Step 9: Advanced Usage Examples

9.1 Using the Facade Directly
use DevKandil\NotiFire\Facades\Fcm; 
use DevKandil\NotiFire\Enums\MessagePriority; 

// Simple notification 

$success = Fcm::withTitle('Hello World') 
    ->withBody('This is a test notification') 
    ->sendNotification($user->fcm_token); 

// Advanced notification with all options 

$success = Fcm::withTitle('New Order') 
    ->withBody('You have received a new order #1234') 
    ->withImage('https://example.com/order-image.jpg') 
    ->withIcon('https://example.com/icon.png') 
    ->withColor('#4F46E5') 
    ->withSound('default') 
    ->withPriority(MessagePriority::HIGH) 
    ->withAdditionalData([ 
        'order_id' => 1234, 
        'user_id' => $user->id, 
        'timestamp' => now()->toIso8601String() 
    ]) 
    ->sendNotification($user->fcm_token); 
9.2 Event-Based Notifications

Create an event listener for automatic notifications:

// app/Events/UserRegistered.php 

namespace App\Events; 

use Illuminate\Foundation\Events\Dispatchable; 

use App\Models\User; 

class UserRegistered 
{ 
    use Dispatchable; 
    public $user; 

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

// app/Listeners/SendWelcomeNotification.php 

namespace App\Listeners; 

use App\Events\UserRegistered; 
use App\Notifications\BrowserPushNotification; 

class SendWelcomeNotification 
{ 
    public function handle(UserRegistered $event) 
    { 
        if ($event->user->fcm_token) { 
            $event->user->notify(new BrowserPushNotification( 
                'Welcome to Our App!', 
                'Thank you for joining us. Get ready for an amazing experience!', 
                ['welcome' => true] 
            )); 
        } 
    } 
} 

Step 10: Testing & Troubleshooting

10.1 Common Issues & Solutions
Issue: Notifications not showing
  • Ensure browser permissions are granted
  • Check if FCM token is properly saved in the database
  • Verify Firebase configuration is correct
  • Check browser console for JavaScript errors
Issue: Service Worker not registering
  • Ensure firebase-messaging-sw.js is in the public directory
  • Check browser console for service worker errors
  • Verify Firebase configuration in service worker
Issue: FCM token not saving
  • Check if Sanctum token is properly set in meta tag
  • Verify CSRF token is included in requests
  • Check Laravel logs for any errors
Issue: Common SSL certificate issue in Windows/Laragon environments
Option 1:
  • Download the certificate file:
  • Update your php.ini:
    • In Laragon: Menu → PHP → Quick settings → php.ini
    • Find these lines and update them (remove semicolon if present):
      curl.cainfo = “C:\laragon\etc\ssl\cacert.pem”
      openssl.cafile = “C:\laragon\etc\ssl\cacert.pem”
  • Restart Laragon
Option 2: Quick Fix (Development Only)

1. Add this to your app/Providers/AppServiceProvider.php in the boot() method:

public function boot() 
{ 
    if (app()->environment('local')) { 
        // Disable SSL verification for development 
        $context = stream_context_create([ 
            "ssl" => [ 
                "verify_peer" => false, 
                "verify_peer_name" => false, 
            ], 
        ]); 
        stream_context_set_default($context); 
    } 
} 
10.2 Testing Commands
# Test Firebase connection 
php artisan tinker 
>>> app(DevKandil\NotiFire\Contracts\FcmServiceInterface::class)->withTitle('Test')->withBody('Test message')->sendNotification('your-fcm-token-here'); 

# Check logs 
tail -f storage/logs/laravel.log

Step 11: Production Considerations

11.1 Queue Configuration

For production, queue your notifications:

// In your notification class 
public function __construct($title, $body, $data = [], $imageUrl = null) 
{ 
    $this->title = $title; 
    $this->body = $body; 
    $this->data = $data; 
    $this->imageUrl = $imageUrl; 

    // Queue the notification 
    $this->onQueue('notifications'); 
} 
11.2 Rate Limiting

Implement rate limiting to prevent spam:

// In your controller 
use Illuminate\Support\Facades\RateLimiter; 

public function sendNotification(Request $request) 
{ 
    $key = 'send-notification:' . $request->user()->id; 
    if (RateLimiter::tooManyAttempts($key, 10)) { 
        return response()->json([ 
            'success' => false, 
            'message' => 'Too many notifications sent. Please try again later.' 
        ], 429); 
    } 

    RateLimiter::hit($key, 60); // 10 attempts per minute 
    // ... rest of your notification logic 
} 
11.3 Environment Variables

Ensure these are set in production:

QUEUE_CONNECTION=redis 
FIREBASE_PROJECT_ID=your-production-project-id 
APP_ENV=production

Conclusion

You now have a complete push notification system integrated with your Laravel authentication project. The system will:

  • Automatically request permission from users
  • Save FCM tokens to the database
  • Send beautiful push notifications
  • Handle both foreground and background notifications
  • Provide a testing interface for sending notifications
Author Image

Ashish Pithava

Ashish contributes to our development team with skills in PHP, Laravel, MySQL and JavaScript. He supports projects through module customization, bug fixing, and responsive UI enhancements, while ensuring clean and efficient code. With a strong learning mindset and problem-solving approach, Ashish plays a key role in delivering quality solutions for our clients.

Related Posts

A Word From Our Proud Clients

See what our most successful clients have to say about working with us...