Fix "More Than One Relationship Was Found" in Supabase
The embed is not broken — PostgREST just cannot guess which of two many-to-many paths you meant, and it tells you both candidates in the error body.
Supabase's query builder throws this when a select() embeds a table that can be reached through more than one foreign-key path, and it refuses to guess which path you meant.
TL;DR#
The error fires when two foreign-key relationships connect the same pair of tables — most often two many-to-many junction tables, or two foreign keys from one table pointing at the same target. Fix it by naming the relationship explicitly with the ! hint: alias:table!relationship(...). The exact hint string to use is printed in the error's details array, so there is no need to guess it from the schema.
Uncaught Error: More than one relationship was found for teams and usersPostgREST — the API layer supabase-js talks to underneath .from().select() — returns a structured error body alongside that message:
{
"hint": "By following the 'details' key, disambiguate the request by changing the url to /origin?select=relationship(*) or /origin?select=target!relationship(*)",
"message": "More than one relationship was found for teams and users",
"details": [
{
"origin": "public.teams",
"relationship": "public.team_memberships[team_memberships_team_id_fkey][team_memberships_user_id_fkey]",
"cardinality": "m2m",
"target": "public.users"
},
{
"origin": "public.teams",
"relationship": "public.team_boards[team_boards_team_id_fkey][team_boards_owner_id_fkey]",
"cardinality": "m2m",
"target": "public.users"
}
]
}This is from Stack Overflow question 69260172, filed against a multitenant schema with teams, users, and two separate junction tables (team_memberships, team_boards) connecting them.
Why it happens#
supabase-js does not resolve the join itself: it builds a PostgREST query string, and PostgREST resolves embedded resources from the foreign-key constraints declared in the database — not from anything the client sends beyond the table name. When organizations.select('*, teams(*, users(*))') asks PostgREST to embed users under teams, PostgREST looks for every foreign-key path between the two tables. If it finds exactly one, it embeds it silently. If it finds more than one, it cannot pick for you, so it returns the ambiguity as data instead of guessing wrong.
The schema in the question has two such paths, both many-to-many:
teams↔usersthroughteam_memberships(a team member)teams↔usersthroughteam_boards(a board owner)
Both are legitimate many-to-many relationships between the same two tables, so teams(users(...)) alone is genuinely ambiguous — there is no "default" one for PostgREST to fall back to.
Reproduced on postgres:16 and postgrest/postgrest:v12.2.3 (both official Docker images), two junction tables between teams and users produce the structured error below — but only once each junction table's two foreign keys form its primary key. The question's tables use a surrogate id uuid PK; with that shape (id serial primary key in the reproduction), current PostgREST does not detect either many-to-many path at all and answers PGRST200 ("Could not find a relationship") instead of this ambiguity. With composite keys primary key (team_id, user_id) and primary key (team_id, owner_id), the same request returns:
{"code":"PGRST201","details":[{"cardinality":"many-to-many","embedding":"teams with users","relationship":"team_boards using team_boards_team_id_fkey(team_id) and team_boards_owner_id_fkey(owner_id)"},{"cardinality":"many-to-many","embedding":"teams with users","relationship":"team_memberships using team_memberships_team_id_fkey(team_id) and team_memberships_user_id_fkey(user_id)"}],"hint":"Try changing 'users' to one of the following: 'users!team_boards', 'users!team_memberships'. Find the desired relationship in the 'details' key.","message":"Could not embed because more than one relationship was found for 'teams' and 'users'"}The message text has shifted since the 2021 question (now "Could not embed because more than one relationship was found for…" rather than "More than one relationship was found for teams and users"), and the body now carries an explicit error code, but the mechanism and the fix are the same: a details array listing every candidate relationship, and a hint naming the exact disambiguation strings. PostgREST's resource embedding documentation covers this under "many-to-many relationships": the join table must contain foreign keys to both other tables, and those keys must be part of its composite key for the many-to-many path to even be detected.
Fix#
1. Read the details array instead of guessing. Each entry names the relationship (the constrained junction table or foreign key) and the cardinality. The hint field spells out the exact strings PostgREST will accept.
2. Add the ! hint to the embedded table, aliased so the key stays readable. With the junction tables keyed on their foreign keys (as above), picking the team_memberships path to resolve teams → users looks like this:
const data = await supabaseClient
.from('organizations')
.select(`
*,
teams(
id,
org_id,
name,
members:users!team_memberships(
id,
full_name,
avatar_url
)
)
`)Only the embedded table changed — users became members:users!team_memberships. Nothing else in the query, the schema, or the data moved between the failing version and this one.
3. Use the hint form that matches the relationship's cardinality. For a many-to-many path, PostgREST compares the hint with the junction table's name only (users!team_memberships); a constraint name such as team_memberships_user_id_fkey is not matched against a many-to-many relationship. In PostgREST v12.2.3's Plan.hs, the constraint-name matcher accepts one-to-many, many-to-one and one-to-one relationships and returns false for anything else, while a separate junction matcher handles many-to-many. Column and constraint hints are for the direct-foreign-key case covered further down. Copy the string from hint rather than typing it from memory.
If your junction tables have a surrogate id primary key, as in the question, the hint alone will not help on current PostgREST: there is no many-to-many relationship to pick. PostgREST's v12 embedding reference states that the join table "must contain foreign keys to other two tables and they must be part of its composite key", and that "the join table is also detected if the composite key has additional columns". Either make the two foreign keys the primary key, or keep id and extend the primary key to include both foreign-key columns.
Verify the fix#
Re-run the same request with the hint added and confirm the response comes back as data, not an error body. In a local PostgREST instance against the reproduced schema, the disambiguated request returns the expected nested shape:
GET /teams?select=id,name,members:users!team_memberships(id,full_name)
→ [{"id":1,"name":"Core","members":[{"id":1,"full_name":"Ada"}]}]against the same ambiguous request without the hint returning the PGRST201 error body above. If your app has more than one place embedding the same ambiguous pair, grep the codebase for the target table name inside a select( string (users( in this case) — a second occurrence that still lacks the ! hint fails with the same PGRST201 the first time that code path runs.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
One relationship vs. two foreign keys on one table#
The question's case is many-to-many (two junction tables). The same error also fires for a simpler shape: one table with two foreign keys to the same target table, with no junction table involved — for example a shifts table with both scan_id_start and scan_id_end referencing scans. The fix is the same operator, aliased per relationship instead of per junction table; the Supabase joins guide walks through that single-table variant in detail, including the constraint-name form of the hint.
Next error: PGRST200 after a bad hint#
PGRST200 means PostgREST found no relationship at all for the embed, rather than too many. In the reproduction it appeared before the junction tables were given composite primary keys: with a surrogate id serial primary key, the teams/users embed came back PGRST200 ("Could not find a relationship") instead of PGRST201. The same code is the answer when a hint names a junction table, column or constraint that does not match any relationship PostgREST has detected — a misspelling, or a constraint name used on a many-to-many path. PostgREST's error reference describes PGRST200 as "caused by stale foreign key relationships, otherwise any of the embedding resources or the relationship itself may not exist in the database". Reloading the schema cache after a migration (Supabase does this automatically; a self-hosted PostgREST needs NOTIFY pgrst, 'reload schema' or a restart) is the usual next step once the schema itself is confirmed correct.
Readers who need the number of rows rather than the rows themselves can continue with how to get COUNT(*) in Supabase, which counts without fetching the data, or the Supabase RLS debug checklist if a disambiguated join returns rows in the SQL editor but comes back empty through the API — that is usually a Row Level Security policy on the embedded table, not a relationship problem. Anyone still untangling the underlying foreign-key constraints themselves may also hit Supabase's foreign-key-violation error (23503), which covers the insert-order and ON DELETE side of the same constraints that PostgREST reads to resolve embeds.
Disambiguation only has to be written once per ambiguous embed — once the !hint is in the select() string, adding more columns, filters, or nesting around it does not reintroduce the error, because the relationship is now explicit rather than inferred.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix "supabaseUrl is required" in Supabase JS
The error reads like a typo in a URL, but almost every case is a build-time environment variable that never made it into the bundle.
Fix Supabase RLS Infinite Recursion (Production 2026)
If your Supabase query returns `infinite recursion detected in policy for relation "X"`, your RLS policy is querying the same table it protects. Here's exactly why it loops, and three production-grade fixes that don't leak data.
How to get COUNT(*) in Supabase
If you are fetching whole result sets just to count them, you are paying for bandwidth and latency you do not need. Supabase already returns counts in query metadata.


