sprintechnology/firebase-cloud-messaging
Send Firebase Cloud Messaging push notifications from Laravel and PHP with a simple, framework-friendly API. Configure FCM credentials, target devices or topics, and deliver notifications to Android, iOS, and web clients.
This is a small, unmaintained-looking bundle (0 stars, 0 dependents) that wraps Firebase Cloud Messaging for Laravel. Treat it as reference code you audit before relying on, not as a battle-tested dependency.
composer require sprintechnology/firebase-cloud-messaging
php artisan vendor:publish --provider="Sprintechnology\FirebaseCloudMessaging\FirebaseCloudMessagingServiceProvider"
First things to inspect after install, in order:
src/ – open the service provider and any Facade class. This tells you the real public API (method names, whether it's a static facade, a bound singleton, or a thin HTTP client wrapper). The README on a 0-star package is often out of date with the code.config/firebase-cloud-messaging.php (or similarly named) – where the server key / credentials live. Confirm whether it expects the legacy server key (AAAA...) or a service-account JSON for the HTTP v1 API. This single detail determines whether the package still works at all (see Gotchas).composer.json – check require constraints (illuminate/support, guzzlehttp/guzzle) and the autoload namespace so you can type-hint the class correctly.First use case: send a single notification to one device token, then verify the raw response.
// pseudo-code based on the typical shape of this kind of bundle — verify against src/
use Sprintechnology\FirebaseCloudMessaging\Facades\FirebaseCloudMessaging;
$response = FirebaseCloudMessaging::send($deviceToken, [
'title' => 'Order shipped',
'body' => 'Tracking: 1Z999...',
]);
// Always dump this once in tinker before building on it
dump($response->status(), $response->json()); // e.g. 200 + { "success": 1, "failure": 0, ... }
Run it from php artisan tinker against a real device token you control. Do not wire it into a queued job until you have seen one successful and one deliberately-failed (bogus token) response, because the error shapes are what your retry logic will key off.
Wrap it, don't call it directly. Because the package is thin and lightly maintained, put a single FcmService (or a SendsPushNotifications trait) between your app and the bundle. When the underlying client breaks or you migrate to kreait/laravel-firebase, you change one class instead of grepping 40 call sites.
Fan-out with explicit token lists. Firebase accepts up to 1000 registration tokens per multicast request. The typical loop:
User::whereNotNull('fcm_token')->chunk(500, function ($users) {
$tokens = $users->pluck('fcm_token')->all();
$result = FirebaseCloudMessaging::sendToMultiple($tokens, $payload);
$this->handleFailures($result->json('results', []), $tokens);
});
Correlate failures back to tokens by index. In legacy-API responses, results[i] corresponds to $tokens[i]. Persist $tokens[i] when results[i]['error'] is NotRegistered or InvalidRegistration and null the column out — that's your free token hygiene.
Queue everything. FCM is a network hop with a 30s+ worst case. Dispatch a SendPushNotification job (ShouldQueue, tries=3, backoff=[10, 60, 300]) and ->onQueue('notifications'). The package itself rarely ships queue-aware code; you supply it.
Bridge into Laravel Notifications. Laravel's built-in Notification channel contract is the cleanest abstraction. Write a FcmChannel that delegates to the package, then $user->notify(new OrderShipped($order)). This keeps domain code unaware of the bundle and lets you swap the driver.
Topic messaging for broadcast. If the package exposes it, sendToTopic('news', $payload) is far cheaper than N single sends for announcements. Remember clients must subscribe to the topic from the Firebase SDK — you can't subscribe them server-side.
Data vs notification payloads. Prefer a notification block for OS-rendered pushes, and a data block for the app to act on. If you need silent/background delivery, send data-only — but be aware iOS throttles those aggressively.
Config caching. Anything your .env feeds (server key, project ID) must be read inside the package's config file, not via env() calls scattered in src/. Run php artisan config:clear after editing .env, then confirm with config('firebase-cloud-messaging.*') in tinker.
The legacy FCM API is dead. Google shut down the https://fcm.googleapis.com/fcm/send endpoint and legacy server keys in mid-2024. Any package whose config asks for a "server key" string is either broken or needs a rewrite to HTTP v1 (https://fcm.googleapis.com/v1/projects/{project}/messages:send) with an OAuth2 bearer token minted from a service-account JSON. Check src/ for the URL it actually hits before you invest time. If it's /fcm/send, plan the migration now.
Zero stars, zero dependents means zero safety net. Nobody has filed the bugs you're about to hit. Read every line of the send path — it's probably under 200 lines — and be prepared to fork. Pin it to an exact version in composer.json ("sprintechnology/firebase-cloud-messaging": "1.0.3"), not ^1.0, so a silent upstream change can't break prod.
Data payload values must be strings. ['order_id' => 12345] will be rejected or coerced unexpectedly. Cast: ['order_id' => (string) $order->id]. Same for booleans ('true'/'false').
4 KB payload ceiling. Notification + data combined. Base64 inflates by ~33%. If you're pushing an avatar URL plus metadata, measure it. The error is a 400 INVALID_ARGUMENT with a message that doesn't say "too big."
Token errors you must handle explicitly:
UNREGISTERED / NotRegistered → delete the token from your DB.INVALID_ARGUMENT → usually a malformed payload, not a bad token; check your structure.SENDER_ID_MISMATCH → token was issued for a different Firebase project; your config creds are wrong.UNAVAILABLE / 503 → retry with backoff.Never log the full response in bulk. Some Firebase error messages echo back parts of the payload and token. Log the error code and a truncated token hash instead.
Guzzle timeouts are unset by default. The package may construct a Client with no timeout. If a push hangs, your whole queue worker stalls. Either the package exposes a timeout config or you wrap the call: Http::timeout(10) / construct your own client and inject.
iOS vs Android divergence. Android respects priority: high for immediate delivery; iOS uses apns-priority headers (10 immediate, 5 power-saving) which the package almost certainly doesn't expose. Expect to fork or drop to raw HTTP for APNs-specific tuning.
Test against the FCM emulator or a scratch project. Sending to production tokens during development wastes quota and can trigger project-level rate limits. Create a firebase-test project, a separate service account, and point .env at it locally. Gate real sends behind if (app()->isProduction()).
Consider the mature alternatives before committing. If you only need "send a push in Laravel," kreait/laravel-firebase (actively maintained, HTTP v1, full Admin SDK) or laravel-notification-channels/fcm are safer choices. This bundle only makes sense if you specifically need its minimal surface, or you're already forked and patched. Budget a day for reading + forking as part of your estimate.
How can I help you explore Laravel packages today?