Skip to main content

Push Notifications

a0 provides built-in push notification delivery through FCM (Android) and APNs (iOS). Your Convex backend sends notifications via a0’s API — no third-party push services needed.
Push notifications do NOT work in Expo Go or the a0 preview app. Test with a custom APK (Android) or TestFlight build (iOS).

How it works

  1. Your app collects device push tokens using expo-notifications
  2. Your Convex backend calls internal.notifications.sendNotification
  3. a0 detects the platform from the token and delivers via FCM or APNs
The convex/notifications.ts file is automatically included in your project. It’s an internal action — only your server-side Convex functions can call it, not the client.

Getting a device push token

Use getDevicePushTokenAsync() from expo-notificationsnot getExpoPushTokenAsync():
import * as Notifications from "expo-notifications";

async function registerForPushNotifications() {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== "granted") return null;

  const { data: token } = await Notifications.getDevicePushTokenAsync();
  return token; // Save this to your Convex database
}

Sending notifications

Call sendNotification from your Convex backend using internal (not api):
import { internal } from "./_generated/api";

// From a mutation — schedule it since mutations can't call actions directly
await ctx.scheduler.runAfter(0, internal.notifications.sendNotification, {
  to: devicePushToken,
  title: "New Message",
  body: "You have a new message",
  data: { screen: "chat", chatId: "abc123" },
});

// From an action — call directly
await ctx.runAction(internal.notifications.sendNotification, {
  to: [token1, token2, token3],
  title: "Announcement",
  body: "Check out our new feature!",
  badge: 1,
});
ArgumentTypeRequiredDescription
tostring | string[]YesDevice push token(s)
titlestringYesNotification title
bodystringYesNotification body
dataRecord<string, string | number | boolean | null>NoCustom data payload
badgenumberNoApp icon badge count (iOS)

Android Setup

Follow the Android FCM Setup guide to configure Firebase Cloud Messaging credentials:
  1. Create a Firebase project and add your Android app
  2. Download google-services.json
  3. Create a Firebase service account key (for FCM v1 delivery)
  4. Upload both in Project Settings → Build Settings → Android Credentials
  5. Create a new APK build

iOS Setup

iOS push notifications are configured per Apple Developer team:
  1. Connect your Apple Developer account via the Publish → App Store flow
  2. Set up APNs credentials (automatic or manual) from the team management dialog
  3. Create a new App Store / TestFlight build
The aps-environment entitlement is included automatically in all builds.

Handling notifications in your app

import * as Notifications from "expo-notifications";

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldPlaySound: true,
    shouldSetBadge: true,
    shouldShowBanner: true,
    shouldShowList: true,
  }),
});

// When user taps a notification
Notifications.addNotificationResponseReceivedListener((response) => {
  const data = response.notification.request.content.data;
  // Navigate based on data
});

Troubleshooting

IssueFix
”no valid aps-environment entitlement”Create a new build — older builds may not include this entitlement
”BadDeviceToken”Device token expired or from wrong environment — get a fresh token
”Firebase service account is not configured”Upload credentials in Build Settings → Android Credentials
”APNs credentials are not configured”Set up APNs key in Publish → App Store → team management
Notifications not receivedMust test on TestFlight (iOS) or custom APK (Android), not Expo Go