Communication · Intermediate

Notifications

Real-time in-app notifications via Supabase Realtime — unread badge, mark-as-read, archive, bulk ops, Web Push subscribe/unsubscribe, and a bell dropdown component. Web Push (VAPID) is optional and can be enabled independently of in-app notifications.

Read the Getting Access guideif you haven't yet.

1. Get the file

Sign in at marrowstack.dev, open the Notifications block, and click Copy all files. Paste the files into your project. The block exports both server functions and a client hook/component.

2. Prerequisites

  • Next.js 14 or 15, App Router
  • Supabase project with Realtime enabled (enabled by default)
  • For Web Push: generate VAPID keys with npx web-push generate-vapid-keys

3. Install

  1. Copy the block files from the block detail page into your project.
  2. Install peer dependencies:
bash
npm install @supabase/supabase-js zod web-push
npm install -D @types/web-push

4. Environment

VariableRequiredDefaultPurpose
NEXT_PUBLIC_SUPABASE_URLyesYour Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEYyesAnon key — used client-side for Realtime subscriptions
SUPABASE_SERVICE_ROLE_KEYyesService role key — used server-side to create notifications
VAPID_PUBLIC_KEYnoVAPID public key for Web Push. Generate: npx web-push generate-vapid-keys
VAPID_PRIVATE_KEYnoVAPID private key — server-side only
VAPID_EMAILnoContact email for Web Push, e.g. mailto:admin@yourdomain.com

5. Database

Run the MIGRATION constant in Supabase SQL Editor. Creates:

  • notifications — title, body, type, read_at, archived_at, user_id
  • push_subscriptions — Web Push endpoint and keys per user device

Also enables Supabase Realtime on the notifications table so the client hook receives live updates without polling.

6. Wire it in

app/api/notifications/route.ts
import { createNotification, markAllRead } from '@/lib/notifications'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { NextRequest, NextResponse } from 'next/server'

// POST — create a notification for a user
export async function POST(req: NextRequest) {
  const { userId, title, body, type } = await req.json()
  const n = await createNotification({ userId, title, body, type })
  return NextResponse.json(n)
}

// PATCH — mark all as read for the current user
export async function PATCH() {
  const session = await getServerSession(authOptions)
  if (!session?.user?.id) return new NextResponse('Unauthorized', { status: 401 })
  await markAllRead(session.user.id)
  return NextResponse.json({ ok: true })
}
Client component — bell dropdown
'use client'
import { NotificationBell } from '@/lib/notifications'
// Drop in anywhere in your layout — shows unread count and live updates
export function Header() {
  return (
    <header>
      <NotificationBell />
    </header>
  )
}

7. Verify it works

  1. Call createNotification() for a user and confirm the row appears in the notifications table.
  2. Open the app in the browser — the bell component should show an unread badge within 1 second via Realtime.
  3. Click a notification — confirm read_at is set in the database.
  4. (Optional) Subscribe to Web Push and confirm a row appears in push_subscriptions. Send a push via sendPushNotification() and confirm it arrives.

8. Failure modes & fixes

Realtime not working — no live updates

Cause: Realtime is not enabled for the notifications table, or the Supabase project is on a plan that limits Realtime connections.

Fix: In Supabase Dashboard → Database → Replication, enable the notifications table for Realtime. Check your plan's connection limit.

Web Push: invalid VAPID keys

Cause: VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY were not generated as a pair, or were rotated without updating subscriptions.

Fix: Generate a fresh pair with npx web-push generate-vapid-keys. Existing subscriptions will need to re-subscribe after a key change.

relation 'notifications' does not exist

Cause: The migration hasn't been run.

Fix: Run the MIGRATION constant in Supabase SQL Editor.