Key takeaways
- Supabase has no built-in push. You store device tokens, then have an Edge Function call FCM when a row changes. iOS reaches through APNs, which FCM handles once you upload the key.
- FCM's legacy API stopped working in June 2024. The path today is HTTP v1, signed with a Google service account key over OAuth.
- Row-level security on the tokens table matters. Without a policy, any signed-in client can read every token, and a token plus your project id is enough to push to a stranger's phone.
- Pushfire runs this pipeline on Supabase for you. You keep the Postgres database and add a Flutter SDK and a console in place of the trigger and the function you would write by hand.
Supabase gives you a Postgres database with auth built in, and Edge Functions for server-side code. It does not send push notifications. There's no supabase.push.send() to call, so getting a banner onto a phone means building a small pipeline yourself, and most of the work sits between the database and the delivery service.
This guide covers Supabase push notifications end to end. You'll store device tokens under row-level security, fire an Edge Function when a row changes, and call FCM over its HTTP v1 API so Android and iOS both light up. After that I show the managed version, because Pushfire runs this same pipeline on Supabase and it's worth seeing what it takes off your plate and what it leaves behind. Pushfire is our product, so weigh that as you read.
The four pieces of any push setup
Every push notification, whatever the stack, needs four things: a device token that identifies the phone, somewhere to keep that token, something that decides when to send, and a service that delivers the message. Supabase covers the middle two. Firebase Cloud Messaging handles delivery for Android and web, and it forwards to Apple's APNs for iOS once you give it an APNs key.
Your job is to connect them. A row lands in a table, a trigger or a database webhook fires, and an Edge Function does the sending. That is the shape of the whole build, and it's the shape Pushfire automates further down.
Step 1: Set up Firebase and the FCM v1 API
Create a Firebase project and add your Android and iOS apps to it. For Android you drop in google-services.json. For iOS you add GoogleService-Info.plist and, in the Firebase console, upload an APNs authentication key (a .p8 from your Apple developer account). Miss the APNs key and iOS notifications fail silently, which is a common half-day lost.
Now the credentials. Open Project settings, then Service accounts, and generate a new private key. You get a JSON file. HTTP v1 signs its requests with that service account over OAuth. The old server key approach is gone: Google removed the legacy FCM API in June 2024, so any tutorial that hands you a server key string is out of date. Keep the JSON out of your repository.
Step 2: Store device tokens with row-level security
You need a table that maps a user to their device tokens. One user can have several, one per device, so a unique constraint on the pair keeps duplicates out.
The policy is the part people skip. Without it, any authenticated client can read every row in the table, and a valid token combined with your project id is enough to send a push to someone else's phone. Call FCM from the server with the service role key, and let the client touch only its own rows.
Step 3: Register the token from the client
On the client you ask for notification permission, read the FCM token, and upsert it into the table. This is Flutter with firebase_messaging. The same idea carries to React Native, Expo, or a web app that uses a VAPID key.
Listen for onTokenRefresh, since tokens rotate over the life of an install. Delete the row on sign-out so you stop targeting a device the user has left.
Step 4: Decide when a notification goes out
You need a trigger point. Create a notifications table with a recipient, a title, a body, and an optional data payload. When a row is inserted, something has to reach out to your Edge Function, because Postgres can't call FCM directly.
There are two ways to make that call. A database webhook, configured under Database then Webhooks, fires on insert and POSTs the new row to your function, and you set it up in the dashboard with no SQL. A Postgres trigger using the pg_net extension does the same from inside the database if you want the logic living in your migrations. For a first build the webhook is less to get wrong.
Step 5: Send from an Edge Function
The function reads the inserted row, looks up the recipient's tokens with the service role key, mints an OAuth token from the service account, and posts to FCM once per device token.
Set the service account as a secret with the CLI, supabase secrets set FIREBASE_SERVICE_ACCOUNT="$(cat service-account.json)", then deploy with supabase functions deploy push. In the webhook, add an auth header carrying the service role key so only Supabase can invoke the function.
Step 6: Send one, then handle the messy parts
Insert a row into notifications with your own user id as the recipient and watch the device. Once it lands, the demo is finished and the upkeep begins.
FCM tells you when a token is dead by returning UNREGISTERED or NOT_FOUND, and you delete those rows so you stop sending into the void. Past that you carry retries on 5xx responses, rate limiting, batching to more than one recipient, scheduling with timezones, web push and its separate VAPID setup, and richer iOS payloads. Each of these is a small job by itself, and they pile up the moment you support more than one kind of notification.
When the hand-built version is the right call
If you send one or two notification types and want no extra service in your stack, the Edge Function above is enough and you own every line of it. Some teams stop here on purpose, and that's a reasonable place to stop.
The cost shows up with scale. Cleaning tokens, segmenting past a single user lookup, laying out a two-step sequence with a delay, testing copy against real opens, reading delivery numbers over time: at that point you're maintaining a small notifications service. That is the gap a managed tool is meant to fill, and it's where a larger engagement platform comparison becomes worth a look.
The same pipeline with Pushfire
Pushfire is built on Supabase, so the model you just wired up (a Postgres database, row-level security, a tokens table) is the model it runs on internally. It's our product, and here's the honest account of what changes and what doesn't.
You add the Flutter SDK, pushfire_sdk, from pub.dev. It registers the FCM token for you, and when you set the auth provider to Supabase it logs the subscriber in the moment Supabase auth emits a signed-in event, so the token-to-user mapping you wrote by hand in Step 2 arrives for free.
In place of the trigger and the Edge Function, you lay the send out on a drag-and-drop canvas. The nodes you work with:
- Push Notification: the message that goes out.
- Wait: a delay before the next step.
- Conditional: a branch on a tag or a subscriber attribute.
One-off sends run through Broadcast rather than a workflow. To fire a workflow from your backend you call the Execute Workflow API with a scoped token, which is the same "a row changed, send something" moment moved behind one HTTP call. Delivery still goes through FCM under the hood, so iOS, Android, and web push come from the single setup.
There's also an onboarding path for AI coding agents. A one-line skills install command drops an onboarding file into tools like Claude Code, Cursor, and Copilot, and the agent wires the SDK into your app for you.
What Pushfire does not remove: you still create the Firebase project and upload the APNs key. That requirement comes from Apple and Google, and no tool skips it.
Where it isn't the right fit: for raw FCM delivery with no console and a single message type, the manual function wins on simplicity. If SMS has to be a primary channel today, or you need a deep email-marketing suite, a larger engagement platform is the closer match.
On price, the free tier covers up to 5,000 subscribers with push only, and the Pro plan runs $29 a month for up to 50,000 subscribers with email and SMS connectors.
Frequently asked questions
Does Supabase send push notifications on its own?
No. Supabase stores your data and runs Edge Functions, and you connect a delivery service to it. FCM handles Android and web, APNs handles iOS, and FCM forwards to APNs once you upload the key. Supabase decides when to send and to whom, and FCM moves the message to the device.
Do I need Firebase if I use Supabase?
For push, yes. FCM is the delivery layer for Android and web, and it routes iOS notifications through APNs. Supabase holds the tokens and the trigger logic, while Firebase carries the message the last mile to the phone.
Why did my Supabase push notifications stop working in 2024?
Google removed the FCM legacy HTTP API in June 2024. Any code that authenticated with a server key stopped sending on that date. Switch to the HTTP v1 endpoint, which signs each request with a service account and an OAuth access token.
How do I keep device tokens secure in Supabase?
Turn on row-level security for the tokens table and write a policy so each user reads and writes only their own rows. Send from an Edge Function with the service role key so your Firebase credentials never reach the client. Delete tokens that FCM reports as unregistered so old devices drop off the list.
Does Pushfire replace this whole setup?
It replaces the trigger and the Edge Function, and it adds a console and a Flutter SDK on top of the same Supabase data model. You still set up the Firebase project and the APNs key yourself, since those come from Apple and Google and sit outside any push tool.
Send from your Supabase data without the plumbing
Pushfire sits on the Supabase model you just built and turns the trigger and the Edge Function into a canvas and an SDK. Point it at your project and see how the pieces line up at pushfire.app.
