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 Fcm Laravel Package

apility/laravel-fcm

A Laravel package for sending Firebase Cloud Messaging (FCM) push notifications to Android and iOS devices, with a simple API for targeting devices and topics, building payloads, and integrating FCM into Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

apility/laravel-fcm is a maintained-fork lineage of brozot/laravel-fcm, so if you have used that package before, the API is identical (only the namespace changes to Apility\LaravelFCM). It wraps the legacy FCM HTTP API (fcm.googleapis.com/fcm/send) with builder objects, a facade, and a response wrapper that categorises tokens.

First, confirm the API is still reachable for your project. The package is archived and speaks the legacy server-key API, which Google shut down during 2024. If you are starting fresh, this package is not the right tool — migrate to the HTTP v1 API (e.g. kreait/firebase-php or laravel-notification-channels/fcm). The guidance below applies to working with existing installations.

Install and publish config:

composer require apility/laravel-fcm
php artisan vendor:publish --provider="Apility\LaravelFCM\FCMServiceProvider"

Then set credentials from the Firebase console (Project settings → Cloud Messaging → legacy server key):

FCM_SERVER_KEY=AAAA...
FCM_SENDER_ID=1234567890

Register the service provider only if you are on a Lumen-style setup or have auto-discovery disabled. Look first at config/fcm.php — forks in this lineage differ on key names (server_key, sender_id, http_client), so read the file rather than trusting an article.

First use case — send a notification to one device token:

use Apility\LaravelFCM\Facades\FCM;
use Apility\LaravelFCM\Message\OptionsBuilder;
use Apility\LaravelFCM\Message\PayloadNotificationBuilder;
use Apility\LaravelFCM\Message\PayloadDataBuilder;

$option = (new OptionsBuilder())->setTimeToLive(60 * 60)->build();

$notification = (new PayloadNotificationBuilder('Order shipped'))
    ->setBody('Your parcel is on its way')
    ->setSound('default')
    ->setClickAction('OPEN_ORDERS')
    ->build();

$data = (new PayloadDataBuilder())->addData(['order_id' => '1234'])->build();

$response = FCM::sendTo($deviceToken, $option, $notification, $data);

$response->numberSuccess(); // 1

Implementation Patterns

Wrap it in a service, don't call the facade from controllers. The builder API is verbose; hide it behind something like PushService::toUser($user, $notification) that resolves device tokens from your device_tokens table and calls FCM::sendTo().

Multicast to many tokens. Pass an array as the first argument; the package batches the call:

$response = FCM::sendTo($tokens, $option, $notification, $data);

FCM caps a multicast request at 1,000 registration tokens. The package does not always chunk for you, so chunk on your side (array_chunk($tokens, 1000)) to avoid silent truncation.

Prune dead tokens from the response. This is the single most valuable part of the package and the main thing you should build on day one:

$response = FCM::sendTo($tokens, $option, $notification, $data);

if ($response->tokensToDelete()) {
    DeviceToken::whereIn('token', $response->tokensToDelete())->delete();
}
if ($response->tokensToModify()) {
    foreach ($response->tokensToModify() as $old => $new) {
        DeviceToken::where('token', $old)->update(['token' => $new]);
    }
}
if ($response->tokensToRetry()) {
    // transient errors — requeue these tokens
}

Wrap this in a small HandleFcmResponse action so every call site gets consistent cleanup.

Topics for fan-out. Subscribe tokens to a topic rather than fanning out per-token when you push to segments:

FCM::subscribeToTopic('news', $tokens);
FCM::unsubscribeFromTopic('news', $tokens);

Subscription changes are not instantaneous — Google advises waiting a moment (or sending to a fresh subscription on the next push) before expecting delivery to new subscribers.

Queue it. FCM calls are network I/O; do them in a queued job with retries and exponential backoff. The package does not wrap itself in a queue, so a ShouldQueue job that catches FCMMessageException and re-throws on retryable errors is standard.

Data-only vs. notification payloads. Use PayloadNotificationBuilder for messages the OS renders, PayloadDataBuilder (or both) for silent messages your app handles. All data values must be strings — cast ints and booleans explicitly or json_encode them.

Testing. The facade is swappable: FCM::shouldReceive('sendTo')->andReturn($fakeDownstreamResponse);. Build fake DownstreamResponse objects in tests rather than mocking Guzzle, if the class is constructible.

Gotchas and Tips

  • The archived status is the headline risk. No patches, no Laravel 11 / PHP 8.3+ guarantees, and no path to the HTTP v1 API. Check the constraint in composer.json; on newer Laravel you may need a composer.json conflict override or a fork. Budget a migration, don't plan around this package long-term.
  • The legacy API shutdown. Server-key auth (key=... header) was retired for new projects and stopped working for most existing ones in 2024. If sends return 401/403, check whether your project still has legacy credentials before digging into the code — many "package bug" reports are actually credential retirement.
  • config/fcm.php is not cached consistently across forks. After editing FCM_SERVER_KEY, run php artisan config:clear. Symptom of a stale cache: requests going to an old project or 401s after a key rotation.
  • sender_id matters less than you'd think. It's used in some upstream paths, not in the downstream send. Don't chase issues there unless you're doing topic/group operations.
  • Exceptions are not granular by default. FCMMessageException is thrown for any non-2xx. Log getMessage() and the raw response ($downstreamResponse->getRawResponse() if available on your fork) — the raw body is where the real error code lives.
  • Time-to-live is your friend. Set TTL on options rather than letting notifications arrive hours late. setTimeToLive(0) means "deliver immediately or drop."
  • Collapse keys. For chat-style notifications, ->setCollapseKey('inbox') prevents stacking N notifications when a user is offline.
  • Guzzle timeout. The default HTTP client timeout can be too aggressive on large multicasts. Check the http_client block in config and raise the timeout; otherwise you get spurious retries and duplicate sends.
  • Token format. tokensToDelete() covers NotRegistered and InvalidRegistration. Do not hand-roll this check — the package classifies the error codes for you and gets the edge cases right.
  • Lumen. Register Apility\LaravelFCM\FCMServiceProvider manually and copy the config file into your config/ with the facade alias set in bootstrap/app.php; auto-discovery does not apply.
  • Extension point. If you need to intercept HTTP traffic (auth headers, proxies), the service provider binds a Guzzle client through the container — override that binding rather than editing vendor files.
  • Verify method names against the source. This lineage has drifted across forks (sendToTopic, sendToGroup, isValidRegistrationToken exist in some versions and not others). Read vendor/apility/laravel-fcm/src/ once; it is small and will save you an hour of guessing.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource