DEV Community

Tomo
Tomo

Posted on AI-assisted

Organizations vs Projects in Supabase: Move a Project Between Orgs, Audit Dormant Ones, and Build a Small Inventory Board

When your Supabase projects start to pile up, three questions come fast: what belongs to the organization and what to the project, how do I move a project, and which ones are dead? I tried all three on a throwaway setup and built a small board that shows the answer on one screen. Everything here is from my run on 2026-10-27, and prices and rules change.

How this was made: I wrote this with Claude's help: it drafted, and I ran every step, edited the text, and checked the results. The mistakes are mine. This is a personal learning project, not an official guide, and I don't work for Supabase.

The non-technical version of the idea (company and offices): https://medium.com/p/f19c78f2ba25
The code: https://github.com/tomozilla/project-inventory-en-demo

What I did and did not do

  • I created a free organization, moved a paused free project into it, moved it back, and deleted the temporary organization. All on free plans.
  • Not tested: moving an active project, moving from a paid to a free organization, permission changes after a move, project-scoped roles. Those come from the docs and are marked as such.
  • Cost of this walkthrough: $0. Paid numbers come from the pricing page and docs as of 2026-10-27.
  • The sample data in the repo is fake. The screenshots show my demo projects only.

What belongs where

The Billing FAQ says it plainly: an organization holds many projects; each project is a dedicated instance with its own Storage, Auth, Functions and Realtime; each organization has one subscription plan; and project add-ons (compute, PITR, custom domains) are set per project but billed through the organization.

My own cheat sheet:

Organization Project
Plan and billing here (uses the organization's)
Members and roles here covered by the organization's
SSO, audit logs here
Database, Auth, Storage, Functions here
Compute size, backups, PITR here
API keys, URL here

Two consequences from the docs: a paid plan applies to every project in that organization, and you cannot mix free and paid projects inside one organization.

What the dashboard shows

The organization's project list: one running project and one marked "Project is paused".

My free organization has two projects. One is paused. The pricing page says free projects pause after a week of inactivity, so I assume that is why. I did not confirm the reason on screen.

Organization settings menu: General, Security, SSO, OAuth Apps, Audit Logs, Audit Log Drains, Legal Documents.

Settings like SSO and audit logs live on the organization side, not in a project.

Three ways to split

  1. By department. One project per team. One bill.
  2. By customer. A dedicated project per client. Nothing mixes, but it gets heavy as you grow.
  3. Live and test. Keep production and experiments apart.

I decide by asking what must never be mixed.

One docs detail surprised me: the limit of two active free projects is counted across every Owner and Admin of the organization. If another admin already used their allowance, you can't launch another free project there. The docs suggest creating another free organization or changing that member's role. I am the only member of my organization, so I did not reproduce this.

Moving a project between organizations

Create the target organization at /dashboard/new. Check that the plan says Free - $0/month.

The "Create a new organization" form: name, type Personal, plan Free - $0/month.

Then open the project's Project Settings -> General and scroll to Transfer project.

The transfer dialog: warnings about downtime, permissions and features, and a target organization picker.

The dialog warns about three things: possible downtime when moving from paid to free, permission changes depending on your role in the target, and lost features (for example image transformations) on a smaller plan.

I picked the new organization and confirmed. It took a few seconds, and the project reference stayed the same.

The new organization's project list, with the moved project.

Then I moved it back the same way and deleted the temporary organization (Organization Settings -> General -> Delete organization). Deleting asks you to type the organization's slug. I checked the page title first, because it is permanent.

What the docs say, which I did not test:

  • You must be an Owner of the source organization and at least a member of the target.
  • No active GitHub integration, no project-scoped roles (Team and Enterprise), and no log drains.
  • Usage is billed to the source organization until the transfer and to the target after it.
  • Moving to a free organization checks that you stay within two free projects, so upgrade the target first if needed.

Auditing dormant projects with the CLI

You don't have to open every project. The CLI lists them (read-only):

supabase projects list --output json
Enter fullscreen mode Exit fullscreen mode

Each project comes back with ref, name, organization_id, region, created_at and status. In my run a paused project showed "status": "INACTIVE".

Two limits matter:

  • The output has no plan and no last-used date. It only has the creation date. "Paused and at least 90 days old" is a good reason to look, not proof that nobody uses it.
  • It shows the organization id, not its name.

Before deleting anything, ask the owner. Paused projects are not charged for compute according to the docs, so there is no rush.

The inventory board

I turned that into a page: paste the CLI output, and see projects grouped by organization.

The first screen: a paste box and a Load sample data button.

It is a static page. No server, no project, no access token, and what you paste stays in the browser.

Architecture: terminal output pasted into the board, a view, and a box for what the CLI output lacks (plan, last-used date).

Because the output has no plan, you pick it per organization. The estimate counts running projects only:

monthly = Math.max(0, active * 10 - 10);   // Micro $10 per running project, minus the $10 included
monthly += plan === "pro" ? 25 : 599;       // plan fee
Enter fullscreen mode Exit fullscreen mode

Run it from the repository root, because the sample button reads ../sample/:

python3 -m http.server 8000     # then open http://localhost:8000/app/
node app/test.js                # 13 checks
supabase projects list --output json | pbcopy   # your own data (macOS)
Enter fullscreen mode Exit fullscreen mode

With the sample, I set organization 1 to free and organization 2 to pro:

Three organizations: a free one with a warning that 3 projects are running but Free allows 2, a pro one at about $25 with a "dormant candidate" mark on a 229-day-old paused project, and one with no plan chosen.

  • Organization 1 (free): 3 running projects, and a warning that Free allows 2.
  • Organization 2 (pro): about $25 a month. The paused project is not counted. The 229-day-old paused one is marked a dormant candidate.
  • Organization 3: I didn't choose a plan, so it says no estimate instead of assuming free.

I also checked that the parser reads my real CLI output (3 projects in 2 organizations). I looked at the counts only and kept the names out of this article and the repo.

Stumbles

Broken input

I decided the page should never go blank silently. A broken paste shows the reason in red, and rows missing ref, name, organization_id, status or created_at are skipped with a count, not dropped quietly.

A red message: Not valid JSON, with the position of the error.

Small things I fixed

The page said "1 projects", and the per-organization tables had different column widths. Both fixed.

Limits I can't fix

  • No plan and no last-used date in the CLI output, so the board can't say a project is unused.
  • No organization names, only ids. You match them to your own list.
  • Not tested: hundreds of projects, phone width, opening the page as a file, and output that includes organizations you don't own.

What it costs

  • This walkthrough and the board: $0.
  • For a paid organization, my estimate is plan fee + running projects x about $10 - $10 included. The docs give the same example: 3 Micro projects on Pro is $25 + $30 - $10 = $45 a month. Not compared with an invoice, and usage overage, tax and add-ons are not included.

If I took this further

Ideas I haven't tried: a separate file that maps organization ids to names and owners, a diff against the last inventory so only new or newly paused projects show up, and never letting the tool decide that something is safe to delete.

Takeaways

  • Organization = plan, billing, members. Project = data, compute, keys.
  • Split by what must never be mixed.
  • A transfer is short and, in my test, kept the project reference. Read the prerequisites first.
  • An inventory is for confirming, not deleting. The CLI can't tell you a project is unused.

Next in this series: how to read your bill.

Top comments (0)