PHPackages                             dive-be/laravel-expo-channel - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Mail &amp; Notifications](/categories/mail)
4. /
5. dive-be/laravel-expo-channel

AbandonedArchivedLibrary[Mail &amp; Notifications](/categories/mail)

dive-be/laravel-expo-channel
============================

Expo Notifications Channel for Laravel

2.0.0(2y ago)8811MITPHPPHP ~8.3

Since Aug 31Pushed 2y agoCompare

[ Source](https://github.com/dive-be/laravel-expo-channel)[ Packagist](https://packagist.org/packages/dive-be/laravel-expo-channel)[ Docs](https://github.com/dive-be/laravel-expo-channel)[ RSS](/packages/dive-be-laravel-expo-channel/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (9)Dependencies (8)Versions (11)Used By (0)

Important

This channel has finally moved to [where it belongs (Laravel Notification Channels project)](https://github.com/laravel-notification-channels/expo), and thus this repository has been abandoned. Please use the official channel from now on.

[![Social Card of Laravel Expo Channel](https://github.com/dive-be/laravel-expo-channel/raw/master/art/socialcard.png?raw=true)](https://github.com/dive-be/laravel-expo-channel/blob/master/art/socialcard.png?raw=true)

Expo Notifications Channel
==========================

[](#expo-notifications-channel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/4cf4e3340cc2785a802f42846f67a18e0c83dfbee3f2b24df953d8a6f7ce1a9f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f646976652d62652f6c61726176656c2d6578706f2d6368616e6e656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/dive-be/laravel-expo-channel)[![GitHub Tests Action Status](https://camo.githubusercontent.com/f39f71e5de6881f7e179b56c2bfa5407fd5b54d1a1edca76e147636d12c0d109/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f646976652d62652f6c61726176656c2d6578706f2d6368616e6e656c2f54657374733f6c6162656c3d7465737473)](https://github.com/dive-be/laravel-expo-channel/actions?query=workflow%3ATests+branch%3Amaster)[![Total Downloads](https://camo.githubusercontent.com/9f5ba27de6ed2096c0196f730d625ad6eb086051aac1ed96edb8da4855994e39/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f646976652d62652f6c61726176656c2d6578706f2d6368616e6e656c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/dive-be/laravel-expo-channel)

[Expo](https://docs.expo.dev/push-notifications/overview/) channel for pushing notifications to your React Native apps.

Contents
--------

[](#contents)

- [Disclaimer](#disclaimer)
- [Installation](#installation)
- [Additional Security](#additional-security-optional)
- [Usage](#usage)
- [Expo Message Request Format](#expo-message-request-format)
- [Testing](#testing)
- [Changelog](#changelog)
- [Contributing](#contributing)
- [Security](#security)
- [Credits](#credits)
- [License](#license)

Disclaimer
----------

[](#disclaimer)

This package is not (yet) part of the [Laravel Notification Channels](https://laravel-notification-channels.com) project, because the maintainer seems to be inactive and the [existing expo channel](https://github.com/laravel-notification-channels/expo/issues/1) has never been completed and is pretty much abandoned. This package respects all of the project's conventions (namespace, message creation ...), so a possible migration in the future should just be about replacing the package's name in your `composer.json`.

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require dive-be/laravel-expo-channel
```

Additional Security (optional)
------------------------------

[](#additional-security-optional)

You can require any push notifications to be sent with an additional [Access Token](https://docs.expo.dev/push-notifications/sending-notifications/#additional-security) before Expo delivers them to your users.

If you want to make use of this additional security layer, add the following to your `config/services.php` file:

```
'expo' => [
    'access_token' => env('EXPO_ACCESS_TOKEN'),
],
```

Usage
-----

[](#usage)

You can now use the `expo` channel in the `via()` method of your `Notification`s.

### Notification / `ExpoMessage`

[](#notification--expomessage)

First things first, you need to have a [Notification](https://laravel.com/docs/9.x/notifications) that needs to be delivered to someone. Check out the [Laravel documentation](https://laravel.com/docs/9.x/notifications#generating-notifications) for more information on generating notifications.

```
final class SuspiciousActivityDetected extends Notification
{
    public function toExpo($notifiable): ExpoMessage
    {
        return ExpoMessage::create('Suspicious Activity')
            ->body('Someone tried logging in to your account!')
            ->data($notifiable->only('email', 'id'))
            ->expiresAt(Carbon::now()->addHour())
            ->priority('high')
            ->playSound();
    }

    public function via($notifiable): array
    {
        return ['expo'];
    }
}
```

> **Note** Detailed explanation regarding the Expo Message Request Format can be found [here](#expo-message-request-format).

You can also apply conditionals to `ExpoMessage` without breaking the method chain:

```
public function toExpo($notifiable): ExpoMessage
{
    return ExpoMessage::create('Suspicious Activity')
        ->body('Someone tried logging in to your account!')
        ->when($notifiable->wantsSound(), fn ($msg) => $msg->playSound())
        ->unless($notifiable->isVip(), fn ($msg) => $msg->normal(), fn ($msg) => $msg->high());
}
```

### Notifiable / `ExpoPushToken`

[](#notifiable--expopushtoken)

Next, you will have to set a `routeNotificationForExpo()` method in your `Notifiable` model.

#### Unicasting (single device)

[](#unicasting-single-device)

The method **must** return either an instance of `ExpoPushToken` or `null`. An example:

```
final class User extends Authenticatable
{
    use Notifiable;

    protected $casts = ['expo_token' => ExpoPushToken::class];

    public function routeNotificationForExpo(): ?ExpoPushToken
    {
        return $this->expo_token;
    }
}
```

> **Warning** No notifications will be sent in case of `null`.

> **Note** More info regarding the model cast can be found [here](#model-casting).

#### Multicasting (multiple devices)

[](#multicasting-multiple-devices)

The method **must** return an `array` or `Collection`, the specific implementation depends on your use case. An example:

```
final class User extends Authenticatable
{
    use Notifiable;

    /**
    * @return Collection
    */
    public function routeNotificationForExpo(): Collection
    {
        return $this->devices->pluck('expo_token');
    }
}
```

> **Warning** No notifications will be sent in case of an empty `Collection`.

### Sending

[](#sending)

Once everything is in place, you can simply send a notification by calling:

```
$user->notify(new SuspiciousActivityDetected());
```

### Validation

[](#validation)

You ought to have an HTTP endpoint that associates a given `ExpoPushToken` with an authenticated `User` so that you can deliver push notifications. For this reason, we're also providing a custom validation `ExpoPushTokenRule` class which you can use to protect your endpoints. An example:

```
final class StoreDeviceRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'device_id' => ['required', 'string', 'min:2', 'max:255'],
            'token' => ['required', ExpoPushToken::rule()],
        ];
    }
}
```

### Model casting

[](#model-casting)

The `ExpoChannel` expects you to return an instance of `ExpoPushToken` from your `Notifiable`s. You can easily achieve this by applying the `ExpoPushToken` as a custom model cast. An example:

```
final class User extends Authenticatable
{
    use Notifiable;

    protected $casts = ['expo_token' => AsExpoPushToken::class];
}
```

This custom value object guarantees the integrity of the push token. You should make sure that [only valid tokens](#validation) are saved.

### Handling failed deliveries

[](#handling-failed-deliveries)

Unfortunately, Laravel does not provide an [OOB solution](https://github.com/laravel-notification-channels/channels/issues/16) for handling failed deliveries. However, there is a `NotificationFailed` event which Laravel does provide so you can hook into failed delivery attempts. This is particularly useful when an old token is no longer valid and the service starts responding with `DeviceNotRegistered` errors.

You can register an event listener that listens to this event and handles the appropriate errors. An example:

```
final readonly class HandleFailedExpoNotifications
{
    public function handle(NotificationFailed $event)
    {
        if ($event->channel !== 'expo') return;

        /** @var ExpoError $error */
        $error = $event->data;

        // Remove old token
        if ($error->type->isDeviceNotRegistered()) {
            $event->notifiable->update(['expo_token' => null]);
        } else {
            // do something else like logging...
        }
    }
}
```

The `NotificationFailed::$data` property will contain an instance of `ExpoError` which has the following properties:

```
final readonly class ExpoError
{
    private function __construct(
        public ExpoErrorType $type,
        public ExpoPushToken $token,
        public string $message,
    ) {}
}
```

Expo Message Request Format
---------------------------

[](#expo-message-request-format)

The `ExpoMessage` class contains the following methods for defining the message payload. All of these methods correspond to the available payload defined in the [Expo Push documentation](https://docs.expo.dev/push-notifications/sending-notifications/#message-request-format).

- [Badge (iOS)](#badge-ios)
- [Body](#body)
- [Category ID](#category-id)
- [Channel ID (Android)](#channel-id-android)
- [JSON data](#json-data)
- [Expiration](#expiration)
- [Mutable content (iOS)](#mutable-content-ios)
- [Notification sound (iOS)](#notification-sound-ios)
- [Priority](#priority)
- [Subtitle (iOS)](#subtitle-ios)
- [Title](#title)
- [TTL (Time to live)](#ttl-time-to-live)

### Badge (iOS)

[](#badge-ios)

Sets the number to display in the badge on the app icon.

```
badge(int $value)
```

> **Note**The value must be greater than or equal to 0.

### Body

[](#body)

Sets the message body to display in the notification.

```
body(string $value)
text(string $value)
```

> **Note**The value must not be empty.

### Category ID

[](#category-id)

Sets the ID of the notification category that this notification is associated with.

```
categoryId(string $value)
```

> **Note**The value must not be empty.

### Channel ID (Android)

[](#channel-id-android)

Sets the ID of the Notification Channel through which to display this notification.

```
channelId(string $value)
```

> **Note**The value must not be empty.

### JSON data

[](#json-data)

Sets the JSON data for the message.

```
data(Arrayable|Jsonable|JsonSerializable|array $value)
```

> **Warning**We're compressing JSON payloads that exceed 1 KiB using Gzip (if `ext-zlib` is available). While you could technically send more than 4 KiB of data, this is not recommended.

### Expiration

[](#expiration)

Sets the expiration time of the message. Same effect as TTL.

```
expiresAt(DateTimeInterface|int $value)
```

> **Warning**`TTL` takes precedence if both are set.

> **Note**The value must be in the future.

### Mutable content (iOS)

[](#mutable-content-ios)

Sets whether the notification can be intercepted by the client app.

```
mutableContent(bool $value = true)
```

### Notification sound (iOS)

[](#notification-sound-ios)

Play the default notification sound when the recipient receives the notification.

```
playSound()
```

> **Warning**Custom sounds are not supported.

### Priority

[](#priority)

Sets the delivery priority of the message.

```
priority(string $value)
default()
normal()
high()
```

> **Note**The value must be `default`, `normal` or `high`.

### Subtitle (iOS)

[](#subtitle-ios)

Sets the subtitle to display in the notification below the title.

```
subtitle(string $value)
```

> **Note**The value must not be empty.

### Title

[](#title)

Set the title to display in the notification.

```
title(string $value)
```

> **Note**The value must not be empty.

### TTL (Time to live)

[](#ttl-time-to-live)

Set the number of seconds for which the message may be kept around for redelivery.

```
ttl(int $value)
expiresIn(int $value)
```

> **Warning**Takes precedence over `expiration` if both are set.

> **Note**The value must be greater than 0.

Testing
-------

[](#testing)

```
composer test
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security
--------

[](#security)

If you discover any security related issues, please email  instead of using the issue tracker.

Credits
-------

[](#credits)

- [Muhammed Sari](https://github.com/mabdullahsari)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity16

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~62 days

Recently: every ~139 days

Total

10

Last Release

788d ago

Major Versions

0.4 → 1.0.02022-09-02

1.3.0 → 2.0.02024-03-15

PHP version history (3 changes)0.1PHP ^8.1

1.2.0PHP ~8.2

1.3.0PHP ~8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/a265853c8db7c699f64f523ee782dc5ec095bfb456379222d1b0b97d90e4c735?d=identicon)[dive](/maintainers/dive)

---

Top Contributors

[![mabdullahsari](https://avatars.githubusercontent.com/u/24608797?v=4)](https://github.com/mabdullahsari "mabdullahsari (28 commits)")

---

Tags

laravelnotificationsexpoexponentdivereact-native

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/dive-be-laravel-expo-channel/health.svg)

```
[![Health](https://phpackages.com/badges/dive-be-laravel-expo-channel/health.svg)](https://phpackages.com/packages/dive-be-laravel-expo-channel)
```

###  Alternatives

[laravel-notification-channels/telegram

Telegram Notifications Channel for Laravel

1.1k3.4M35](/packages/laravel-notification-channels-telegram)[s-ichikawa/laravel-sendgrid-driver

This library adds a 'sendgrid' mail driver to Laravel.

4139.3M1](/packages/s-ichikawa-laravel-sendgrid-driver)[spatie/laravel-health

Monitor the health of a Laravel application

85810.0M83](/packages/spatie-laravel-health)[laravel-notification-channels/discord

Laravel notification driver for Discord.

2371.3M11](/packages/laravel-notification-channels-discord)[benwilkins/laravel-fcm-notification

Laravel FCM (Firebase Cloud Messaging) Notification Channel

210964.1k1](/packages/benwilkins-laravel-fcm-notification)[laravel-notification-channels/expo

Expo Notifications Channel for Laravel

64426.3k1](/packages/laravel-notification-channels-expo)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
