Skip to content

Two Supabase projects on one GitHub repository

A monorepo holds two apps, apps/a/ and apps/b/, each with its own supabase/ directory and its own Supabase project. Both projects connect to the same GitHub repository through the Supabase GitHub integration, each with its own working directory, and both have Automatic branching on. This page covers which pull requests create a preview branch on which project, and whether a CI job can wait for one project’s preview when both report checks on the same commit.

Every figure marked measured comes from experiments/github-branching in the supabase-lab harness. Everything ran on 2026-09-23 on a Pro organization in ap-southeast-1, against two Micro projects and a private throwaway repository, all created and destroyed the same day. A second session from 03:27 UTC used two fresh projects and a fresh repository for the file-kind, late-change and merge questions; its modules (GB04-GB06), artifacts and RUNLOG Runs 5-7 are at 0c90777. Project A was connected with Working directory apps/a, project B with apps/b. The branching docs the results are compared against were read the same day.12 Anything not marked measured is cited, or labelled as a design or as not run.

TL;DR:

  • A second project can connect to a repository another project already uses; B’s first attempt did not save and the reconnect did. Each connection keeps its own working directory.
  • With “Supabase changes only” off, each project created a preview for all 5 pull requests in the run, a root README.md change included. With it on, a project created a preview only for pull requests that changed files under its own <workdir>/supabase/. Migrations were tested on both projects; a seed.sql, config.toml, function or NOTES.md change under A’s <workdir>/supabase/ also triggered A.
  • Both projects posted check-runs named Supabase Preview, from the same GitHub App, to every pull request head commit read. Each project posts at least one skipped run; a project that does not branch posts 2, both skipped.
  • The docs’ wait-by-check-name workflow, path-filtered to app A, read project B’s run both times: B’s skipped on an A-only pull request, which skipped the migrate job, and B’s success on a pull request touching both apps.
  • The Management API branch list, which is per project, separated the outcomes: MIGRATIONS_FAILED for A and FUNCTIONS_DEPLOYED for B on the same pull request. The first sample, at 15 s, read FUNCTIONS_DEPLOYED on both, before CREATING_PROJECT appeared at 32 s.
  • With the setting on, a migration pushed to a pull request that opened without Supabase changes created no preview until the pull request was closed and reopened.
  • Merging a pull request that changed only app A started a production action run on both projects, with “Supabase changes only” on. One merge was tried.
GitHub repositoryapps/a/supabase/apps/b/supabase/pull requesthead commitproject Aworking directory apps/aGitHub Appproject Bworking directory apps/bGitHub Apppreview branch Aif it branchescheck-runs on the commitall named Supabase Preview2 to 6 runspreview branch Bif it branches2 to 6 runs
  1. One pull request’s head commit reaches both projects through the one GitHub App.
  2. Each project decides on its own whether to create a preview branch.
  3. Both projects post their check-runs to the same commit under the same name, whether they branch or not.
GoalSetupStatus
A pull request that touches only app A creates only A’s preview”Supabase changes only” on, on both projectsmeasured
Every pull request gets a preview on both projects”Supabase changes only” offmeasured; 2 previews per pull request, each billed for its own usage3
A CI job waits for app A’s preview before seeding or testing itpoll project A’s branch list for the pull request’s git branchbranch-list signal measured; the wait loop below was not run in CI
A CI job waits on the Supabase Preview check by namedo not use with two projects connectedmeasured: it read the other project’s run
Only one project reports on a commitAutomatic branching off on one project; create its branches from CInot run: whether a connected project with Automatic branching off still posts check-runs is untested

Preview creation: the working directory and “Supabase changes only”

Section titled “Preview creation: the working directory and “Supabase changes only””

The docs define the working directory as “the path from your repository root to the directory that contains the supabase/ folder”, and the setting as “only create Supabase branches when Supabase files change”.1 Neither says which files count when two projects share a repository. GB01 opened the same five pull requests once with the setting off on both projects and once with it on.

Pull request changesOff: preview on AOff: preview on BOn: preview on AOn: preview on B
README.md at the repository rootyesyesnono
apps/a/README.mdyesyesnono
a migration under apps/a/supabase/yesyesyesno
a migration under apps/b/supabase/yesyesnoyes
a migration under eachyesyesyesyes

With the setting off, the working directory did not limit previews: each project previewed all 5 pull requests, 10 in all. With it on, each project previewed only the pull requests that changed a migration under its own <workdir>/supabase/ (5 shapes, one pull request each); apps/a/README.md is inside A’s working directory and did not trigger A. These two runs changed only migrations; a later run tested seed.sql, config.toml, a function and a non-CLI file.

Closing the pull requests removed every preview within 2 minutes in every run; nothing was left for the harness to delete.

The connection form set Branch limit to 3, and GB00 read limit=3 back on both projects. The run raised it to 10 on both before opening any pull request, so the limit could not cap the count.

In that later run, GB04, with the setting on, each pull request changed one file under apps/a/supabase/. All four kinds triggered a preview on A, NOTES.md included, though the CLI does not read it:

File changedPreview on APreview on B
seed.sql (a second insert)yesno
config.toml (a comment line appended)yesno
functions/probe/index.ts (a new function)yesno
NOTES.md (a file the CLI does not read)yesno

On the function pull request, A’s comment marked Edge Functions with a warning: “Only Functions declared in config.toml will be automatically deployed to branches”. Whether probe deployed to the preview was not checked.

With the setting on, a pull request opened with only apps/a/README.md got no preview on A. A migration pushed to it afterwards still produced none in 240 s, and closing and reopening the pull request produced one at the first 20 s poll. This was one pull request; late seed.sql or config.toml changes were not tried.

A seed.sql change pushed to a pull request whose preview already exists did not re-seed it: 240 s after the push the new row was absent from the preview database. The docs say the same: “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.”2

With two projects connected, both projects’ check-runs land on the same commit.

On the head commitMeasured
Check-run nameSupabase Preview on every run, from both projects
GitHub App and check suitesupabase for every Supabase Preview run; one check suite on the setting-off commits (RUNLOG snapshot)
Runs from a project that branched5, once 6: 1 skipped, 1 success (or failure), 3 (once 4) in_progress when observation ended
Runs from a project that did not branch (“Supabase changes only” on)2, both skipped
Field that identifies the projectdetails_url only, of external_id, details_url, output title and output summary
external_idempty on every run
Output titleSupabase Preview or empty

Observation ran 600 s, or 480 s for GB03; the setting-off figures are a snapshot at 01:59:12 UTC, inside the window. Commits with the wait workflow also carried that workflow’s own github-actions runs. details_url links the parent project’s dashboard on the early runs and the preview branch’s own project on later ones.

GitHub’s check-runs endpoint defaults to filter=latest, “the most recent check runs”.4 In GB01 with the setting on, that view returned a single Supabase Preview run, and on 2 of the 5 pull requests, the A-only and the two-app one, the project that run belonged to changed during the build. The A-only pull request was sampled every 3 s for the first 150 s. In that window the name-filtered default view showed two different B skipped runs in turn, then A’s first in_progress, then A’s success. filter=all returns every run.

The integration also comments on each pull request, once per connected project, from supabase[bot]. Each comment starts with a [supa]:<ref> marker line. On a pull request that changed only apps/a/supabase/, B’s comment carried B’s own ref and read “This pull request has been ignored for the connected project … because there are no changes detected in apps/b/supabase directory.” A’s comment carried its preview branch’s ref instead of project A’s own ref, with its Deployments and Tasks tables and the line “Tasks are run on every commit but only new migration files are pushed. Close and reopen this PR if you want to apply changes from existing seed or migration files.”

On a one-app pull request the ignored project’s comment is the one carrying its own ref or the “has been ignored” text. A branching project’s comment carries its preview’s ref, and only the parent’s branch list (project_ref) maps that back to the parent. The early check-runs’ details_url carries the parent ref directly. Comments from two projects that both branched were not compared.

The branching docs show a CI job that waits for the preview with fountainhead/action-wait-for-check@v1.2.0 and checkName: Supabase Preview, then gates a migrate job on the result being success.2 The action lists check-runs by name with the endpoint’s default filter and returns the conclusion of the first completed one.5 GB02 added that workflow to the repository with a path filter of apps/a/supabase/**, the migrate job reduced to an echo, and “Supabase changes only” on for both projects.

Pull requestWait step returnedThat run belonged toMigrate jobA’s first success seen at
migration under apps/a onlyskippedproject Bskipped108 s
migration under each appsuccessproject Bran108 s

Times are seconds after the run started creating the pull requests, on a 30 s poll.

On the A-only pull request the wait step read B’s skipped run, started at 02:22:13 UTC. A’s own skipped run had started at 02:22:10 and already completed, so it was not the latest. A’s success run started at 02:22:58, and the poll first saw it completed at 108 s. The path filter only decides which pull requests run app A’s workflow; no B-only pull request was opened in this run. It cannot keep B’s runs off A’s commits: with “Supabase changes only” on, B posts skipped runs to every pull request commit it does not branch for.

The Management API’s branch list is per project. GB03 sampled GET /v1/projects/{ref}/branches on each parent project, matched on git_branch, on one pull request that broke A’s migration and left B’s valid:

First sample showing the value (s after opening, 15 s polling)A branch statusB branch status
15FUNCTIONS_DEPLOYEDFUNCTIONS_DEPLOYED
32CREATING_PROJECTCREATING_PROJECT
65RUNNING_MIGRATIONSRUNNING_MIGRATIONS
81MIGRATIONS_FAILEDFUNCTIONS_DEPLOYED

A loop that exits on FUNCTIONS_DEPLOYED can return at 15 s, before CREATING_PROJECT at 32 s and so before the preview project exists. The published OpenAPI document marks status as deprecated, with “List action runs to get branch status instead”.6 On A at 81 s, the preview project’s latest action run (GET /v1/projects/{preview_ref}/actions) showed migrate DEAD with deploy and seed still CREATED; on B every step was EXITED.

In GB02, 3 action runs carried a check_run_id pointing at that run’s commits, one per preview project: 1 on the A-only pull request and 2 on the two-app one. Each pointed at a check-run still in_progress at the end of the 600 s window, although every step of the action run was EXITED, so it cannot serve as a wait target. The parent projects’ action runs pointed at no check-run on those commits.

A wait loop on the branch list. The API calls are the ones the harness made; the loop itself was not run in CI.

Terminal window
# Needs SUPABASE_ACCESS_TOKEN and this app's parent project ref.
seen_busy=0
for i in $(seq 1 60); do
s=$(curl -s -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
"https://api.supabase.com/v1/projects/$PROJECT_REF/branches" |
jq -r --arg b "$GITHUB_HEAD_REF" '.[] | select(.git_branch == $b) | .status')
case "$s" in
CREATING_PROJECT|RUNNING_MIGRATIONS|MIGRATIONS_PASSED) seen_busy=1 ;;
MIGRATIONS_FAILED|FUNCTIONS_FAILED) [ "$seen_busy" = 1 ] && exit 1 ;;
FUNCTIONS_DEPLOYED) [ "$seen_busy" = 1 ] && exit 0 ;;
esac
sleep 10
done
exit 1

MIGRATIONS_PASSED and FUNCTIONS_FAILED come from the OpenAPI enum and were not observed in the run.6 The seen_busy guard skips the FUNCTIONS_DEPLOYED reading at 15 s. It also means a loop that starts after the preview has finished never sees a busy state and times out. A job that can start late could compare the branch’s updated_at with the head commit’s time instead; the run did not read updated_at, so this is untested.

Both projects had Deploy to production on, with main as the production branch. The artifact does not record that value directly, but the merge module runs only if both default branch rows read git_branch main, and it ran. The dashboard form writes that field only when the option is enabled. The docs say that on a push or merge to it, new migrations are applied and the Edge Functions and Storage buckets declared in config.toml are deployed; they do not mention “Supabase changes only” or the working directory in that section.1 GB06 merged a pull request with one migration under apps/a/supabase/ and watched both projects for 240 s:

After the mergeProject AProject B
New production action runs11
Stepsclone, pull, health, migrate, seed, deploy EXITED; configure PAUSEDthe same
The merged migration in the project’s migrations listyesno
supabase app check-runs on the merge commit, at the end of the watch3: success + 2 in_progress3: success + 2 in_progress

In the one merge tried, B’s production run reached EXITED on the same six steps as A’s (clone, pull, health, migrate, seed, deploy) for a merge that changed nothing under apps/b/, with “Supabase changes only” on. Whether it changed anything on B was not measured, including whether deploy redeployed anything (B had no functions). seed reached EXITED on both projects, but whether it seeded anything was not checked; the docs say seed files are ignored on production deploys.1

PracticeEvidenceModule
Turn “Supabase changes only” on for every project sharing the repository.Off, each project previewed all 5 pull requests; on, only those changing its own <workdir>/supabase/.GB01
Keep files that should not trigger a preview outside <workdir>/supabase/.apps/a/README.md inside A’s working directory did not trigger A with the setting on.GB01
Raise Branch limit before relying on per-pull-request previews.GB00 read limit=3 back on both projects as connected.GB00
Do not wait on the Supabase Preview check by name with two projects connected.The docs’ workflow for app A read B’s run on 2 of 2 pull requests.GB02
Read check-runs with filter=all when inspecting a shared commit.The default view returned one run per name, where a commit on which both projects branched carried 10 or 11 Supabase Preview runs: 10 on the five setting-off commits (RUNLOG snapshot) and 11 on the setting-on two-app commit.GB01
Wait on your project’s branch list, matched on git_branch, once a non-terminal status is seen.status separated MIGRATIONS_FAILED from FUNCTIONS_DEPLOYED per project, after a first reading of FUNCTIONS_DEPLOYED at 15 s.GB03
Do not wait on an action run’s check_run_id.It pointed at a check-run left in_progress after the action’s steps had finished.GB02
Open a pull request with its Supabase changes, or close and reopen it after adding them.A late migration created no preview in 240 s; close and reopen created one at the first 20 s poll.GB05
Close and reopen the pull request to re-seed a preview after changing seed.sql.The new row was absent 240 s after the push; close and reopen is the docs’ remedy2 and was not run.GB05
Expect a merge that changes one app to start a production run on the other project too.An A-only merge started a production action run on B and put 3 of B’s check-runs on the merge commit.GB06
Identify the ignored project’s bot comment by its [supa]: ref or “has been ignored” text.B’s comment carried B’s own ref and named apps/b/supabase.GB04
Map a branching project’s comment to its parent through the branch list’s project_ref.A’s comment carried its preview’s ref, on one-app pull requests only.GB04
Budget for one billed preview per project per matching pull request.Each branch is billed for its own usage;3 with the setting off, 5 pull requests made 10 previews.GB01
DocsRuntime measured
no mention of connecting more than one project to one repositorytwo projects connected with distinct working directories, both branching
”Supabase changes only” matches “Supabase files”, directory unstatedeach project previewed the pull requests changing a migration under its own <workdir>/supabase/
the wait examples filter on supabase/** and wait on Supabase Preview by namewith two projects, the wait read the other project’s run
”Supabase changes only” is described for branch creation; what happens to Supabase changes pushed after opening is unstateda migration pushed later created no preview until a close and reopen
Deploy to production is described per project; multiple projects on one repository are not mentionedboth connected projects ran production on a merge that changed only one app

None of these has been filed with Supabase as of 2026-09-23.

  • The runs used two pairs of projects, one pair per session, in one organization and one region, on one day. The per-commit counts could change without notice: 5 runs from a project that branched and 2 from one that did not. B posted 6 on the two-app pull request in GB01 with the setting on, and 5 in GB02 and GB03. On every commit read, every supabase app run was named Supabase Preview and every connected project posted to it.
  • Preview first-seen times in the GB01 runs are 30 s poll samples; creation latency was not measured. In GB04 the previews were first seen at 56 s on a 30 s poll, counted from when the run started creating the pull requests.
  • The late-change, re-seed and merge results are one pull request each.
  • GB01 with the setting off used an earlier revision of the scoping module that read only the default check-runs view. Its check-run figures come from a filter=all snapshot taken during that run and recorded in the RUNLOG, not from its published artifact.
  • The wait-by-name result depends on when the action’s first poll lands relative to the check-runs. GB02 recorded one pull request per shape on GitHub-hosted runners; a different start time can pick a different project’s run.
  • A single project on its own was not tested. In GB01 with the setting on, on the A-only pull request, A’s own skipped run came 29 s before its next run, and during those 29 s the name-filtered view showed only B’s skipped runs. The run therefore cannot say whether a wait step would ever read a lone project’s own skipped.
  • The Vercel integration was not part of the run.

Measured claims map to module ids in experiments/github-branching; the run record is its RUNLOG.md, and the redacted artifacts with their measurement tables are under out/2026-09-23/. The second session’s RUNLOG entries and artifacts are at RUNLOG.md and out/2026-09-23/ as of 0c90777.

ClaimStatusHow it was checked
Two projects connected to one repository with working directories apps/a and apps/b; Branch limit 3 on both as connectedmeasuredGB00 - connection settings read back from GET /v2/organizations/{slug}/integrations/github/connections, in the published v2 OpenAPI document7
Setting off: 5 of 5 pull requests previewed on both projectsmeasuredGB01 - five pull requests opened together, both branch lists polled every 30 s for 600 s
Setting on: previews only for changes under the project’s <workdir>/supabase/measuredGB01 - same five shapes, second run
Runs per project on a commit, skipped runs from a project that did not branch, details_url as the only identifying fieldmeasuredGB01 - check-runs read with filter=all, attributed by the refs in details_url
Default view showed B’s skipped before A’s first in_progress on an A-only pull requestmeasuredGB01 - name-filtered default view sampled every 3 s for 150 s
Docs workflow for app A returned B’s skipped and B’s successmeasuredGB02 - the docs’ workflow in the repository, the check-run id read from the action’s log
An action run’s check_run_id points at a run left in_progressmeasuredGB02 - action runs on each preview project matched against the commit’s check-runs
Branch status per project on a failing and a passing preview; FUNCTIONS_DEPLOYED at 15 s before CREATING_PROJECTmeasuredGB03 - branch list and action runs sampled every 15 s for 480 s
Four kinds of file under A’s <workdir>/supabase/ trigger with the setting on; the bot comments once per project, the ignored one naming its directorymeasuredGB04 - four pull requests under apps/a/supabase/, both branch lists polled every 30 s for 600 s, comments read before cleanup
A migration pushed after opening needs a close and reopen; a pushed seed.sql change does not re-seedmeasuredGB05 - one pull request per question, 20 s poll, 240 s phases, the seed row read on the preview with SQL
An A-only merge started a production action run on both projectsmeasuredGB06 - squash merge, both parents’ action runs and migrations lists read for 240 s
The wait loop abovedesignnot run in CI
Automatic branching off on one project, its branches created from CInot runnot tested whether such a project still posts check-runs
ModuleExperimentTestArtifact
GB00github-branchinggb00-control.tsout/2026-09-23
GB01github-branchinggb01-pr-scoping.tsout/2026-09-23
GB02github-branchinggb02-wait-by-name.tsout/2026-09-23
GB03github-branchinggb03-per-project-signal.tsout/2026-09-23
GB04github-branchinggb04-supabase-file-kinds.tsout/2026-09-23
GB05github-branchinggb05-late-changes.tsout/2026-09-23
GB06github-branchinggb06-merge-fanout.tsout/2026-09-23
  1. Supabase, “GitHub integration,” Supabase Docs. https://supabase.com/docs/guides/deployment/branching/github-integration 2 3 4

  2. Supabase, “Working with branches,” Supabase Docs. https://supabase.com/docs/guides/deployment/branching/working-with-branches 2 3 4

  3. Supabase, “Manage Branching usage,” Supabase Docs. https://supabase.com/docs/guides/platform/manage-your-usage/branching 2

  4. GitHub, “REST API endpoints for check runs,” GitHub Docs. https://docs.github.com/en/rest/checks/runs#list-check-runs-for-a-git-reference

  5. fountainhead, “src/poll.ts at v1.2.0,” action-wait-for-check. https://github.com/fountainhead/action-wait-for-check/blob/v1.2.0/src/poll.ts

  6. Supabase, “Management API OpenAPI document (BranchResponse_Output.status),” api.supabase.com. https://api.supabase.com/api/v1-json 2

  7. Supabase, “Management API v2 OpenAPI document,” api.supabase.com. https://api.supabase.com/api/v2-json