apps/docs/content/guides/deployment/branching/working-with-branches.mdx
This guide covers how to work with Supabase branches effectively, including migration management, seeding behavior, and development workflows.
You can subscribe to webhook notifications when an action run completes on a persistent branch. The payload format follows the webhook standards.
{
"type": "run.completed",
"timestamp": "2025-10-17T02:27:18.705861793Z",
"data": {
"project_ref": "xuqpsshjxdecrwdyuxvs",
"details_url": "https://supabase.com/dashboard/project/xuqpsshjxdecrwdyuxvs/branches",
"action_run": {
"id": "d5f8b4298d0a4d37b99e255c7837e7af",
"created_at": "2025-10-17T02:27:10.133329324Z"
"steps": [
{
"name": "clone",
"status": "exited",
"updated_at": "2025-10-17T02:27:10.788435466Z"
},
{
"name": "pull",
"status": "exited",
"updated_at": "2025-10-17T02:27:11.701742857Z"
},
{
"name": "health",
"status": "exited",
"updated_at": "2025-10-17T02:27:12.79205717Z"
},
{
"name": "configure",
"status": "exited",
"updated_at": "2025-10-17T02:27:13.726839657Z"
},
{
"name": "migrate",
"status": "exited",
"updated_at": "2025-10-17T02:27:14.97017507Z"
},
{
"name": "seed",
"status": "exited",
"updated_at": "2025-10-17T02:27:15.637684921Z"
},
{
"name": "deploy",
"status": "exited",
"updated_at": "2025-10-17T02:27:18.604193114Z"
}
]
}
}
}
We recommend registering a single webhooks processor that dispatches events to downstream services based on the payload type. The easiest way to do that is by deploying an Edge Function. For example, the following Edge Function listens for run completed events to notify a Slack channel.
// Setup type definitions for built-in Supabase Runtime APIs
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
console.log('Branching notification booted!')
const slack = Deno.env.get('SLACK_WEBHOOK_URL') ?? ''
Deno.serve(async (request) => {
const body = await request.json()
const blocks = [
{
type: 'header',
text: {
type: 'plain_text',
text: `Action run ${body.data.action_run.failure ? 'failed' : 'completed'}`,
emoji: true,
},
},
{
type: 'section',
fields: [
{
type: 'mrkdwn',
text: `*Branch ref:*\n${body.data.project_ref}`,
},
{
type: 'mrkdwn',
text: `*Run ID:*\n${body.data.action_run.id}`,
},
],
},
{
type: 'section',
fields: [
{
type: 'mrkdwn',
text: `*Started at:*\n${body.data.action_run.created_at}`,
},
{
type: 'mrkdwn',
text: `*Completed at:*\n${body.timestamp}`,
},
],
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `<${body.data.details_url}|View logs>`,
},
},
]
const resp = await fetch(slack, {
method: 'POST',
body: JSON.stringify({
blocks,
}),
})
const message = await resp.text()
return new Response(
JSON.stringify({
message,
}),
{
status: 200,
}
)
})
Create a [Slack webhook URL](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) and set it as Function secrets.
```markdown
supabase secrets set --project-ref <branch-ref> SLACK_WEBHOOK_URL=<your-webhook-url>
```
</StepHikeCompact.Details>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}> <StepHikeCompact.Details title="Deploy your webhooks processor" fullWidth>
Create and deploy an Edge Function to process webhooks.
```markdown
supabase functions deploy --project-ref <branch-ref> --use-api notify-slack
```
</StepHikeCompact.Details>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}> <StepHikeCompact.Details title="Update branch notification URL" fullWidth>
Update the notification URL of your target branch to point to your Edge Function.
```markdown
supabase branches update <branch-ref> --notify-url https://<branch-ref>.supabase.co/functions/v1/notify-slack
```
</StepHikeCompact.Details>
</StepHikeCompact.Step> </StepHikeCompact>
After completing the steps above, you should receive a Slack message whenever an action run completes on your target branch.
Migrations are run in sequential order. Each migration builds upon the previous one.
The preview branch has a record of which migrations have been applied, and only applies new migrations for each commit. This can create an issue when rolling back migrations.
If you want to use your own ORM for managing migrations and seed scripts, you will need to run them in GitHub Actions after the preview branch is ready. The branch credentials can be fetched using the following example GHA workflow.
name: Custom ORM
on:
pull_request:
types:
- opened
- reopened
- synchronize
branches:
- main
paths:
- 'supabase/**'
jobs:
wait:
runs-on: ubuntu-latest
outputs:
status: ${{ steps.check.outputs.conclusion }}
steps:
- uses: fountainhead/[email protected]
id: check
with:
checkName: Supabase Preview
ref: ${{ github.event.pull_request.head.sha || github.sha }}
token: ${{ secrets.GITHUB_TOKEN }}
migrate:
needs:
- wait
if: ${{ needs.wait.outputs.status == 'success' }}
runs-on: ubuntu-latest
env:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
SUPABASE_PROJECT_ID: ${{ secrets.SUPABASE_PROJECT_ID }}
steps:
- uses: supabase/setup-cli@v1
with:
version: latest
- run: supabase --experimental branches get "$GITHUB_HEAD_REF" -o env >> $GITHUB_ENV
- name: Custom ORM migration
run: psql "$POSTGRES_URL_NON_POOLING" -c 'select 1'
You might want to roll back changes you've made in an earlier migration change. For example, you may have pushed a migration file containing schema changes you no longer want.
To fix this, push the latest changes, then delete the preview branch in Supabase and reopen it.
The new preview branch is reseeded from the ./supabase/seed.sql file by default. Any additional data changes made on the old preview branch are lost. This is equivalent to running supabase db reset locally. All migrations are rerun in sequential order.
Your Preview Branches are seeded with sample data using the same as local seeding behavior.
The database is only seeded once, when the preview branch is created. To rerun seeding, delete the preview branch and recreate it by closing, and reopening your pull request.
You can develop with branches using either local or remote development workflows.
supabase db diffsupabase db pullUse the branch dropdown in the Supabase dashboard to switch between different branches. Each branch has its own:
Each branch has unique credentials that you can find in the dashboard:
Branches are completely isolated from each other. Changes made in one branch don't affect others, including: