Skip to main content
ASoc
Tutorial

How to Share a Supabase Project: You Share the Organization

There is no per-project invite below the Team plan. The four roles, the 24-hour invite, and why 271 lines of migrations beat a dashboard seat for most collaborators.

The ASoc Team8 min read

You cannot share a single Supabase project on its own. Access is granted at the organization level: you invite someone from the owning organization's team settings, the invite is valid for 24 hours, and the role you give them applies to every project in that organization. Project-scoped roles exist, but only on the Team and Enterprise plans.

That surprises people, and it is usually not what they wanted anyway. "Share the project" turns out to mean four different things, and only one of them is a dashboard seat. This storefront runs a real Supabase project behind its accounts, entitlements and gated downloads, and most of what a new contributor needs is in the repository, not in the dashboard.

Four things "share a Supabase project" can mean

What you actually wantWhat to hand overWhere it lives
A teammate who administers the databaseAn organization invite with a roleDashboard → organization team settings
A developer who needs to run the app locallyThe migrations and .env.exampleYour git repository
An app or a service that needs to read dataThe project URL and the anon keyEnvironment variables
A second project that should carry the same schemaThe migrations again, replayedYour git repository
Someone to take the project over permanentlyA project transferDashboard → project transfer

Only the first row is a dashboard invite. The second and fourth are the same answer, which is the most useful thing in this table: for a collaborator, the schema is the artifact, not the seat.

Inviting a person: the four roles

Supabase documents four roles, and the difference between them is mostly about settings, not data:

RoleWhat it can doCaveat
OwnerEverything, including organization settings, adding other owners, and transferring projects outAt least one owner must always exist
AdministratorEverything except updating organization settings, transferring projects out, and adding ownersThe default for a trusted engineer
DeveloperRead-only on organization resources; content access to project resources, but cannot change project settingsThe right answer for most invites
Read-OnlyRead-only on bothTeam and Enterprise plans only

To send one, open the organization's team settings and invite by email. Three details that cost people time:

  • The invite expires after 24 hours. A link sent on a Friday is dead by Monday.
  • Organization-scoped roles cover current and future projects. Add a Developer today and they have the same access to the project you create next year.
  • Project-scoped roles are a paid feature. On Team and Enterprise, a member assigned to specific projects cannot even see the others in the dashboard. On the free plan there is no partial visibility — this is the single biggest reason to put side projects in their own organization rather than piling them into one.

If you would rather check membership from a script than a browser, the Management API answers it directly:

export SUPABASE_ACCESS_TOKEN="your-personal-access-token"
export ORG_ID="your-organization-id"

curl "https://api.supabase.com/v1/organizations/$ORG_ID/members" \
  -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN"

Leaving an organization has one precondition worth knowing before you need it: every organization must keep at least one owner, so a sole owner has to promote someone else, or invite a new owner who accepts, before they can leave.

Sharing the schema instead of a seat

Here is what a new developer on this project actually receives, and it is a directory in git:

supabase/migrations/
  0001_commerce_init.sql              78 lines — tables, RLS, read-own-only policies
  0002_display_name_allowlist.sql      8
  0003_commerce_rpcs.sql              37 — atomic SECURITY DEFINER functions
  0004_lock_down_rate_limit_fn.sql     4
  0005_retain_anonymized_download_audit.sql   7
  0006_pricing_v2_slot_kinds.sql      15
  0007_atomic_download_rate_limit.sql 67
  0008_refund_requests.sql            55

271 lines, eight files, six row-level-security policies. That is the entire database — schema, constraints, policies and functions — in a form that is reviewable in a pull request, replayable against a brand-new project, and readable by someone who has never logged into your dashboard. The first file opens with the decision it encodes:

-- RLS: read-own only; NO write policies for anon/authenticated — all writes go
-- through server code using the service-role key (which bypasses RLS).

A dashboard invite conveys none of that. A migration file conveys it permanently, to everyone who clones the repository, including the person who inherits it after you leave.

The corollary is a rule rather than a tip: make every schema change as a migration, never in the SQL editor. A change clicked into the dashboard exists in exactly one place and is invisible to code review, to the next environment, and to the collaborator you just invited. Changes made in the editor are also the most common cause of the "it works on my project" class of bug, because the only record of them is the live database.

Sharing connection details

The third row of the table is the one with a trap in it. A Supabase project has two keys, and they are not interchangeable:

# .env.example — committed. .env* is gitignored except this file.
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-or-publishable-key
# SECRET — server-only, bypasses RLS.
SUPABASE_SERVICE_ROLE_KEY=your-service-role-secret

The project URL and the anon key are designed to be public — they ship to the browser on every page load, and row-level security is what makes that safe. Sharing them is not a disclosure; it is the normal mode of operation.

The service-role key is the opposite. It bypasses RLS entirely, which makes it equivalent to handing over the whole database. It belongs in a server environment variable and nowhere else — not in a Slack message, not in a screenshot of the dashboard, and not in a NEXT_PUBLIC_-prefixed variable, which would compile it into the client bundle.

This codebase enforces that mechanically rather than by convention. src/lib/supabase/admin.ts:

import "server-only";
import { createClient as createSbClient } from "@supabase/supabase-js";

/**
 * Service-role Supabase client — SERVER ONLY. Bypasses RLS.
 * Never import from a Client Component; the `server-only` guard
 * makes such an import fail the build.
 */
export function createAdminClient() {
  return createSbClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    { auth: { persistSession: false, autoRefreshToken: false } },
  );
}

That single import "server-only" line is the difference between a rule in a document and a rule in the build. Any import of this module from a Client Component fails next build with an explicit error. It is the cheapest safety measure in the entire project, and it is the one to copy before you share credentials with anyone.

Two further clients sit beside it — client.ts (browser, anon key) and server.ts (server, anon key, RLS enforced, bound to request cookies). Three files, three trust levels, each one obvious from its name. When a collaborator asks "which client do I use", the answer is a filename.

What to do instead, by situation

SituationDo thisNot this
Contractor working on one featureSeparate organization + its own project, seeded from your migrationsAdding them to the organization holding production
Open-source contributorRepository with supabase/migrations/ + .env.example; they create their own free projectAny credential at all
Teammate who needs the dashboardOrganization invite, Developer roleOwner "because it is easier"
A second environment (staging)Replay the same migrations against a new projectpg_dump from production into it
Handing the project over for goodProject transfer to their organizationSharing your account login
Agency that needs temporary accessInvite, then remove on the agreed date, then rotate the service-role keyLeaving the invite in place

That last cell is the step people skip. Removing a member revokes dashboard access; it does not invalidate a service-role key they already copied. If a departing collaborator ever had it, rotate it.

Mistakes and troubleshooting

SymptomCauseFix
"I cannot find a way to invite someone to one project"Invites are organization-levelInvite to the organization, or move the project to its own organization
Invite link does not workInvites expire after 24 hoursSend a fresh one
New member can see every projectThey were given an organization-scoped roleProject-scoped roles, on Team/Enterprise — otherwise separate organizations
Invite rejected for an SSO organizationInvites from a SAML SSO account can only be accepted by an account from the same identity providerInvite them through the identity provider
Cannot leave the organizationYou are the only ownerPromote another member to Owner, or invite one who accepts, then leave
Collaborator's local app returns empty tablesThey have the credentials but not the schemasupabase/migrations/ in the repository, replayed on their project
Queries work locally but 401 in productionService-role key used locally, anon key in production, with no RLS read policyAdd the read policy; do not promote the service-role key
Service-role key ended up in the browser bundleIt was given a NEXT_PUBLIC_ prefix, or imported into a Client ComponentDrop the prefix, add import "server-only", and rotate the key

Frequently asked questions

Can I share one Supabase project without sharing the others? Only with project-scoped roles, which are a Team and Enterprise feature. On the free plan, a member of the organization sees every project in it — so the practical answer is to give each project that needs separate access its own organization.

Is it safe to share the Supabase URL and anon key? Yes, by design — both are shipped to every browser that loads your app. What makes that safe is row-level security on your tables. If a table has RLS off, the anon key is a public read of that table, so audit your policies before you treat the key as harmless.

How do I give someone the database without giving them my account? Commit your migrations. A collaborator creates their own free project and replays them. They get the full schema, constraints and policies with none of your data and none of your credentials.

Does removing a member revoke their API keys? No. Removing a member removes dashboard access. Any API key they copied — especially the service-role key — keeps working until you rotate it.

Templates in this post

Every template below is a static Next.js build with no backend of its own, which is exactly the point: you add Supabase to it with the three-client pattern above, and the marketing pages do not change. All three have a feature that wants a database the moment you launch. ASoc Fade is a barbershop site with services, a price list, team profiles and booking. ASoc Fiscal is a financial-platform marketing site covering payments and invoicing. ASoc Flow is a workflow-automation SaaS landing page with a hub preview and a visual flow builder.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates.

Keep reading

Tutorial8 min read

Building an HTML/CSS Navigation Bar (and the Bug That Hides It)

Six ingredients build an accessible nav bar — and this site's own mobile drawer shipped the pointer-events-none bug that leaves closed links keyboard-reachable.

Read more
Tutorial9 min read

HTML Nav: The Element, Not the Layout

A real production nav, from a 4-item desktop bar to a slide-out drawer — the aria-expanded toggle, the z-index stacking, and what a div-only navbar loses.

Read more