Ionic 8, Angular 20, and Capacitor Local Notifications Example

by Didin J. on Oct 16, 2025 Ionic 8, Angular 20, and Capacitor Local Notifications Example

Learn how to implement local notifications in Ionic 8, Angular 20, and Capacitor with repeating, actions, and sound alerts.

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.

Ionic 8, Angular 20, and Capacitor Local Notifications Example - ionic serve

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:

  1. Foreground Test:

    • Run the app on a device using ionic cap run android -l or ionic cap run ios -l.

    • Tap the Schedule Notification button and wait 5 seconds.

    • You should see an in-app alert (not a system notification).

  2. 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.

  3. 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 containing LocalNotifications

  • 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:

  1. Combine with Background Tasks
    Use Capacitor’s BackgroundTask plugin to trigger notifications based on app logic, timers, or remote events.

  2. Integrate Push Notifications
    Upgrade to Capacitor Push Notifications for real-time, server-triggered alerts (via Firebase Cloud Messaging or APNs).

  3. Add Deep Linking Support
    Include navigation logic when users tap a notification — e.g., open a specific page or item in your app.

  4. Enhance User Experience
    Schedule context-aware notifications (e.g., “Remind me in 30 minutes” or “Notify me when near a location”).

  5. 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:

Thanks!