Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Firebase Laravel Package

tomatophp/laravel-firebase

Laravel Firebase integration for connecting your app to Firebase services with less boilerplate. Includes configuration helpers and a clean API for authentication, Firestore data, and cloud messaging, making it easy to send notifications and sync records from your Laravel or Tomatophp panel.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a ~thin Laravel service provider around kreait/firebase-php, the community Firebase Admin SDK for PHP. Almost every line you write will be kreait SDK code. The wrapper's only real job is: read config/firebase.php, build a Kreait\Firebase\Factory, and bind the resulting clients into Laravel's container. Understand that in ten minutes and the rest of your day is spent in kreait's docs, not this repo's.

Bootstrap path (about 15 minutes):

composer require tomatophp/laravel-firebase
php artisan vendor:publish --tag=firebase-config   # verify the tag against the provider

Then generate a service account in the Firebase console: Project settings → Service accounts → Generate new private key. Store the JSON outside the web root:

storage/app/firebase/service-account.json

and point config at it, ideally with an absolute path computed in config/firebase.php:

FIREBASE_CREDENTIALS=/var/www/app/storage/app/firebase/service-account.json
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_DATABASE_URL=https://your-project-id.firebaseio.com

Where to look first, in this exact order. The whole package is small enough that reading it beats reading any docs:

  1. config/firebase.php — tells you every key the package reads and which Factory::with*() methods it chains (withServiceAccount, withProjectId, withDatabaseUri, cache, HTTP client).
  2. src/*ServiceProvider.php — the real contract. This shows what class is bound, whether it's a singleton, and which aliases/facade point at it. Read this before you trust the facade in the README.
  3. The facade class — confirm whether it resolves to Kreait\Firebase\Contract\Messaging, the Firebase aggregate, or the raw Factory. These have different methods and you will waste time if you assume the wrong one.

First use case — send one FCM push. This is why most people install it:

use Kreait\Firebase\Contract\Messaging;
use Kreait\Firebase\Messaging\CloudMessage;

final class OrderNotifier
{
    public function __construct(private Messaging $messaging) {}

    public function shipped(string $deviceToken, string $orderId): void
    {
        $this->messaging->send(
            CloudMessage::fromArray([
                'token'        => $deviceToken,
                'notification' => ['title' => 'Order shipped', 'body' => "#{$orderId} is on the way"],
                'data'         => ['order_id' => $orderId],
            ])
        );
    }
}

If Messaging doesn't resolve from the container, fall back to whatever the provider does bind — usually the Factory — and call ->createMessaging() off it. Confirm against the provider source, not the description file.

Verify before building a feature: in php artisan tinker, resolve the binding and dump the class. That single check saves you from writing an abstraction against the wrong type.

Implementation Patterns

Inject contracts; never call the facade in application code. Messaging, Auth, FirestoreClient, Database all resolve from the container when the provider is correct. Injection keeps your class unit-testable and keeps the package at the edge.

// ✅ container-resolved, mockable
public function __construct(private Messaging $messaging) {}

// ❌ static, global, hostile to tests
Firebase::messaging()->send($message);

Wrap Firebase behind your own interface on day one. Given the adoption signal (1 star, no dependents, single maintainer, last release March 2024), the wrapper is a starter kit, not a dependency you want threaded through 40 files. One interface, one adapter, one binding:

interface PushNotifier { public function send(string $token, array $payload): void; }

final class FirebasePushNotifier implements PushNotifier
{
    public function __construct(private Messaging $messaging) {}

    public function send(string $token, array $payload): void
    {
        $this->messaging->send(CloudMessage::fromArray(['token' => $token] + $payload));
    }
}

// AppServiceProvider::register()
$this->app->bind(PushNotifier::class, FirebasePushNotifier::class);

Now replacing the package with a direct kreait/firebase-php binding — which is ~20 lines in AppServiceProvider — is a one-file change. Do this before the first feature ships; retrofitting it across a codebase is the expensive version.

Queue everything that touches FCM. Pushes are network I/O against a rate-limited API. Never loop send() inside a request cycle.

final class SendPushJob implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public int $tries = 3;
    public array $backoff = [10, 60, 300];

    public function __construct(public string $token, public array $payload) {}

    public function handle(PushNotifier $notifier): void
    {
        $notifier->send($this->token, $this->payload);
    }
}

Dispatch it from a domain event listener (OrderShipped::dispatch($order) → listener → SendPushJob::dispatch(...)). That's the Laravel-native shape, and it keeps your controllers thin.

Batch instead of looping. Messaging has sendAll(array $messages) with a hard 500 cap. Chunk larger audiences:

foreach (array_chunk($messages, 500) as $chunk) {
    $this->messaging->sendAll($chunk);
}

sendAll returns a report object — iterate it and branch on failures, don't just count successes.

Verify ID tokens at a single boundary. For a Firebase-Auth client with a Laravel backend, verify once in middleware and mint a local session:

public function handle(Request $request, Closure $next)
{
    $token = $request->bearerToken();
    abort_unless($token, 401);

    try {
        $claims = app(Auth::class)->verifyIdToken($token)->claims();
    } catch (\Throwable $e) {
        abort(401, 'Invalid Firebase token');
    }

    $user = User::firstOrCreate(
        ['firebase_uid' => $claims['sub']],
        ['email' => $claims['email'] ?? null],
    );

    Auth::login($user);

    return $next($request);
}

Everything downstream uses normal Laravel auth. Do not scatter verifyIdToken calls through controllers.

Roles via custom claims. Set them server-side, read them client-side:

app(Auth::class)->setCustomUserClaims($uid, ['admin' => true]);

Claims are baked in at token issue time. Clients must refresh (or force re-auth) before new claims take effect — this trips people up constantly, because the server is "correct" and the client is still stale for up to an hour.

Multi-environment by config, multi-tenant by hand. One service account per environment, path resolved from env(), is the happy path. If you need two Firebase projects simultaneously (staging + prod, or per-tenant isolation), don't fight the wrapper's single-project assumption — build separate Factory instances yourself and register them as named singletons:

$this->app->singleton('firebase.tenant_b', fn () => (new Factory)
    ->withServiceAccount(storage_path('firebase/tenant-b.json'))
    ->withProjectId('tenant-b')
    ->createMessaging());

Artisan commands for ops. User lookups, bulk claim assignment, topic subscription management, and stale-token pruning are perfect php artisan firebase:* commands. This is where the package pays for itself operationally — no ad-hoc scripts against the console.

Gotchas and Tips

Treat the wrapper as unmaintained. Last release March 2024, one maintainer, zero dependents. Pin the exact version ("tomatophp/laravel-firebase": "1.0.0"), not ^1.0, and keep a fork-and-patch runbook. The code is small — you can vendor it into app/ in an afternoon if needed. The risk is abandonment, not lock-in.

The pinned kreait/firebase-php version is what will actually break you. kreait ships breaking changes on majors (credential factories, client construction, component interfaces). Check before you commit to the wrapper:

composer show tomatophp/laravel-firebase   # inspect `requires`
composer why kreait/firebase-php

If the resolved kreait version is too old for what you need (newer FCM HTTP v1 features, emulator support, Firestore query builders), skip the wrapper entirely and bind the SDK yourself. Same capability, actively maintained, ~20 lines.

Credential resolution failure modes — memorize these three:

  • Relative paths resolve against the process CWD, not the app root. A queue worker started from a different directory will fail where php artisan serve succeeded. Always use base_path()/storage_path() in config.
  • Config cache freezes env() reads. After any credential change: php artisan config:clear (dev) and re-run config:cache (prod). Editing .env while a stale cached config is live is the classic hour-long debugging session.
  • Private-key newlines. A JSON file is fine. If you switch to env-var credentials, \n in private_key usually needs re-expansion — str_replace('\n', "\n", ...) in the config file.

Config and facade collisions. If kreait/laravel-firebase is or was also installed, you get conflicting config/firebase.php keys and possibly a duplicate facade alias. Remove one before debugging the other. Symptoms are confusing: valid credentials, "permission denied" errors, or silent fallback to no auth.

Octane / long-lived workers. The client is a singleton — great for performance, but credential and token caching is per-worker. Rotate a service account and running workers keep the old one until reboot. Also grep for any new Factory inside request paths; the convenience of the wrapper evaporates if someone instantiates a fresh client per call.

Silent delivery failures are the default failure mode. FCM happily accepts a message for an unregistered token. Wrap your send and inspect the report:

$report = $this->messaging->sendAll($chunk);

foreach ($report->failures()->getItems() as $failure) {
    $isGone = $failure->error() instanceof Unregistered || $failure->error() instanceof NotFound;
    if ($isGone) {
        DeviceToken::where('token', $failure->target()->value())->delete();
    }
}

Add a scheduled validateRegistrationTokens() sweep for stale device tokens. Without pruning, your failure rate climbs quietly and your dashboards lie.

Testability requires the interface upfront. The facade encourages static calls; write them and you cannot mock the messaging layer without container gymnastics. Set up PushNotifier before the first feature, not after — and in tests, swap the binding with $this->mock(PushNotifier::class) rather than mocking kreait internals.

Debugging opaque errors. Firebase errors surface as Kreait\Firebase\Exception\Messaging\* subclasses with uneven specificity. Log the raw message plus a prefix of the token (never the whole token) so you can correlate without leaking credentials into logs.

Extension points to know cold. The Factory is the entire surface: withServiceAccount(), withProjectId(), withDatabaseUri(), withHttpClient(), withVerifierCache(), createMessaging(), createAuth(), createFirestore(). If the wrapper doesn't expose something — emulator mode via FIREBASE_EMULATOR_HOST, custom HTTP clients for proxies, Firestore query builders — don't wait for a release. Instantiate the Factory yourself in a service provider and bind exactly what you need. Testing against the Firebase emulator is otherwise effectively impossible through a stale wrapper, and that alone is usually reason enough to go direct.

Realistic budget: half a day to first working push, assuming clean credentials. The ramp is small. The non-obvious cost is Firebase console setup (service account, database URL, FCM config for each platform) and the fact that this package ships no worked examples — you'll be living in kreait's docs regardless. If your Laravel/PHP versions don't line up cleanly with the pinned kreait version, choose the direct-SDK binding from the start: same capability, actively maintained, and you keep every extension point.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core