In this tutorial, we’ll walk you through how to implement local notifications in a modern Ionic 8 and Angular 20 application using Capacitor. This is an updated version of the original Ionic 3 and Cordova tutorial, reworked with the latest tools and best practices in 2025.
Local notifications are an essential feature in many mobile apps — they allow your app to notify users of events, reminders, or updates even when the app isn’t active. With Capacitor’s modern API, integrating local notifications into Ionic has become simpler, more consistent across platforms, and fully TypeScript-friendly.
By the end of this tutorial, you’ll have a fully functional Ionic app that:
-
Requests user permission for notifications.
-
Schedules and triggers local notifications.
-
Handles notification events (e.g., tapping or dismissing).
-
Works seamlessly on both Android and iOS using Capacitor.
What You’ll Learn
-
How to create a new Ionic 8 + Angular 20 app.
-
How to add and configure the Capacitor Local Notifications plugin.
-
How to schedule, manage, and cancel notifications.
-
How to test notifications on a real device or emulator.
What You’ll Need
Before we begin, make sure you have:
-
Node.js v20+ installed.
-
Ionic CLI v8+ installed globally.
-
A code editor like VS Code.
-
Android Studio or Xcode (for running the app on a real device or emulator).
Example Output
You’ll build a simple app with a button to schedule a notification. When tapped, a notification appears after a short delay — just like reminders or alerts in real-world apps.
Project Setup and Dependencies
In this section, we’ll set up a brand-new Ionic 8 project using Angular 20 and Capacitor 6. We’ll also install the Local Notifications plugin to implement native notifications on both Android and iOS.
Step 1: Install Ionic CLI
If you haven’t already installed the Ionic CLI globally, do so using npm:
npm install -g @ionic/cli
Verify the installation:
ionic -v
You should see a version like 8.x.x
. or 7.2.1
.
Step 2: Create a New Ionic Angular App
Next, create a new Ionic Angular app using the official blank starter template:
ionic start ionic-local-notification blank --type=angular
Move into the new project directory:
cd ionic-local-notification
This command creates a clean Ionic + Angular 20 project structure.
Step 3: Add Capacitor
If you selected Cordova during creation, remove it and use Capacitor (which is now the default in Ionic 8):
ionic integrations disable cordova
ionic integrations enable capacitor
Step 4: Install Local Notifications Plugin
Install the Capacitor Local Notifications plugin:
npm install @capacitor/local-notifications
Sync the plugin with native platforms:
ionic cap sync
Step 5: Add Platforms
To build and test on mobile devices, add Android and iOS platforms:
ionic cap add android
ionic cap add ios
⚠️ Note: You need Android Studio and Xcode installed for building and running the respective platforms.
Step 6: Run the App in Browser (Optional)
Before diving into notifications, verify that the app runs properly in the browser:
ionic serve
You should see the default Ionic starter app running at http://localhost:8100.
✅ At this point, your Ionic 8 + Angular 20 app is ready with Capacitor integration and the Local Notifications plugin installed.
Configuring Local Notifications and Permissions
Now that the Ionic 8 project is set up and the Local Notifications plugin installed, let’s configure it to request permissions and display notifications properly on both Android and iOS devices.
Step 1: Import and Request Permissions
First, open your main app component file:
src/app/app.component.ts
Add the following imports and permission logic:
import { Component, OnInit } from '@angular/core';
import { LocalNotifications } from '@capacitor/local-notifications';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent implements OnInit {
async ngOnInit() {
// Request permission to use local notifications
const permission = await LocalNotifications.requestPermissions();
if (permission.display === 'granted') {
console.log('Notification permission granted!');
} else {
console.warn('Notification permission denied.');
}
}
}
This ensures that when the app first runs, it asks the user for permission to show local notifications — a requirement on iOS and Android 13+.
Step 2: Add a Notification Scheduling Function
You can test a simple notification by adding a button to your main page.
Open src/app/home/home.page.ts
and modify it as follows:
import { Component } from '@angular/core';
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';
import { LocalNotifications } from '@capacitor/local-notifications';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
imports: [IonButton, IonHeader, IonToolbar, IonTitle, IonContent],
})
export class HomePage {
constructor() { }
async scheduleNotification() {
await LocalNotifications.schedule({
notifications: [
{
title: 'Hello from Ionic!',
body: 'This is your first local notification 🎉',
id: 1,
schedule: { at: new Date(Date.now() + 1000 * 5) }, // 5 seconds later
sound: undefined,
attachments: undefined,
actionTypeId: '',
extra: null,
},
],
});
console.log('Notification scheduled!');
}
}
Then, update the template file src/app/home/home.page.html
:
<ion-header>
<ion-toolbar color="primary">
<ion-title>Local Notification Demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button expand="block" (click)="scheduleNotification()">
Schedule Notification
</ion-button>
</ion-content>
Now, when you tap the “Schedule Notification” button, a notification will appear after 5 seconds.
Step 3: Configure Android Settings
If you’re testing on Android, ensure your app has the correct notification permissions.
Open android/app/src/main/AndroidManifest.xml
and confirm these lines exist inside the <manifest>
tag:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
Capacitor’s plugin automatically handles channels and scheduling, but if you need fine control (like grouping or sound), you can customize notification channels in the Android project later.
Step 4: Configure iOS Settings
For iOS, open ios/App/App/Info.plist
and add this key inside the <dict>
tag:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
Also, iOS requires you to explicitly request permission before scheduling notifications, which we already did in the AppComponent
.
Step 5: Test the Notifications
To test on a real device:
ionic cap build android
ionic cap open android
or
ionic cap build ios
ionic cap open ios
Then run the app from Android Studio or Xcode on a physical device or emulator. Tap the button, wait five seconds, and you should see the notification appear.
✅ At this point, your app can successfully request permissions and schedule basic local notifications using Ionic 8, Angular 20, and Capacitor 6.
Advanced Notification Features (Repeating, Actions, and Custom Sounds)
Now that your Ionic 8 app can send a simple local notification, let’s take it further by exploring advanced notification features—including repeating notifications, interactive actions, and custom sounds.
These capabilities will make your app’s notifications more dynamic and useful for real-world scenarios such as reminders, alerts, and interactive updates.
Step 1: Repeating Notifications
Capacitor’s Local Notifications API allows you to schedule repeating notifications using the repeats
and every
properties in the schedule
object.
Here’s how you can create a daily reminder notification.
Open src/app/home/home.page.ts
and add this method:
async scheduleDailyReminder() {
await LocalNotifications.schedule({
notifications: [
{
title: 'Daily Reminder',
body: 'Don’t forget to check your tasks for today ✅',
id: 2,
schedule: {
on: { hour: 9, minute: 0 }, // fires every day at 9:00 AM
repeats: true,
},
},
],
});
console.log('Daily reminder scheduled!');
}
Then update your home.page.html
to include a button for this new feature:
<ion-header>
<ion-toolbar color="primary">
<ion-title>Local Notification Demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button expand="block" (click)="scheduleNotification()">
Schedule Notification
</ion-button>
<ion-button
expand="block"
color="secondary"
(click)="scheduleDailyReminder()"
>
Schedule Daily Reminder
</ion-button>
</ion-content>
This will trigger a notification every day at 9:00 AM. You can adjust the time as needed.
Step 2: Adding Action Buttons
You can add interactive actions to notifications, allowing users to respond directly (e.g., "Snooze" or "Done").
First, define the action types in your app initialization (in app.component.ts
):
import { Component, OnInit } from '@angular/core';
import { LocalNotifications } from '@capacitor/local-notifications';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent implements OnInit {
async ngOnInit() {
const permission = await LocalNotifications.requestPermissions();
if (permission.display === 'granted') {
// Define notification actions
await LocalNotifications.registerActionTypes({
types: [
{
id: 'TASK_ACTIONS',
actions: [
{
id: 'done',
title: 'Done',
},
{
id: 'snooze',
title: 'Snooze',
},
],
},
],
});
console.log('Notification actions registered!');
}
}
}
Now, use these actions in a new notification (in home.page.ts
):
async scheduleActionNotification() {
await LocalNotifications.schedule({
notifications: [
{
title: 'Task Reminder',
body: 'Complete your daily report 📝',
id: 3,
actionTypeId: 'TASK_ACTIONS',
},
],
});
console.log('Action notification scheduled!');
}
Add a button to trigger this notification in your template:
<ion-button expand="block" color="tertiary" (click)="scheduleActionNotification()">
Schedule Action Notification
</ion-button>
To handle user responses to these actions, add a listener (also in app.component.ts
):
LocalNotifications.addListener('localNotificationActionPerformed', (event) => {
const actionId = event.actionId;
console.log('User tapped action:', actionId);
if (actionId === 'done') {
alert('Good job! Task marked as done.');
} else if (actionId === 'snooze') {
alert('Snoozed for 5 minutes.');
}
});
Now, users can interact with your notifications right from the system tray.
Step 3: Custom Notification Sounds
You can assign a custom sound to notifications to make them stand out.
1. Add your sound file:
Place a .mp3
or .wav
file in:
Example: notify_sound.mp3
-
Android:
android/app/src/main/res/raw/
-
iOS:
ios/App/App/public/
2. Use it in your notification:
async scheduleSoundNotification() {
await LocalNotifications.schedule({
notifications: [
{
title: 'Custom Sound Alert',
body: 'This notification has a custom sound 🔔',
id: 4,
sound: 'notify_sound', // file name without extension
},
],
});
console.log('Custom sound notification scheduled!');
}
3. Add the button:
<ion-button expand="block" color="success" (click)="scheduleSoundNotification()">
Schedule Custom Sound Notification
</ion-button>
Step 4: Testing Advanced Notifications
To verify all the advanced features:
ionic cap sync
ionic cap open android
# or
ionic cap open ios
Then run your app on a real device (not just a browser).
Try each button and observe:
-
The daily repeating notification appears automatically at the set time.
-
The action notification displays buttons (Done/Snooze).
-
The custom sound plays when that notification fires.
✅ At this point, your Ionic 8 app supports advanced local notifications with repeating schedules, interactive buttons, and custom alert sounds — all powered by Capacitor’s modern Local Notifications API.
Handling Notifications in the Background and Foreground
Now that your Ionic 8 app can display advanced notifications, it’s important to handle how notifications behave when the app is in the foreground, background, or terminated state.
By default, local notifications are designed to appear in the system tray when the app is in the background. However, when the app is open (foreground), the notifications may not always appear as expected — especially on iOS. Let’s fix that and make sure notifications work consistently across all app states.
Step 1: Detect Foreground Notifications
Capacitor’s LocalNotifications
plugin provides an event listener called localNotificationReceived
, which fires when a notification is triggered while the app is running in the foreground.
Add the following to your app.component.ts
(below the existing ngOnInit
code):
import { Component, OnInit } from '@angular/core';
import { IonApp, IonRouterOutlet } from '@ionic/angular/standalone';
import { LocalNotifications } from '@capacitor/local-notifications';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
imports: [IonApp, IonRouterOutlet],
})
export class AppComponent implements OnInit {
async ngOnInit() {
const permission = await LocalNotifications.requestPermissions();
if (permission.display === 'granted') {
// Define notification actions
await LocalNotifications.registerActionTypes({
types: [
{
id: 'TASK_ACTIONS',
actions: [
{
id: 'done',
title: 'Done',
},
{
id: 'snooze',
title: 'Snooze',
},
],
},
],
});
console.log('Notification actions registered!');
}
LocalNotifications.addListener('localNotificationActionPerformed', (event) => {
const actionId = event.actionId;
console.log('User tapped action:', actionId);
if (actionId === 'done') {
alert('Good job! Task marked as done.');
} else if (actionId === 'snooze') {
alert('Snoozed for 5 minutes.');
}
});
// ✅ Listen for foreground notifications
LocalNotifications.addListener('localNotificationReceived', (notification) => {
console.log('Notification received in foreground:', notification);
alert(`📬 ${notification.title}\n${notification.body}`);
});
}
}
Now, whenever a notification triggers while your app is open, an alert dialog will appear — letting the user know it arrived, even though it wouldn’t normally show in the tray.
Step 2: Handle Background Notifications
When your app is in the background, notifications are automatically shown in the system tray.
However, you can also perform background logic when the user taps the notification.
Add another listener to handle user actions or taps (still in app.component.ts
):
LocalNotifications.addListener('localNotificationActionPerformed', (event) => {
const notification = event.notification;
const actionId = event.actionId;
console.log('Notification tapped:', notification);
console.log('Action selected:', actionId);
// Handle notification actions
if (actionId === 'done') {
alert('✅ Task marked as done!');
} else if (actionId === 'snooze') {
alert('😴 Task snoozed for 5 minutes!');
} else {
// Default tap (no action)
alert(`You opened: ${notification.title}`);
}
});
With this, your app can respond intelligently when users interact with notifications — even if the app was previously in the background.
Step 3: Handling App Resume from a Notification Tap
If your app was terminated and the user taps a notification to reopen it, you can check which notification triggered the resume event using the App
plugin from Capacitor.
Install the plugin (if not already):
npm install @capacitor/app
Then modify app.component.ts
to detect notification-triggered launches:
import { App } from '@capacitor/app';
App.addListener('appUrlOpen', (data) => {
console.log('App opened from URL:', data);
});
App.addListener('appStateChange', (state) => {
console.log('App state changed:', state);
});
While local notifications don’t automatically pass URLs, you can include deep links or metadata in extra
fields of notifications to perform navigation or specific logic upon reopening the app.
Example:
await LocalNotifications.schedule({
notifications: [
{
title: 'New Message',
body: 'Tap to open chat',
id: 5,
extra: { page: '/chat' },
},
],
});
Then handle it on launch:
LocalNotifications.addListener('localNotificationActionPerformed', (event) => {
const page = event.notification.extra?.page;
if (page) {
console.log(`Navigate to: ${page}`);
// Example: this.router.navigate([page]);
}
});
Step 4: Testing Foreground and Background Behavior
To verify that everything works correctly:
-
Foreground Test:
-
Run the app on a device using
ionic cap run android -l
orionic cap run ios -l
. -
Tap the Schedule Notification button and wait 5 seconds.
-
You should see an in-app alert (not a system notification).
-
-
Background Test:
-
Press the Home button or minimize the app.
-
Wait for a notification to appear in the system tray.
-
Tap it — the app should open, and your event listeners should trigger.
-
-
Terminated App Test:
-
Force close the app completely.
-
Trigger a scheduled notification (e.g., a repeating daily reminder).
-
Tap the notification — the app should open, and your
localNotificationActionPerformed
listener will handle it.
-
✅ At this point, your Ionic 8 + Angular 20 + Capacitor app fully supports foreground and background notification handling, including:
-
Displaying alerts while active
-
Responding to user taps
-
Detecting app resume events
Testing on Real Devices and Troubleshooting Common Issues
At this point, your Ionic 8 app is feature-complete with working local notifications, but notifications behave differently across devices and operating systems. This section will guide you through testing on real hardware, debugging permission or delivery issues, and resolving common platform-specific problems for both Android and iOS.
Step 1: Prepare for Real Device Testing
Before you begin, make sure the following steps are completed:
ionic build
ionic cap sync
ionic cap copy
Then open your native projects in Android Studio or Xcode:
ionic cap open android
# or
ionic cap open ios
Testing in the browser (ionic serve) will not trigger notifications because Capacitor plugins like LocalNotifications
rely on native APIs.
Step 2: Testing on Android Devices
a. Install the app on your Android device:
Run the app directly from Android Studio or via the command line:
b. Verify notification permission (Android 13+):
Starting with Android 13 (API level 33), users must explicitly grant notification permission.
If notifications are not appearing, check permission status manually:
-
Go to Settings → Apps → [Your App] → Notifications
-
Ensure it’s toggled ON
c. Check notification channels:
Capacitor automatically creates channels, but if you want to ensure proper configuration, you can define channels manually in your app’s initialization code:
await LocalNotifications.createChannel({
id: 'reminder',
name: 'Reminders',
description: 'Daily reminders and alerts',
importance: 5, // MAX importance
});
Then, reference the channel in your notifications:
await LocalNotifications.schedule({
notifications: [
{
title: 'Task Reminder',
body: 'Time to review your goals!',
id: 6,
channelId: 'reminder',
},
],
});
d. Verify sound and vibration:
If custom sounds don’t play:
-
Ensure your sound file is placed in
android/app/src/main/res/raw/
-
Remove file extensions when setting the sound (e.g.,
sound: 'notify_sound'
)
Step 3: Testing on iOS Devices
a. Run the app on a physical iPhone:
Local notifications won’t appear on the iOS simulator — you must test on a real device.
Connect your iPhone and run:
ionic cap run ios --device
b. Grant notification permission:
The first time you open the app, iOS prompts you to allow notifications.
If you accidentally deny it, you can re-enable it under:
Settings → [Your App Name] → Notifications → Allow Notifications
c. Verify Info.plist configuration:
Make sure your Info.plist
includes these keys:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
d. Custom sound troubleshooting:
-
Sound files must be included in your app bundle (
ios/App/App/public/
) -
Use
.wav
or.aiff
formats for the highest compatibility -
Ensure your iPhone is not in silent mode (notifications won’t play sound otherwise)
Step 4: Debugging Common Issues
Issue | Possible Cause | Solution |
---|---|---|
Notifications not appearing | Permissions denied | Check LocalNotifications.requestPermissions() result |
Notifications only appear once | repeats flag missing |
Add repeats: true to your schedule |
Custom sound not playing | Incorrect file path or name | Verify res/raw on Android, correct format on iOS |
Notifications don’t show in foreground | Foreground listener missing | Add localNotificationReceived handler |
Tapping notification does nothing | No action handler | Add localNotificationActionPerformed listener |
Notifications don’t show on Android 13+ | Permission required | Request permission via plugin or system settings |
Step 5: Logging and Debugging
Use Capacitor’s built-in debugging tools:
-
In Android Studio:
Logcat
→ Filter by your app’s package name
Look for logs containingLocalNotifications
-
In iOS (Xcode):
Open the Console tab while running the app on a real device.
Add logs to your event handlers for visibility:
LocalNotifications.addListener('localNotificationReceived', (notification) => {
console.log('Foreground notification received:', notification);
});
LocalNotifications.addListener('localNotificationActionPerformed', (event) => {
console.log('Notification action performed:', event);
});
Step 6: Useful Testing Tips
-
Always use real devices for full notification behavior testing.
-
If notifications fail randomly, try rebuilding the native projects:
ionic cap clean ionic cap sync
-
Test multiple scenarios:
-
Foreground notifications
-
Background notifications
-
App terminated
-
Repeating notifications
-
Action button responses
-
✅ At this stage, you’ve successfully tested your Ionic 8 + Angular 20 + Capacitor notification app across real devices and platforms.
You also know how to diagnose permission issues, confirm configuration settings, and debug plugin behavior effectively.
Conclusion and Next Steps
In this updated guide, you’ve learned how to build a modern Local Notifications app using Ionic 8, Angular 20, and Capacitor 6 — fully replacing the legacy Cordova-based implementation from Ionic 3.
This new approach is faster, more reliable, and future-proof, leveraging Capacitor’s direct access to native APIs without the need for additional wrappers or dependencies.
✅ What You’ve Learned
By following along, you now understand how to:
-
✅ Create an Ionic 8 + Angular 20 project using the latest CLI tools
-
✅ Install and configure the Capacitor Local Notifications plugin
-
✅ Request permissions dynamically on Android and iOS
-
✅ Schedule one-time and repeating notifications
-
✅ Add interactive actions like “Snooze” and “Done”
-
✅ Play custom sounds for specific alerts
-
✅ Handle notifications across foreground, background, and terminated states
-
✅ Debug and test notifications effectively on real devices
🚀 What’s Next
You can now extend your app to use notifications for real-world scenarios. Here are some ideas to take it further:
-
Combine with Background Tasks
Use Capacitor’sBackgroundTask
plugin to trigger notifications based on app logic, timers, or remote events. -
Integrate Push Notifications
Upgrade to Capacitor Push Notifications for real-time, server-triggered alerts (via Firebase Cloud Messaging or APNs). -
Add Deep Linking Support
Include navigation logic when users tap a notification — e.g., open a specific page or item in your app. -
Enhance User Experience
Schedule context-aware notifications (e.g., “Remind me in 30 minutes” or “Notify me when near a location”). -
Analytics and Tracking
Log and analyze how often users interact with your notifications to improve timing and relevance.
🧩 Modern Stack Benefits
This updated implementation offers several advantages over the older Ionic 3 + Cordova version:
Feature | Old (Cordova) | New (Capacitor) |
---|---|---|
Compatibility | Limited to older Android/iOS versions | Fully supports Android 14 and iOS 18 |
Performance | Slower (uses JS bridge) | Native performance |
Plugin Support | Many outdated or unmaintained | Officially supported by Capacitor |
Codebase | Ionic 3 + Angular 5 (deprecated) | Ionic 8 + Angular 20 |
Maintenance | Requires Cordova builds | Uses standard Capacitor sync/build tools |
💡 Final Thoughts
Migrating from Cordova to Capacitor is one of the best steps you can take for long-term app stability and performance.
With Capacitor’s modern APIs, TypeScript support, and official maintenance from the Ionic team, you can focus on building great user experiences without worrying about plugin compatibility or performance overhead.
You can find the full source code on our GitHub.
We know that building beautifully designed Ionic apps from scratch can be frustrating and very time-consuming. Check Ionic 6 - Full Starter App and save development and design time. Android, iOS, and PWA, 100+ Screens and Components, the most complete and advanced Ionic Template.
That's just the basics. If you need more deep learning about Ionic, Angular, and TypeScript, you can take the following cheap course:
- Ionic - Build iOS, Android & Web Apps with Ionic & Angular
- Master Ionic 8+: Beginner to Expert Food Delivery App Course
- Ionic 8+ Masterclass:Build Real-Time Chat Apps with Firebase
- Ionic 8+ & Supabase Full-Stack Mastery: Build Real-Time Apps
- Learn Ionic React By Building a WhatsApp Clone
- Progressive Web Apps (PWA) - The Complete Guide
- IONIC - Build Android & Web Apps with Ionic
- Ionic Framework with VueJS: Build a CRUD App Using SQLite
- Angular, Ionic & Node: Build A Real Web & Mobile Chat App
- Ionic Apps with Firebase
Thanks!