Add push notifications to your Lovable app using Firebase Cloud Messaging (FCM). Store your Firebase service account key in Cloud → Secrets, create a Supabase Edge Function that calls the FCM HTTP v1 API, register a service worker in your Lovable frontend for web push, and trigger notifications from database events or user actions. The entire setup takes about 60 minutes and requires a Firebase project with FCM enabled.
Send push notifications from your Lovable app using Firebase Cloud Messaging
Push notifications are one of the most effective tools for re-engaging users — they appear on the user's device even when the app is not open, enabling use cases like order status updates, new message alerts, scheduled reminders, and real-time event notifications. Firebase Cloud Messaging (FCM) is Google's free, enterprise-grade service for delivering these notifications at scale across web, iOS, and Android platforms.
Lovable does not have a native connector for FCM, but the integration is straightforward using Lovable's Edge Function pattern. The architecture has two halves. On the server side, a Supabase Edge Function holds your Firebase service account credentials and calls the FCM HTTP v1 API to dispatch notifications. On the client side, your Lovable React app registers a web push service worker, requests notification permission from the user, and stores the resulting device registration token in Supabase so you know which devices belong to which users.
One important distinction for web push: FCM web push requires HTTPS and a registered service worker file. Lovable's deployed apps run on HTTPS by default, and adding a service worker to a Vite-based project is straightforward using the vite-plugin-pwa or a manually placed public/firebase-messaging-sw.js file. The service worker handles background notification display — showing the notification even when the user's browser tab is not focused. This guide covers the complete setup from Firebase project creation through your first triggered notification.
Integration method
FCM integration uses a Supabase Edge Function to call the FCM HTTP v1 API server-side, using a Firebase service account key stored in Cloud → Secrets via Deno.env.get(). The frontend registers a service worker for web push, requests notification permission, and stores device tokens in Supabase. Notifications are triggered by sending a POST request to the Edge Function from frontend actions or database webhooks.
Prerequisites
- A Lovable account with an active Lovable Cloud project
- A Google account to create a Firebase project (free at console.firebase.google.com)
- Your Lovable app deployed to a live HTTPS URL (web push requires HTTPS — Lovable's published apps use HTTPS by default)
- Firebase project created with Cloud Messaging enabled in the Firebase Console
- Firebase service account JSON key downloaded from Firebase Console → Project Settings → Service Accounts
Step-by-step guide
Create a Firebase project and configure Cloud Messaging
Create a Firebase project and configure Cloud Messaging
Start by setting up your Firebase project for web push notifications. Open a new browser tab and go to console.firebase.google.com. Click 'Add project', give it a name that matches your Lovable app, and follow the setup wizard. You can disable Google Analytics for this project unless you plan to use Firebase Analytics separately — it is not required for FCM. Once the project is created, click the web icon ('< />') on the project overview page to add a web app. Give the app a nickname. You do not need to set up Firebase Hosting. After registering the app, Firebase shows you a firebaseConfig object containing your apiKey, projectId, messagingSenderId, and appId. Copy these values — you will need them for the frontend service worker configuration. Now go to Project Settings (gear icon in the left sidebar) → Cloud Messaging tab. This page shows two important values: your Server Key (legacy) and VAPID key. The VAPID key is what your frontend service worker uses to subscribe users to web push. Copy the VAPID key (click 'Generate key pair' if none exists) and keep it accessible. Finally, go to Project Settings → Service Accounts tab. Click 'Generate new private key'. Firebase downloads a JSON file containing your service account credentials. This file includes the private_key, client_email, and project_id needed to authenticate FCM API calls from your Edge Function. Store this file securely — do not commit it to GitHub. You will extract specific fields from it in the next step.
Pro tip: The service account JSON file is the most sensitive credential in this integration. It grants full Firebase Admin access, not just FCM. Store it carefully and never paste its contents into Lovable's chat.
Expected result: A Firebase project is created with a web app registered. You have the firebaseConfig values, the VAPID key for web push, and the service account JSON file downloaded.
Store Firebase credentials in Cloud → Secrets
Store Firebase credentials in Cloud → Secrets
The service account JSON file contains sensitive credentials that must be stored in Lovable's encrypted Secrets panel, never in frontend code or Git commits. You will store three values extracted from the service account JSON: the private key, the client email, and the project ID. These are the only values needed to authenticate FCM API calls. To access the Secrets panel in Lovable, click the '+' icon next to the Preview label at the top of the editor. In the Cloud panel that opens, click the 'Secrets' tab. Click 'Add new secret' and add the following three secrets: - Name: FIREBASE_PRIVATE_KEY — Value: the private_key field from the service account JSON. This is a multi-line string starting with '-----BEGIN RSA PRIVATE KEY-----'. Paste the entire value including the header and footer lines. - Name: FIREBASE_CLIENT_EMAIL — Value: the client_email field from the service account JSON (looks like firebase-adminsdk-xxxxx@your-project.iam.gserviceaccount.com) - Name: FIREBASE_PROJECT_ID — Value: the project_id field from the service account JSON (same as your Firebase project ID, e.g., 'my-app-12345') Also add your VAPID key as a non-secret environment variable if you plan to use it in Edge Functions, though the VAPID key is also used on the frontend. For the frontend VAPID key, you can include it in your React code directly since it is a public key by design — the web push specification is built around public/private key pairs where the VAPID public key is safe to expose. After saving, verify all three secrets appear in the Secrets list. The private key value will be masked — if it shows as a short masked string, the full multi-line value was likely truncated. Test it by invoking the Edge Function in the next step.
Pro tip: When pasting the Firebase private key, make sure to include the full value with \n escape sequences preserved as-is. The Edge Function code will need to replace \n with actual newlines when using the key.
Expected result: Three secrets — FIREBASE_PRIVATE_KEY, FIREBASE_CLIENT_EMAIL, and FIREBASE_PROJECT_ID — are stored in Cloud → Secrets. The Secrets panel shows all three with masked values.
Create an Edge Function to send FCM notifications
Create an Edge Function to send FCM notifications
The Edge Function handles the server-side authentication with Firebase and dispatches push notifications. FCM's HTTP v1 API requires OAuth 2.0 authentication using a Google access token, which is obtained by signing a JWT with your service account private key and exchanging it for a short-lived access token. This is more complex than a simple API key, but the Edge Function below handles the full flow. The function accepts a request body containing the FCM device token (the registration token from the user's browser), a notification title, body text, and an optional click URL. It constructs the Google OAuth JWT, exchanges it for an access token, and then calls the FCM v1 messages.send endpoint. Ask Lovable to create this Edge Function by pasting the prompt below into chat. Alternatively, use the code snippet directly in the Code panel by navigating to supabase/functions/ and creating a new file. The function uses only Deno's built-in crypto API for JWT signing — no external libraries required.
Create a Supabase Edge Function at supabase/functions/send-push-notification/index.ts that sends an FCM web push notification. It should: (1) read FIREBASE_PRIVATE_KEY, FIREBASE_CLIENT_EMAIL, and FIREBASE_PROJECT_ID from Deno.env.get, (2) create a Google OAuth access token by signing a JWT with the service account credentials using Deno's built-in crypto, (3) call the FCM HTTP v1 messages.send endpoint with the access token, (4) accept a request body with fcmToken, title, body, and optional clickUrl fields, and (5) return success or the FCM error message. Include CORS headers.
Paste this in Lovable chat
1// supabase/functions/send-push-notification/index.ts2const corsHeaders = {3 'Access-Control-Allow-Origin': '*',4 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',5};67async function getGoogleAccessToken(): Promise<string> {8 const privateKeyPem = (Deno.env.get('FIREBASE_PRIVATE_KEY') ?? '').replace(/\\n/g, '\n');9 const clientEmail = Deno.env.get('FIREBASE_CLIENT_EMAIL')!;10 const now = Math.floor(Date.now() / 1000);1112 const header = { alg: 'RS256', typ: 'JWT' };13 const payload = {14 iss: clientEmail,15 scope: 'https://www.googleapis.com/auth/firebase.messaging',16 aud: 'https://oauth2.googleapis.com/token',17 iat: now,18 exp: now + 3600,19 };2021 const encode = (obj: unknown) =>22 btoa(JSON.stringify(obj)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');2324 const signingInput = `${encode(header)}.${encode(payload)}`;2526 const pemBody = privateKeyPem.replace(/-----[^-]+-----/g, '').replace(/\s/g, '');27 const keyBuffer = Uint8Array.from(atob(pemBody), c => c.charCodeAt(0));2829 const cryptoKey = await crypto.subtle.importKey(30 'pkcs8', keyBuffer,31 { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },32 false, ['sign']33 );3435 const signature = await crypto.subtle.sign(36 'RSASSA-PKCS1-v1_5',37 cryptoKey,38 new TextEncoder().encode(signingInput)39 );4041 const signatureB64 = btoa(String.fromCharCode(...new Uint8Array(signature)))42 .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');4344 const jwt = `${signingInput}.${signatureB64}`;4546 const tokenRes = await fetch('https://oauth2.googleapis.com/token', {47 method: 'POST',48 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },49 body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${jwt}`,50 });5152 const tokenData = await tokenRes.json();53 if (!tokenData.access_token) throw new Error(`OAuth error: ${JSON.stringify(tokenData)}`);54 return tokenData.access_token;55}5657Deno.serve(async (req) => {58 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders });5960 try {61 const { fcmToken, title, body, clickUrl } = await req.json();62 const projectId = Deno.env.get('FIREBASE_PROJECT_ID')!;63 const accessToken = await getGoogleAccessToken();6465 const message: Record<string, unknown> = {66 token: fcmToken,67 notification: { title, body },68 webpush: {69 fcm_options: clickUrl ? { link: clickUrl } : undefined,70 notification: { icon: '/icon-192.png' },71 },72 };7374 const fcmRes = await fetch(75 `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`,76 {77 method: 'POST',78 headers: {79 Authorization: `Bearer ${accessToken}`,80 'Content-Type': 'application/json',81 },82 body: JSON.stringify({ message }),83 }84 );8586 const fcmData = await fcmRes.json();87 return new Response(JSON.stringify(fcmData), {88 status: fcmRes.ok ? 200 : fcmRes.status,89 headers: { ...corsHeaders, 'Content-Type': 'application/json' },90 });91 } catch (error) {92 console.error('FCM error:', error);93 return new Response(JSON.stringify({ error: String(error) }), {94 status: 500,95 headers: { ...corsHeaders, 'Content-Type': 'application/json' },96 });97 }98});Pro tip: The private key replacement (.replace(/\\n/g, '\n')) is critical — service account keys stored in environment variables have escaped newlines that must be converted back to actual newline characters before the key can be parsed by the crypto API.
Expected result: The Edge Function is deployed and visible in Cloud → Logs. A test invocation with a valid FCM token returns a successful FCM message name response.
Add the service worker and request push permission in the frontend
Add the service worker and request push permission in the frontend
Web push notifications require a service worker — a JavaScript file running in the browser background that displays notifications even when the app tab is not focused. The service worker file must be accessible at the root of your domain (e.g., yourdomain.com/firebase-messaging-sw.js), which means it needs to be placed in Lovable's public/ directory so Vite includes it as a static asset. The service worker imports the Firebase Messaging SDK from a CDN URL that is compatible with service worker environments. It initializes Firebase with your project's config values and listens for background messages, displaying them using the browser's native Notification API. To add the service worker file, open Lovable's chat and provide the prompt below. Lovable will create the file in the public/ directory and modify your React app to request notification permission on load and store the resulting FCM registration token in Supabase. IMPORTANT: Replace the placeholder config values (VITE_FIREBASE_API_KEY, etc.) with your actual Firebase web app config values. These values are public and safe to include in frontend code — they are not the service account credentials you stored in Secrets. You can add them as VITE_ prefixed environment variables in Lovable's Cloud → Secrets panel if you prefer to keep them out of the code directly (VITE_ prefix makes them available at build time to the frontend).
Create a service worker file at public/firebase-messaging-sw.js for Firebase Cloud Messaging web push. It should import Firebase from the compat CDN, initialize the app with the config values, and call onBackgroundMessage to display notifications when the browser tab is not focused. Also update the main React app to: (1) import the Firebase web SDK, (2) request notification permission on first visit, (3) get the FCM registration token using the VAPID key, and (4) save the token to a 'device_tokens' table in Supabase with the current user's ID. Use these Firebase config values: [paste your firebaseConfig here].
Paste this in Lovable chat
1// public/firebase-messaging-sw.js2importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js');3importScripts('https://www.gstatic.com/firebasejs/10.7.1/firebase-messaging-compat.js');45firebase.initializeApp({6 apiKey: self.FIREBASE_API_KEY || 'YOUR_API_KEY',7 authDomain: 'YOUR_PROJECT.firebaseapp.com',8 projectId: 'YOUR_PROJECT_ID',9 storageBucket: 'YOUR_PROJECT.appspot.com',10 messagingSenderId: 'YOUR_SENDER_ID',11 appId: 'YOUR_APP_ID',12});1314const messaging = firebase.messaging();1516messaging.onBackgroundMessage((payload) => {17 const { title, body, icon } = payload.notification ?? {};18 self.registration.showNotification(title ?? 'New notification', {19 body: body ?? '',20 icon: icon ?? '/icon-192.png',21 data: payload.data,22 });23});Pro tip: The service worker's Firebase config values are NOT secret — they are the same values visible in the Firebase console's web app setup and are safe to include in public JavaScript.
Expected result: The service worker file exists in the public/ directory and is served at your app's root URL (e.g., https://yourapp.lovable.app/firebase-messaging-sw.js). The React app requests notification permission on first load and saves the FCM token to Supabase if permission is granted.
Trigger a test notification and verify end-to-end delivery
Trigger a test notification and verify end-to-end delivery
With the Edge Function deployed and the frontend service worker registered, you can test the full notification flow. First, deploy your Lovable app by clicking the Publish button in the top-right corner of the editor. Web push requires a live HTTPS URL — it does not work in the Lovable preview iframe due to service worker scope restrictions. Open your deployed app URL in a browser. You should see a browser permission prompt asking if you allow notifications. Click 'Allow'. Open your browser's developer tools, go to the Console tab, and look for a log message showing your FCM registration token. Copy this token (it is a long string, typically 100-200 characters). Now test the Edge Function directly using Lovable's Cloud → Logs panel or by invoking it from a temporary button in your app. In Lovable's chat, prompt: 'Add a temporary test button to the admin page that calls the send-push-notification Edge Function with my FCM token. Use the token I'll provide and send a notification with title Test Notification and body This is working correctly.' Replace the placeholder token with the one you copied from the console. Click the test button in your deployed app. Within 1-3 seconds you should see a browser push notification appear, even if the app is in the background. If the notification does not appear, check Cloud → Logs for Edge Function errors and verify the FCM token was stored correctly in the device_tokens table in your Supabase database. For production use, remove the test button and implement proper notification triggers — for example, calling the Edge Function from a Supabase database trigger when a new order is placed, or from a user action like sending a message.
Add a button on the dashboard page that sends a test push notification to the current user. It should fetch the user's FCM token from the device_tokens table, then call the send-push-notification Edge Function with title 'Test Notification' and body 'Your push notifications are working!' Show a toast message confirming the notification was sent.
Paste this in Lovable chat
Pro tip: If the notification appears in the browser console log but not as a system notification, check that your operating system notification settings allow browser notifications and that you are not in 'Do Not Disturb' mode.
Expected result: A push notification appears in the browser (or as a system notification if the browser is not in focus) with the correct title and body. Cloud → Logs shows a successful 200 response from the FCM API.
Common use cases
Send order status notifications for an e-commerce app
When a Shopify order status changes or a database record is updated, trigger an Edge Function that sends a push notification to the customer's registered device. This keeps customers informed without requiring them to check the app, reducing support inquiries about order status.
Create an Edge Function called 'send-push-notification' that accepts a userId, title, body, and optional URL. Look up the user's FCM device token from the 'device_tokens' table in Supabase, then send a web push notification via the FCM HTTP v1 API using the Firebase service account key from Cloud Secrets. Return success or failure for each token.
Copy this prompt to try it in Lovable
Alert team members about new form submissions
When users submit a contact form or application through your Lovable app, immediately notify your team via push notification. This is faster than email notifications for time-sensitive leads and requires no additional messaging tool setup beyond FCM.
Add a push notification trigger to the contact form submission handler. When a new submission is saved to the database, call the send-push-notification Edge Function to alert all admin users. Store admin device tokens in a separate 'admin_tokens' table and fan out notifications to all registered admin devices.
Copy this prompt to try it in Lovable
Remind users of scheduled events or tasks
Supabase's pg_cron extension or a scheduled Edge Function can query upcoming events and send push reminders to relevant users. A task management app, appointment booking tool, or event platform can use this to deliver timely reminders without a separate notification service.
Create a scheduled Edge Function that runs every hour, queries the 'appointments' table for records where the appointment time is within the next 24 hours and a reminder has not been sent, sends a push notification to each appointment owner using FCM, and marks the record as reminder_sent. Use Deno cron syntax or Supabase's pg_cron for scheduling.
Copy this prompt to try it in Lovable
Troubleshooting
Edge Function returns 'Error: OAuth error: invalid_grant' or the FCM API returns a 401 Unauthorized response
Cause: The Firebase private key stored in Cloud → Secrets has corrupted line breaks. When service account private keys are stored as environment variable strings, the \n escape sequences must be preserved exactly — if the key was pasted incorrectly or the environment variable was processed through a tool that converted escaped newlines to actual newlines, the key becomes invalid.
Solution: Go to Cloud → Secrets and delete the FIREBASE_PRIVATE_KEY secret. Re-open the service account JSON file you downloaded from Firebase, copy the private_key value exactly as it appears (including the \n characters as literal backslash-n sequences, not as newlines). Paste the raw string value into the new secret. The Edge Function code includes .replace(/\\n/g, '\n') which converts the escaped sequences to real newlines at runtime.
Notification permission prompt does not appear when visiting the deployed app
Cause: Web push permission prompts can only be triggered by a user gesture (a button click), not automatically on page load, in some browsers. Chrome requires a user interaction before showing the notification permission dialog since a 2020 browser update. If the permission request is triggered on mount without a user gesture, the browser silently ignores it.
Solution: Update the notification permission request to be triggered by a button click rather than a useEffect on mount. Ask Lovable: 'Move the notification permission request to happen when the user clicks an Enable Notifications button rather than automatically on page load. Only request permission if the current permission state is default (not already granted or denied).'
Push notifications work in the browser tab but not as background notifications when the tab is minimized or closed
Cause: The service worker file at public/firebase-messaging-sw.js is not being served from the correct URL, the Firebase config values in the service worker do not match the app's config, or the service worker registration failed silently in the browser.
Solution: Open browser developer tools → Application tab → Service Workers. Confirm the firebase-messaging-sw.js service worker is registered and shows as 'activated and is running'. If it shows an error, check the URL: it must be served from yourdomain.com/firebase-messaging-sw.js (not /public/firebase-messaging-sw.js). Verify the Firebase config values inside the service worker file match your Firebase project's web app configuration exactly.
FCM token is generated but push notifications are never received on the device
Cause: The FCM token stored in Supabase does not match the token being passed to the Edge Function, the token has expired or been invalidated by the browser (tokens refresh when the service worker updates), or the device_tokens table is storing tokens without associating them with the correct user ID.
Solution: Add token refresh handling in your frontend: when the FCM token is refreshed (using the onTokenRefresh callback from the Firebase Messaging SDK), update the record in the device_tokens table for the current user. Also verify that the token in Supabase matches what the browser's developer tools show as the current registration token. Mismatches indicate a stale token in the database.
Best practices
- Always request notification permission in response to a user gesture (button click) rather than on page load — browsers actively suppress automatic permission prompts, and users who feel ambushed by permission dialogs often deny them permanently.
- Store FCM device tokens in Supabase with the user ID and a timestamp so you can clean up stale tokens and know which users have push notifications enabled, which lets you avoid sending notifications to users who have never granted permission.
- Handle token refresh by listening to the onTokenRefresh event from the Firebase Messaging SDK and updating the stored token in Supabase — registration tokens are periodically rotated by FCM and stale tokens cause silent notification failures.
- Keep your Firebase service account JSON file in a secure location separate from your project files and never commit it to Git — the private key it contains grants full Firebase Admin API access, far beyond just FCM.
- Use FCM's notification targeting carefully — the device token approach used in this guide sends to specific devices, but FCM also supports topic subscriptions and condition targeting for fan-out notifications to user segments.
- Test push notifications on multiple browsers (Chrome, Firefox, Edge) because service worker support and notification display behavior differ slightly across browsers — Safari on iOS requires additional configuration not covered in this web push guide.
- For high-volume notification use cases (thousands of notifications per hour), consider batching FCM requests in the Edge Function using FCM's batch send endpoint rather than making one HTTP call per notification.
Alternatives
Choose Twilio if you need SMS, MMS, or WhatsApp notifications rather than web push — Twilio is a Lovable native shared connector with no manual setup, while FCM requires full Edge Function configuration.
Choose MongoDB Atlas if your primary need is document storage rather than notifications — FCM and MongoDB serve entirely different purposes, though both use the Edge Function integration pattern.
Choose SendGrid if email notifications are sufficient for your use case — email is simpler to implement than web push and reaches users on all devices without requiring notification permission grants.
Frequently asked questions
Do push notifications work on iOS Safari with this FCM setup?
Web push on iOS Safari requires additional configuration beyond what is covered in this guide. Apple added web push support to Safari on iOS 16.4+ (March 2023), but it requires your app to be added to the home screen as a Progressive Web App (PWA) and uses Apple's own push service (APNs) rather than FCM's web push directly. For iOS push notifications from a Lovable web app, you need to configure APNs credentials in your Firebase project and handle the Safari-specific registration flow separately.
Can I use FCM to send notifications to mobile app users?
FCM supports iOS and Android apps, but this integration guide covers web push only. For mobile app notifications, you would need a React Native or native mobile app that uses the Firebase SDK to register for push notifications. The Edge Function sending logic remains the same — you just provide a mobile FCM token instead of a web push token. Lovable generates web apps (React + Vite), so native mobile push is outside this guide's scope.
Is Firebase Cloud Messaging free?
Yes. FCM is completely free with no usage limits — Google does not charge for sending push notifications. The Firebase project itself is free on the Spark plan. You only pay if you use other Firebase services (Firestore reads/writes, Cloud Functions invocations, etc.) beyond their free tier limits. The only costs in this integration are your Lovable plan (for Edge Function usage) and optionally a paid Firebase plan for other Firebase features.
Why do I need an Edge Function to send notifications instead of calling FCM directly from the frontend?
The FCM HTTP v1 API requires authentication with a Google OAuth access token derived from your Firebase service account private key. Service account private keys are highly sensitive credentials — anyone who has the key can impersonate your Firebase project and send notifications to any of your users. Keeping the key in an Edge Function means it never reaches the browser, preventing exposure through browser developer tools or JavaScript source inspection.
What happens to FCM tokens when users clear their browser data or uninstall the app?
When a user clears browser data, the service worker and FCM registration are removed. The next time the user visits your app and grants notification permission again, a new token is generated. The old token in your database becomes invalid — sending to it will return an error from FCM. Implement error handling in your Edge Function that detects invalid token errors (FCM returns UNREGISTERED or INVALID_ARGUMENT for stale tokens) and deletes them from the device_tokens table automatically.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation