Supabase new row notifications on your phone.

Get a push notification when a row is inserted into a Supabase table, with the title and columns you choose.

How it works

Three steps to your first push

STEP 01

Install the recipe

One click in Studio gives you a personal webhook URL.

STEP 02

Paste your URL into Supabase

Add it as a webhook for Insert.

STEP 03

Get a push on your phone

With the text, sound and buttons from the recipe.

Setup

How to set up the Supabase insert webhook

  1. Click Install in Studio and sign in. The recipe is added to your account and you get a personal webhook URL.
  2. In the Supabase dashboard, open Integrations > Database Webhooks (enable it if asked) and click Create a new hook.
  3. Pick the table and the Insert event, and choose HTTP Request with method POST.
  4. Paste your webhook URL as the URL, adding any options from the setup notes below, and create the webhook.https://••••••••/•••••••• your personal URL, shown after install
  5. Without options, the push is titled "🆕 New row in *table*" and shows up to three short columns (skipping `id` and timestamps). Add query parameters to the URL to choose what it says:
  6. | Parameter | What it does | Example |
  7. | --- | --- | --- |
  8. | `title` | The push title. `{column}` is replaced with that column's value, `{table}` with the table name. | `title=New signup: {email}` |
  9. | `field` | Comma-separated columns for the message. With one column you get just its value. | `field=email,plan` |
  10. | `message` | A full message template instead of `field`. | `message={email} joined on {plan}` |
  11. | `url` | A button link, with `{column}` placeholders. | `url=https://app.example.com/orders/{id}` |
  12. | `priority` | `-2` (lowest) to `2` (highest). Default `0`. | `priority=1` |
  13. | `table` | Only push for these tables (comma-separated). | `table=orders,signups` |
  14. For example:
  15. ```
  16. your webhook URL?title=New%20signup%3A%20%7Bemail%7D&field=plan,referrer
  17. ```
  18. Encode spaces as `%20`, `:` as `%3A` and `{` `}` as `%7B` `%7D`. You can also add the parameters one by one under **HTTP Parameters** when you create the webhook; Supabase puts them on the URL for you.
  19. Supabase sends the **whole row** to JustPush. Don't hook up tables with secrets or tokens in them, or create the trigger in SQL with a smaller payload (see the New user recipe for an example). The request isn't signed; you can add a custom header in the webhook form, but JustPush doesn't check it, so keep your endpoint URL private.
  20. Each webhook has a timeout (Supabase's own SQL example uses 1000 ms). If the `net._http_response` table shows timeouts, raise it to 5000 ms in the webhook form.
The code

What runs when the webhook arrives

Studio calls handleRequest(request) with the incoming webhook and sends the message it returns. It's yours after install; change anything.

transform.js
1// Supabase → Database Webhook INSERT (new row)
2// Fires when a row is inserted into a table you picked in Supabase's Database Webhooks.
3// You choose what the push says with query parameters on the webhook URL (see INSTALL.md).
4// Docs: https://supabase.com/docs/guides/database/webhooks
5//
6// Payload: { type: "INSERT", table, schema, record: { … }, old_record: null }
7
8// Columns that rarely make a useful message on their own.
9const BORING = new Set(["id", "uuid", "created_at", "updated_at", "inserted_at", "deleted_at"])
10
11function has(obj, key) {
12 return obj && typeof obj === "object" && Object.prototype.hasOwnProperty.call(obj, key)
13}
14
15// Keep long values readable in a notification.
16function clip(value, max = 80) {
17 const s = String(value).replace(/\s+/g, " ").trim()
18 return s.length > max ? s.slice(0, max - 1) + "…" : s
19}
20
21// A column's value as text, or null when it's missing or empty.
22function show(record, key) {
23 if (!has(record, key)) return null
24 const value = record[key]
25 if (value === null || value === undefined || value === "") return null
26 return clip(typeof value === "object" ? JSON.stringify(value) : value)
27}
28
29// Replace {column} (and {table}) in a template with the row's values.
30function fill(template, record, table) {
31 return template
32 .replace(/\{([\w.-]+)\}/g, (_, key) => (key === "table" && !has(record, "table") ? table : show(record, key) ?? ""))
33 .replace(/\s+/g, " ")
34 .trim()
35}
36
37// Comma-separated list from a query parameter.
38function list(value) {
39 return typeof value === "string" ? value.split(",").map((s) => s.trim()).filter(Boolean) : []
40}
41
42// Only link to real http(s) URLs.
43function link(url, cta) {
44 return typeof url === "string" && /^https?:\/\/\S+$/.test(url) ? [{ cta, url }] : []
45}
46
47function handleRequest(request) {
48 const body = request.body && typeof request.body === "object" ? request.body : {}
49 const query = request.query && typeof request.query === "object" ? request.query : {}
50 const record = body.record
51
52 // Guard: only inserts from a Database Webhook.
53 if (body.type !== "INSERT" || !record || typeof record !== "object" || typeof body.table !== "string") {
54 console.log(`Ignoring Supabase "${body.type ?? "unknown"}" payload`)
55 return null
56 }
57
58 // ?table=orders,signups limits which tables push (handy when one integration takes several hooks).
59 const tables = list(query.table)
60 if (tables.length && !tables.includes(body.table) && !tables.includes(`${body.schema}.${body.table}`)) {
61 console.log(`Skipping table ${body.schema}.${body.table}`)
62 return null
63 }
64 // New auth users have their own recipe; don't push their whole row by accident.
65 if (!tables.length && body.schema === "auth") {
66 console.log("Skipping auth schema row — use the Supabase New user recipe")
67 return null
68 }
69
70 const table = body.table
71
72 // Message: ?message=… template, else the ?field=… columns, else a few scalar columns.
73 let message = typeof query.message === "string" ? fill(query.message, record, table) : ""
74 if (!message) {
75 const fields = list(query.field)
76 const chosen = fields.length
77 ? fields
78 : Object.keys(record)
79 .filter((k) => !BORING.has(k) && ["string", "number", "boolean"].includes(typeof record[k]))
80 .slice(0, 3)
81 const parts = chosen.map((k) => [k, show(record, k)]).filter(([, v]) => v !== null)
82 message = parts.length === 1 && fields.length === 1 ? parts[0][1] : parts.map(([k, v]) => `${k}: ${v}`).join(" · ")
83 }
84 if (!message) message = show(record, "id") ? `id ${show(record, "id")}` : "A new row was added"
85
86 const title = (typeof query.title === "string" && fill(query.title, record, table)) || `🆕 New row in ${table}`
87
88 // ?priority=-2…2 overrides the default.
89 const wanted = Number(query.priority)
90 const priority = query.priority !== undefined && Number.isInteger(wanted) && wanted >= -2 && wanted <= 2 ? wanted : 0
91
92 return {
93 title,
94 message,
95 topic: "Supabase",
96 priority, // Normal by default — you picked this table because you care about it
97 buttons: typeof query.url === "string" ? link(fill(query.url, record, table).replace(/ /g, "%20"), "Open") : [],
98 }
99}
Payload

The Supabase INSERT webhook

This is what Supabase sends to your URL for insert. It is a sample, trimmed to the fields recipes use.

INSERT · sample.json
1{
2 "type": "INSERT",
3 "table": "waitlist",
4 "record": {
5 "id": 128,
6 "plan": "pro",
7 "email": "[email protected]",
8 "referrer": "producthunt",
9 "created_at": "2026-09-29T09:12:44.512Z"
10 },
11 "schema": "public",
12 "old_record": null
13}
FAQ

Supabase new row notifications: questions

Does this work on iPhone and Android?

Yes. Install the JustPush app from the App Store or Google Play and sign in. Every phone signed in to your account gets the notification.

Do I need to write code?

No. Install the recipe in Studio, paste your webhook URL into Supabase and you are done. The code is there if you want to change the text, the sound or the buttons.

Can I change what the notification says?

Yes. After install the recipe's code is yours. Edit it in Studio and test it against the sample payload before you save.

What does it cost?

JustPush is free for 30 days. After that it's $19.99 a year, or $39.99 once. There is no extra charge for recipes.

Ready when you are

Supabase on your phone in two minutes.

Install the recipe, paste one URL, done. Free for 30 days, no credit card required.