Add Real-Time Notifications to Any Web App With Supabase

Add secure in-app notifications that appear instantly, track unread activity, and stay available after refresh with Supabase Realtime.

Roadmap & Resources

Define Your Notifications

A notification tells one person that something happened in your app that they need to know about — and one click takes them to it:

Example notifications

Task assigned

The assigned person sees: You were assigned to “Homepage redesign”.

Order status changed

The customer sees: Your order is now ready.

New reply

The ticket owner sees: Support replied to your ticket.

These are only examples. Pick the few events your users genuinely need to know about — too many notifications make the important ones easy to miss.

Plan Your Notifications

Plan Your Notifications

Describe the events that matter in your app and let AI turn them into a clear notification plan before anything is built.

I want to add in-app notifications to this existing web app, so signed-in users see important events inside the app as they happen. The app uses Supabase. What the app does: {{APP_PURPOSE}} Events that should create a notification: {{NOTIFICATION_EVENTS}} Who should receive each notification: {{RECIPIENTS}} What users should see: {{MESSAGES}} Where clicking a notification should take them: {{DESTINATIONS}} Notification types that should behave differently: {{SPECIAL_RULES}} Do not change any files, and do not change the database. This task only plans the notifications. Look through the project only enough to use its real names — the actual features, pages and routes, such as tasks, orders or tickets — so the plan describes this app rather than a generic template. Do not audit the code or the database yet. That is the next step. Turn my answers into a short notification plan, with one row per notification: 1. Event: what happens in the app 2. Recipient: exactly who receives it 3. Type: a short, stable name, such as task_assigned or order_status_changed 4. Title and message: what the user sees — short and specific, for example: You were assigned to “Homepage redesign” 5. Destination: the page in this app that opens when it is clicked, or none 6. Behavior: anything this notification does differently, or standard Rules: - Use the events I listed. If an important event seems to be missing, suggest it and ask me before adding it. - Do not notify people about their own actions, such as assigning a task to themselves — unless it is a result they are waiting for, such as a finished export. - Keep every message short and free of private details — no passwords, payment details, private documents or full AI prompts. The destination page holds the details. - If my answers conflict or leave a gap, ask me instead of guessing. Finish with the complete plan as a compact table I can copy and keep. Then wait for my approval. Do not implement anything.

What does your web app do?

A project tracker where teams create tasks, assign them and discuss the work...

Which events should create a notification?

Who should receive each notification?

Task assigned: the person assigned. New order: every admin.

What should users see in the notification?

You were assigned to “Homepage redesign”.

Where should clicking a notification take them?

Task assigned: that task's page. Order updates: the order page.

Should any notification types behave differently?

Payment problems should also show a pop-up. Everything else can wait in the list.

Adjust the plan with the AI until it matches how your app should work. Keep this conversation open — every later prompt builds on the approved plan. If you ever start a new conversation, paste the plan in first.

Approve the Notification Plan

You should now know which events create a notification, who receives each one and what they see.

Every event that should notify someone is on the plan

Each notification names exactly who receives it

Each notification has a short message and, where it helps, a page to open

I saved a copy of the final plan

Check Your Web App

Confirm Supabase Sign-In Works

Notifications belong to signed-in users, so they are added on top of the sign-in your app already has.

People can already sign up and sign in to my app with Supabase Auth

Add User Authentication to Any Website with Supabase

No sign-in yet? Set it up first, then come back.

/video/add-user-authentication-supabase

Inspect Your Project

Nothing changes in this step. The AI maps how your app works today and plans the smallest safe changes.

Check the Web App

Let AI inspect your sign-in, tables, security rules, server logic and any existing Realtime or notification code, then return a safe plan before anything changes.

Using the notification plan we just approved, inspect this project before anything is added. Do not change any files or the database in this task. If the approved plan is not in this conversation, STOP and ask me to paste it. Find and report: - How Supabase Auth is set up: the Supabase client, sign-in, sessions and how the app reads the signed-in user - The Supabase tables behind each event in the plan, and which features read or change them - For each event in the plan: exactly how it happens today — which page, function or server code makes the change, and whether it runs in the browser, in server code, in an Edge Function or in the database - Edge Functions, server routes, API endpoints, database functions and triggers that already run when these events happen - Any existing notification, activity or alert feature: tables, components, badges or emails - Whether the app already uses Supabase Realtime: which channels, whether they are public or private, and whether they use Broadcast, Presence or Postgres Changes - The main layout, navigation and dashboards, and where notifications would naturally fit - Whether a Supabase Secret or service-role key appears anywhere in browser code Do NOT assume the SQL in this repository matches what is live in Supabase. Policies, functions and triggers are often changed directly in the Supabase Dashboard. Give me ONE read-only SQL query to run in the Supabase SQL Editor. For the relevant tables only, it should report: - Whether Row Level Security is enabled - Each policy's name, command, roles, USING and WITH CHECK expressions - Existing triggers, and the functions they call - Any existing tables, columns or functions related to notifications - Every existing policy on realtime.messages The query must read metadata only. Do not select or print any user rows. Do not modify anything. I will run the query and paste the result back into this conversation. Then give me a short plan: 1. What already exists: anything we must reuse or extend instead of duplicating 2. Reliable events: which events in the plan can create a notification reliably, and any that cannot yet, with the reason 3. Where each notification is created: for each event, the trusted place — a database trigger, a database function, an Edge Function or a server route — and why 4. Changes needed: the database changes and the interface changes 5. Implementation plan: the smallest safe set of changes, in order, including anything I must fix first Rules for the plan: - If a notification system already exists, improve or complete it. Do not create a second one. - Do not rebuild or replace authentication. - Notifications for trusted events are created by the database or server code, never by the browser. - Keep existing security rules working. Do not loosen any of them. Wait for my approval before changing anything.

Run the read-only SQL it gives you in the Supabase SQL Editor, then paste the result back into the same conversation. Approve the plan once it names a trusted place to create each notification.

Create the Notification System

Create the Notifications Table

One table holds every notification, and each user can reach only their own.

Create the Notifications Table

Generate the SQL for a secure notifications table: only the fields your plan needs, per-user access rules and fast indexes.

Create the notifications table from the approved notification plan, following the implementation plan. If the approved plan or the implementation plan is not in this conversation, STOP and ask me for them. If the plan found an existing notifications table, extend it instead of creating a second one, and list every change you make to it. Fields — only what this app needs, for example: - id - the recipient's user id, referencing auth.users, with their notifications removed when the account is deleted - type, limited to the notification types in the plan - title and message - link: the in-app path that opens when it is clicked, such as /tasks/123, or empty. Never a full external URL. - the related record, such as the task or order id, where the plan needs one - a duplicate-protection key: an optional text value that is unique per recipient, so the same event can never notify the same person twice - read_at, empty until the user reads it - created_at Do not add fields the plan does not need. Keep private data out of notifications: no passwords, payment details, private documents, full AI prompts or other sensitive content. The linked page holds the details. Security: - Enable Row Level Security - Signed-in users can read only the notifications they are the recipient of - Users can mark their own notifications as read and change nothing else. Enforce it in the database: an UPDATE policy limited to their own notifications, plus column privileges that let signed-in users update read_at only. - The browser cannot create or delete notifications: no policy allows it, and signed-in and signed-out users have no insert or delete privilege on the table. Trusted database and server code creates them in a later step. - Signed-out users get no access - Never put the Supabase Secret or service-role key in browser code Indexes — only for how the app reads notifications: - the recipient with created_at, newest first, for the notification list - a partial index on the recipient for unread notifications, for the unread count - the duplicate-protection key, unique per recipient Keep notifications after they are read, and do not add automatic cleanup. If the table ever grows large, cleanup can be added later. Follow current Supabase guidance for policy performance, for example wrapping auth.uid() in a select. Give me: 1. A short list of what the SQL creates 2. ONE SQL block to run in the Supabase SQL Editor, written so it is safe to run again 3. What I should see in Supabase afterwards If this project keeps database changes in migration files, also save the same SQL as a new migration so the repository matches what is live. Do not create notifications from app events and do not add Realtime yet. Those come next.

Read the list of what the SQL creates Open the SQL Editor in your Supabase project Paste the SQL and run it once Confirm it finishes without errors

Expected result

The Table Editor shows your notifications table with Row Level Security enabled. It stays empty for now.

Test Private Access

Add one test notification and prove that only its recipient can read it.

Check Notification Access

Get a SQL snippet that adds one test notification, plus a read-only check that proves only its recipient can see it.

Help me prove the notifications table is private before anything depends on it. I have two test accounts: User A and User B. Give me two short SQL snippets for the Supabase SQL Editor: 1. Add ONE test notification for User A, found by email address. Put the email in a clearly marked placeholder. Use a type from the plan, a harmless test title and message, and no link. 2. A read-only access check. For each test account, act as that signed-in user inside a transaction that is rolled back at the end — switch to the authenticated role and set the user's id in the request's JWT claims — then count the notifications the user can see. As User B, also try to mark User A's test notification as read, and count the rows it changed. Put both emails in clearly marked placeholders, and show all the counts together in one final result. Print counts only — no email addresses and no notification contents. Do not change anything else. Expected: User A sees 1 notification, and User B sees 0 and changes 0 rows. When I paste the result back, tell me whether the table is private, and fix anything that is not before we continue.

Sign up two test accounts in your app if you do not have them yet: User A and User B Run the first snippet with User A's email Run the access check with both emails and paste the result back into the conversation

Expected result

The test user now has a secure notification record that only that user can access: User A sees 1 notification, and User B sees 0 and changes nothing.

Trigger Notifications From Real App Events

Trusted events only

Your database or server creates each notification, and only after the real action succeeds. The browser never creates one, because anyone can send any request from a browser.

Create Notifications From Real Events

Each event in your plan creates its notification in a trusted place, only after the action succeeds.

Create Notifications From Real Events

Connect every event in your plan to the notifications table from trusted database or server code, with no duplicates and no notifications for failed actions.

Now connect the events in the approved notification plan to the notifications table, following the implementation plan. If the approved plan or the implementation plan is not in this conversation, STOP and ask me for them. For each event, create its notification in the trusted place the plan chose: - Database trigger or function — when the event is a change in a Supabase table, such as a task's assignee or an order's status. Fire only when the relevant value actually changes, so saving the same value again creates nothing. - Edge Function or server route — when the event already happens in server code, such as a payment or a finished background job. Create the notification after the action has succeeded, in the same server code, with the server-side Supabase client. For every notification: - Create it only when the real action succeeded. If the action fails or is rolled back, no notification may remain. - A problem creating a notification must never block or undo the real action. In a trigger, catch the error and raise a warning instead; in server code, log it and still complete the action. - Send it only to the recipient the plan names, worked out from the real record — never from a user id sent by the browser - Do not notify people about their own action, unless the plan says it is a result they are waiting for - Fill in the title, message and link from the plan with the real record's details. Links are in-app paths only. - Set the duplicate-protection key from the event and the real record it is about — for example ticket_reply:<reply id> — and skip the insert when that key already exists for this recipient, so a retried action never notifies anyone twice - Keep private data out of the title and message Security: - Every database function that creates notifications is security definer with a fixed, empty search_path - None of them can be called from the browser: revoke execute from public, anon and authenticated, or keep them in a schema the API does not expose - The Supabase Secret or service-role key stays in server code only - Do not create notifications from browser code Build only what the plan's events need — no generic event system. Keep existing behavior working exactly as before. Give me: 1. For each event: where its notification is now created, and why that place is trusted 2. ONE SQL block to run in the Supabase SQL Editor for the database changes, written so it is safe to run again 3. What I should see afterwards If this project keeps database changes in migration files, also save the same SQL as a new migration. Run the project's build afterwards.

Try it with test records first

These triggers run on your real tables. If people already use your app, run the SQL at a quiet time and check one event straight away. If the action itself starts failing, ask the AI to fix the trigger or remove it.

Run the SQL once in the Supabase SQL Editor Let the AI finish any code changes and the build With a test account, do one event from your plan in the app Open the notifications table in the Table Editor

Expected result

One new row appears in the notifications table for the right recipient, with the title, message and link from your plan.

Broadcast New Notifications

A database trigger announces each new notification on a private topic that belongs to its recipient.

Broadcast New Notifications

Add a database trigger that sends each new notification over a private Supabase Realtime Broadcast topic for its recipient only.

Now deliver new notifications in real time with Supabase Realtime Broadcast from the database — Supabase's recommended approach for database changes. Do not use Postgres Changes for notifications, and do not add the notifications table to the supabase_realtime publication. Requirements: - A trigger on the notifications table that runs after each insert and sends the new notification with realtime.send, marked as private - Topic: one per recipient, built from their user id, for example notifications:<recipient user id>. Use exactly this format everywhere — the security rules in the next task match it. - Event name: a clear one, such as notification_created - Payload: only what the notification list needs — id, type, title, message, link, read_at and created_at. Nothing else. - The trigger function is security definer with a fixed, empty search_path, and cannot be called from the browser - The table row stays the source of truth. If a broadcast is ever missed, the notification is still saved and appears on the next load. Every notification passes through this one trigger, so it covers every creation path from the previous task. Give me: 1. ONE SQL block to run in the Supabase SQL Editor, written so it is safe to run again 2. The exact topic format and event name, so the app can subscribe to them If this project keeps database changes in migration files, also save the same SQL as a new migration. Do not change app code yet.

Run it in the SQL Editor the same way as before, and note the topic format and event name the AI gives you.

Expected result

The SQL finishes without errors. Nothing shows in your app yet — the next task decides who may receive each topic.

Protect the Notification Channel

Realtime Authorization makes sure each user can join only their own notification topic.

Protect the Notification Channel

Add Realtime Authorization rules so each signed-in user can receive only their own notification topic, and nobody can send fake notifications.

Now protect the notification topics with Supabase Realtime Authorization, using the topic format from the previous task. If the live list of realtime.messages policies from the inspection is not in this conversation, STOP and ask me for it. Private channels are authorized by Row Level Security policies on realtime.messages: SELECT policies decide who may receive messages on a topic, and INSERT policies decide who may send them. Use realtime.topic() to read the topic being joined. Requirements: - A signed-in user may receive Broadcast messages only on their own notification topic — the one built from their own auth.uid(). Never another user's. - Nobody may send to a notification topic from the browser. Only the database trigger sends to them. - Signed-out users get no access to notification topics - Policies combine permissively. If an existing policy already lets users receive or send on any topic, a new policy next to it protects nothing. Keep notification topics private even then — for example with restrictive policies that apply only to topics starting with notifications: — without breaking the channels the app already uses. - Follow current Supabase guidance for policy performance, for example wrapping auth.uid() and realtime.topic() in a select Give me: 1. A plain-language list of every policy you will add or change, and why 2. ONE SQL block to run in the Supabase SQL Editor, written so it is safe to run again 3. ONE read-only query I can run afterwards to confirm the live policies on realtime.messages If this project keeps database changes in migration files, also save the same SQL as a new migration. Do not change app code yet.

Review the policy changes before you run them

This SQL decides who can join your app's Realtime channels. Read the AI's list of changes first — a wrong policy can expose one user's notifications to another or break channels your app already uses.

Run the SQL once in the Supabase SQL Editor Run the read-only check query and paste the result back Continue once the AI confirms that only its own user can receive each notification topic

Expected result

Each notification topic can be received only by its own user, and no policy lets anyone send to one from the browser.

Verify One Real Event

Prove that one real event reaches the right user live, before you build the interface.

Check Live Delivery

Add a temporary, development-only listener that shows your own notification broadcasts in the browser console, then remove it once confirmed.

Help me confirm Realtime delivery works before we build the interface. Add a temporary, development-only listener to the app: - After sign-in, it subscribes to the signed-in user's own notification topic as a private channel, with the exact topic format and event name from the broadcast task - Before subscribing, make sure Realtime uses the user's session, for example with await supabase.realtime.setAuth(), as Supabase's docs show for private channels - It logs the subscription status and each notification it receives to the browser console - It never runs in production and never logs access tokens Tell me exactly where to look. When I confirm it works, remove the listener again.

Sign in as User B and open your browser's developer console In a private window, sign in as User A and do an event from your plan that notifies User B Watch User B's console, then check the notifications table in the Table Editor Ask the AI to remove the temporary listener

Expected result

The event creates one notification and sends it live to the intended user: User B's console shows it right away, and the notifications table has the same row.

Build the Real-Time Notification Experience

Add the Notification Center

Recent notifications, an unread count and live updates, in your app's existing design.

Add the Notification Center

Show each user's saved notifications with an unread count, and add new ones live over their private channel — in your app's existing design.

Now connect the notification system to the app's interface, following the approved notification plan and the implementation plan. If the approved plan is not in this conversation, STOP and ask me for it. Inspect the current layout, navigation and components first. Preserve the existing design, and add only what fits this app. Where notifications should appear: {{PLACEMENT}} Pop-ups for new notifications: {{TOASTS}} Anything else to include or leave out: {{EXTRA_REQUESTS}} Build: - Initial load: when a user signs in or opens the app, load their most recent notifications from the notifications table, newest first — for example the latest 20 — and their unread count. The database is the source of truth, so notifications survive a refresh, a browser restart and a lost connection. - List: each notification's title, message, a relative time such as 5 minutes ago, and a clear unread style - Unread count: a badge with the number of unread notifications, hidden when there are none - Opening a notification goes to its link. Follow in-app paths only. - Empty state: No notifications yet — nothing more - Live updates: subscribe to the signed-in user's own private notification topic, with the exact topic format and event name from the broadcast step. Make sure Realtime uses the user's session before subscribing, for example with await supabase.realtime.setAuth(). When a notification arrives, add it to the top of the list and update the unread count right away. - No duplicates: merge notifications by id, so one that arrives both live and from a load appears once - Reconnects: when the channel reconnects after a drop, reload the recent notifications and the unread count, so anything sent while offline still appears - Clean up: remove the channel when the component unmounts, when the signed-in user changes and when the user signs out. A signed-out user or the next account must never see the previous account's notifications, not even for a moment. - If Realtime is unavailable, the app keeps working and still shows the saved notifications Keep the Supabase logic in one place, such as a notifications hook or service, rather than scattered across components. Do not: - Create notifications from the browser - Use a public channel, or subscribe to any topic other than the user's own - Change database policies to make the interface work Marking notifications as read comes in the next task, so leave it out for now. When you are done, list the files you changed and where notifications now appear. Run the project's build afterwards.

Where should notifications appear?

Should new notifications also pop up?

Anything to include or leave out?

Show the 10 most recent, with a See all link to a full notifications page.

Expected result

Signed in as User B, you see their saved notifications and unread count. A new notification appears at the top straight away, without a refresh.

Add Mark as Read

Users mark their own notifications as read, and the unread count follows.

Add Mark as Read

Let users mark one or all of their notifications as read, updating Supabase and the interface together.

Now let users mark their notifications as read. - Opening a notification marks it as read. If it fits this app, also allow marking one as read without opening it. - Add Mark all as read if the list can hold several unread notifications - Save the change by setting read_at on the user's own notifications only. The database already limits users to their own notifications and to the read_at column — rely on that, and never send another user's id. - Update the list and the unread count straight away. If Supabase refuses the change or the request fails, restore the previous state and show a short message. - Read notifications stay in the list with a quieter style Do not delete notifications, and do not change database policies. When you are done, list the files you changed. Run the project's build afterwards.

Expected result

Opening a notification marks it as read, the unread count goes down, and both stay that way after a refresh.

Test Notifications & Security

Test Live Notifications in the App

Two windows side by side: User A in a normal window, User B in a private one.

Sign in as User A in a normal window and as User B in a private window As User A, do an event from your plan that notifies User B Watch User B's window, then work through the checks below

The event created the right notification — right type, message and recipient

User B sees it without refreshing

User B's unread count goes up right away

After a refresh, the notification is still there

Opening it marks it as read, and it stays read after a refresh

The unread count stays correct as notifications are read

It opens the right page, where your plan gives it one

Verify the Security Boundary

A notification that shows up correctly is not proof. This checks that Supabase itself refuses everything else.

Verify Notification Security in Supabase

Test both accounts directly against Supabase so reading, subscribing, editing and faking another user's notifications — plus failed and retried actions — are proven safe.

Test the notification rules directly at the Supabase level, not through the interface, using my two test accounts, User A and User B. Use the safest method already available in this project: 1. An existing test runner or test utilities, if the project has them 2. Otherwise a temporary LOCAL-ONLY script that uses the project's normal Supabase client and its Publishable or public key Do NOT use the Secret or service-role key. It bypasses Row Level Security and Realtime Authorization, so the test would prove nothing. Ask me to put the test accounts' credentials in local, uncommitted environment variables. Never commit passwords, never put them in source code and never print them. Run these tests: - READ: as User A, try to read User B's notifications. Expected: none returned. - SUBSCRIBE: as User A, try to join User B's private notification topic, then trigger a notification for User B. Expected: the join is refused, or User A receives nothing. - MARK AS READ: as User A, try to mark one of User B's notifications as read. Expected: zero rows changed, and it is still unread for User B. - EDIT: as User B, try to change the title, message, link or recipient of their own notification. Expected: refused. - CREATE: as User A, try to insert a notification for User B, call each database function that creates notifications, and send a message to User B's notification topic. Expected: all refused. - FAILED ACTION: trigger one event in a way the app refuses — for example a change User A is not allowed to make, or invalid input. Expected: the action fails and no notification is created. - RETRY: repeat one successful event exactly — for example save the same status again, or retry the same request. Expected: no duplicate notification. - SIGNED OUT: repeat one read and one subscription with no session. Expected: nothing returned, and the subscription is refused. Use test data only. Do not change production data. Remove any temporary script afterwards and do not commit it. Report expected versus actual for every test, then finish with exactly one verdict: NOTIFICATIONS SECURE or NEEDS ATTENTION If NEEDS ATTENTION, list only what is still failing.

Continue when the AI reports NOTIFICATIONS SECURE. If it reports NEEDS ATTENTION, fix the listed issues and run the check again.

Test Sign-Out, Account Switching and Reconnects

Notifications must follow the right account — after signing out, switching accounts or losing the connection.

In User B's window, sign out As User A, do an event that notifies User B, and watch User B's signed-out window In that window, sign in as User A, then sign out and sign in as User B Close User B's window, do one more event for User B as User A, then open the app again as User B

Nothing new appeared in the signed-out window

Each account saw only its own notifications, with no flash of the other account's list

The notification sent while User B was away was waiting when they came back

The rest of your app still works as before

Your web app now has secure real-time notifications that appear instantly, stay available after refresh, and only reach the right users.