Local Notifications Plugin for NativePHP Mobile#
Send, schedule, and manage local notifications in NativePHP Mobile apps. Supports immediate and delayed notifications, recurring schedules, action buttons, tap events with URL navigation, custom sounds, badge counts, and silent delivery.
Works on iOS and Android.
Installation#
composer require nativephp/mobile-local-notifications
Quick Start#
use NativePHP\LocalNotifications\Facades\LocalNotifications; // Request permission firstLocalNotifications::requestPermission(); // Send a notificationLocalNotifications::send('hello') ->title('Hello!') ->body('World'); // Schedule a daily recurring notificationLocalNotifications::schedule('daily-practice') ->title('Practice Time') ->body('Time for your daily session!') ->dailyAt('09:00');
No terminal method needed — chains auto-fire via __destruct.
Sending Notifications#
// SimpleLocalNotifications::send('hello') ->title('Hello!')->body('World'); // With URL navigation on tapLocalNotifications::send('shipped') ->title('Order Shipped')->body('On the way!') ->url('/orders/123'); // Delayed (fires after N seconds)LocalNotifications::send('reminder') ->title('Meeting Soon')->delay(300); // With action buttons (max 3)LocalNotifications::send('friend-request') ->title('Friend Request')->body('John wants to connect') ->action('Accept', '/friends/accept/42') ->action('Decline', destructive: true) ->action('View Profile', '/users/42'); // Silent (no sound or vibration)LocalNotifications::send('sync') ->title('Synced')->body('Data synced')->silent(); // Badge (iOS auto-increments, Android managed by OS)LocalNotifications::send('msg') ->title('New Message')->body('You have mail')->badge(); // Custom soundLocalNotifications::send('alert') ->title('Alert')->body('Check this out') ->sound('mario'); // Custom data payloadLocalNotifications::send('order') ->title('Order Ready') ->data(['order_id' => 55, 'type' => 'pickup']);
Custom Sounds#
Place audio files in your app's resources/sounds/ directory:
resources/ sounds/ mario.mp3 chime.wav alert.caf
Reference by name without extension:
LocalNotifications::send('alert') ->title('Alert') ->sound('mario');
Supported formats: .mp3, .wav, .caf, .aiff, .m4a
Files are automatically copied to the native app bundle during build. On iOS, the extension is auto-resolved from the bundle. On Android, a dedicated notification channel is created per sound.
Recurring Schedules#
// Daily at a specific timeLocalNotifications::schedule('morning') ->title('Good Morning')->body('Start your day!') ->dailyAt('09:00'); // Weekly on specific daysLocalNotifications::schedule('standup') ->title('Standup')->body('Starting soon') ->weeklyOn([1,2,3,4,5], '09:55'); // MonthlyLocalNotifications::schedule('rent') ->title('Rent Due')->body('Pay today') ->monthlyOn(1, '10:00');
All Frequency Methods#
| Method | Description |
|---|---|
hourly() |
Every hour |
hourlyAt(15) |
Every hour at minute 15 |
daily() |
Every day at midnight |
dailyAt('09:00') |
Every day at 9:00 AM |
weekly() |
Every week on Sunday at midnight |
weeklyOn(1, '09:00') |
Every Monday at 9:00 AM |
weeklyOn([1,3,5], '09:00') |
Mon, Wed, Fri at 9:00 AM |
monthly() |
First of every month at midnight |
monthlyOn(15, '09:00') |
15th of every month at 9:00 AM |
yearly() |
January 1st at midnight |
yearlyOn(3, 15, '09:00') |
March 15th at 9:00 AM |
everyFifteenMinutes() |
Every 15 minutes |
everyThirtyMinutes() |
Every 30 minutes |
twiceDaily(8, 20) |
Twice daily at 8:00 AM and 8:00 PM |
CRUD for Schedules#
$all = LocalNotifications::scheduled(); // List all$entries = LocalNotifications::get('morning'); // Get by IDLocalNotifications::edit('morning')->dailyAt('10:00'); // EditLocalNotifications::remove('morning'); // RemoveLocalNotifications::cancel('id'); // Cancel specificLocalNotifications::cancelAll(); // Cancel allLocalNotifications::clearBadge(); // Clear badge (iOS)
Deploy-Time Schedules#
Define in routes/console.php for notifications that run on a fixed schedule regardless of user input:
use Illuminate\Support\Facades\Schedule; Schedule::notification('standup') ->title('Standup')->dailyAt('09:55'); Schedule::notification('hydrate') ->title('Drink Water')->everyThirtyMinutes();
Action Buttons#
->action(string $label, ?string $url = null, bool $destructive = false, array $data = [])
- label — Button text. Auto-generates a slug identifier.
- url — Navigates on tap. Falls back to notification-level
url(). - destructive — Red button on iOS. No visual difference on Android.
- data — Per-action data merged with notification-level
data()in tap events. - Max 3 per notification.
Tap Behavior#
| Scenario | Navigation | actionIdentifier |
|---|---|---|
| Tap body, no URL | Open app | null |
Tap body, has url() |
Navigate to URL | null |
| Tap action with URL | Navigate to action URL | slug of label |
Tap action without URL, has url() |
Navigate to notification URL | slug of label |
Tap action without URL, no url() |
Open app | slug of label |
Cold start: URL navigation works even when the app is killed.
Events#
PHP (Livewire)#
use Native\Mobile\Attributes\OnNative;use NativePHP\LocalNotifications\Events\NotificationTapped;use NativePHP\LocalNotifications\Events\PermissionGranted; #[OnNative(NotificationTapped::class)]public function handleTap(string $id, ?string $actionIdentifier = null, array $data = [], ?string $url = null){ // Handle notification tap} #[OnNative(PermissionGranted::class)]public function handlePermission(bool $granted){ // Handle permission result}
JavaScript#
import { LocalNotifications, Events } from '#local-notifications';import { On, Off } from '#nativephp'; On(Events.NotificationTapped, ({ id, actionIdentifier, data, url }) => { console.log('Tapped:', id);}); On(Events.PermissionGranted, ({ granted }) => { console.log('Permission:', granted);});
JavaScript API#
Mirrors the PHP API.
import { LocalNotifications } from '#local-notifications'; // Sendawait LocalNotifications.send('hello').title('Hi').body('World');await LocalNotifications.send('delayed').title('Soon').delay(10);await LocalNotifications.send('custom').title('Alert').sound('mario'); // Scheduleawait LocalNotifications.schedule('daily').title('Practice').dailyAt('09:00');await LocalNotifications.schedule('mwf').title('Gym').weeklyOn([1,3,5], '08:00'); // CRUDconst all = await LocalNotifications.scheduled();await LocalNotifications.remove('daily');await LocalNotifications.cancel('id');await LocalNotifications.cancelAll();await LocalNotifications.clearBadge();await LocalNotifications.requestPermission();
Laravel Notification Channel#
class OrderShipped extends Notification { use HasMobileNotification; public function via($notifiable) { return [...$this->viaMobile()]; } public function toMobile($notifiable) { return MobileMessage::create() ->title('Shipped')->body('Your order shipped') ->url('/orders/'.$this->order->id) ->action('Track', '/orders/track/'.$this->order->id); }}
Platform Notes#
| Feature | iOS | Android |
|---|---|---|
sound('name') |
Custom sound from bundle | Custom sound via dedicated channel |
badge() |
Increments badge count | Badge dots managed by OS |
clearBadge() |
Resets badge to zero | No effect |
destructive: true |
Red button text | No visual difference |
silent() |
No sound or vibration | No sound or vibration |
channelId() |
No effect | Sets notification channel |
subtitle() |
Below title | Below title |
| Persistence | OS is source of truth | SQLite database |
Permissions#
Android#
POST_NOTIFICATIONS— Required on API 33+SCHEDULE_EXACT_ALARM/USE_EXACT_ALARM— For precise schedulingRECEIVE_BOOT_COMPLETED— Re-register alarms after reboot
iOS#
.alert,.sound,.badgeviaUNUserNotificationCenter
Weekday Reference#
0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday
License#
MIT License. See LICENSE for details.