# 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. - Company: Supabase (https://www.justpush.io/recipes/supabase) - Event: `INSERT` (Insert) - Tags: Backend & auth, Database changes - Install: https://studio.justpush.io/recipes/supabase/row-inserted - Web page: https://www.justpush.io/recipes/supabase/row-inserted ## Setup 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 (shown in Studio after install) as the URL, adding any options from the setup notes below, and create the webhook. 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 (shown in Studio after install)?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. ## Code Studio calls `handleRequest(request)` with the incoming webhook (`{ method, headers, body, query, raw }`) and sends the message object it returns. Returning `null` sends nothing. ```js // Supabase โ†’ Database Webhook INSERT (new row) // Fires when a row is inserted into a table you picked in Supabase's Database Webhooks. // You choose what the push says with query parameters on the webhook URL (see INSTALL.md). // Docs: https://supabase.com/docs/guides/database/webhooks // // Payload: { type: "INSERT", table, schema, record: { โ€ฆ }, old_record: null } // Columns that rarely make a useful message on their own. const BORING = new Set(["id", "uuid", "created_at", "updated_at", "inserted_at", "deleted_at"]) function has(obj, key) { return obj && typeof obj === "object" && Object.prototype.hasOwnProperty.call(obj, key) } // Keep long values readable in a notification. function clip(value, max = 80) { const s = String(value).replace(/\s+/g, " ").trim() return s.length > max ? s.slice(0, max - 1) + "โ€ฆ" : s } // A column's value as text, or null when it's missing or empty. function show(record, key) { if (!has(record, key)) return null const value = record[key] if (value === null || value === undefined || value === "") return null return clip(typeof value === "object" ? JSON.stringify(value) : value) } // Replace {column} (and {table}) in a template with the row's values. function fill(template, record, table) { return template .replace(/\{([\w.-]+)\}/g, (_, key) => (key === "table" && !has(record, "table") ? table : show(record, key) ?? "")) .replace(/\s+/g, " ") .trim() } // Comma-separated list from a query parameter. function list(value) { return typeof value === "string" ? value.split(",").map((s) => s.trim()).filter(Boolean) : [] } // Only link to real http(s) URLs. function link(url, cta) { return typeof url === "string" && /^https?:\/\/\S+$/.test(url) ? [{ cta, url }] : [] } function handleRequest(request) { const body = request.body && typeof request.body === "object" ? request.body : {} const query = request.query && typeof request.query === "object" ? request.query : {} const record = body.record // Guard: only inserts from a Database Webhook. if (body.type !== "INSERT" || !record || typeof record !== "object" || typeof body.table !== "string") { console.log(`Ignoring Supabase "${body.type ?? "unknown"}" payload`) return null } // ?table=orders,signups limits which tables push (handy when one integration takes several hooks). const tables = list(query.table) if (tables.length && !tables.includes(body.table) && !tables.includes(`${body.schema}.${body.table}`)) { console.log(`Skipping table ${body.schema}.${body.table}`) return null } // New auth users have their own recipe; don't push their whole row by accident. if (!tables.length && body.schema === "auth") { console.log("Skipping auth schema row โ€” use the Supabase New user recipe") return null } const table = body.table // Message: ?message=โ€ฆ template, else the ?field=โ€ฆ columns, else a few scalar columns. let message = typeof query.message === "string" ? fill(query.message, record, table) : "" if (!message) { const fields = list(query.field) const chosen = fields.length ? fields : Object.keys(record) .filter((k) => !BORING.has(k) && ["string", "number", "boolean"].includes(typeof record[k])) .slice(0, 3) const parts = chosen.map((k) => [k, show(record, k)]).filter(([, v]) => v !== null) message = parts.length === 1 && fields.length === 1 ? parts[0][1] : parts.map(([k, v]) => `${k}: ${v}`).join(" ยท ") } if (!message) message = show(record, "id") ? `id ${show(record, "id")}` : "A new row was added" const title = (typeof query.title === "string" && fill(query.title, record, table)) || `๐Ÿ†• New row in ${table}` // ?priority=-2โ€ฆ2 overrides the default. const wanted = Number(query.priority) const priority = query.priority !== undefined && Number.isInteger(wanted) && wanted >= -2 && wanted <= 2 ? wanted : 0 return { title, message, topic: "Supabase", priority, // Normal by default โ€” you picked this table because you care about it buttons: typeof query.url === "string" ? link(fill(query.url, record, table).replace(/ /g, "%20"), "Open") : [], } } ``` ## Supabase INSERT webhook payload (sample) ```json { "type": "INSERT", "table": "waitlist", "record": { "id": 128, "plan": "pro", "email": "ada@example.com", "referrer": "producthunt", "created_at": "2026-09-29T09:12:44.512Z" }, "schema": "public", "old_record": null } ``` ## FAQ ### 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.