Enhanced Type Inference for Embedded Functions (Computed Relationships)

Oct 22, 2025

With @supabase/supabase-js@2.75.1 and a CLI version > 2.53.1, the type-generation and runtime support for embedded functions / computed relationships has been improved. The introspection now includes SetofOptions metadata via the supabase/postgres-meta#971, and the client library types in supabase-js now provide stronger type inference and compile-time errors for invalid function usage.

🔍 What’s new#

  • Functions returning SETOF or table types are now correctly inferred as embedded relationships (arrays) when used in .select() queries (computed relationships). Supported via SetofOptions from introspection. note: If you define a function in Postgres with RETURNS SETOF … ROWS 1, the tool-chain (introspection + type generation) will infer this as a single object instead of an array.

  • TypeScript errors at call-site when you attempt to call a function with wrong or conflicting parameters. For example:


    _10
    const res = await supabase.rpc('postgrest_unresolvable_function', { a: 'test' });
    _10
    // Error: Could not choose the best candidate function between: public.postgrest_unresolvable_function(a => integer), public.postgrest_unresolvable_function(a => text).

    This kind of error helps you catch ambiguous overloads that the introspection cannot decide between.

  • Additional error cases like:


    _10
    const res = await supabase.rpc('blurb_message', {});
    _10
    // Error: Searched for the function public.blurb_message with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.

    This corresponds to underlying PostgREST error codes PGRST202 / PGRST203.

Important caveats:

Nullable embed relationships by default: When you embed a function (computed relationship) in a .select() query, the generated type will treat the function result as possibly null, since it may not return any rows.

You can override the nullability globally using a DeepMerge override when generating types using the CLI. Example:


_17
import { MergeDeep } from 'type-fest'
_17
import { Database as DatabaseGenerated } from './database-generated.types'
_17
_17
export type Database = MergeDeep<
_17
DatabaseGenerated,
_17
{
_17
public: {
_17
Functions: {
_17
my_function: {
_17
SetofOptions: {
_17
isNotNullable: true
_17
}
_17
}
_17
}
_17
}
_17
}
_17
>

This approach mimics defining custom JSON types via MergeDeep.

However, when a function has multiple overloads or multiple SetofOptions variants (for example one overload returns from someTablesomeOtherTable, another returns someTable2someOtherTable), a global override may become ambiguous:


_10
function_name:
_10
| { ..., SetofOptions: { from: 'someTable', to: 'someOtherTable' } }
_10
| { ..., SetofOptions: { from: 'someTable2', to: 'someOtherTable' } }

In such cases, the recommendation is to use the !inner hint in the query itself so the inference knows the result won’t be null:


_10
client
_10
.from('table')
_10
.select('field1, computed_relationship!inner(id)')
_10
// Inferred type:
_10
// { field1: string, computed_relationship: { id: string } }
_10
// rather than default:
_10
// { field1: string, computed_relationship: { id: string } | null }

✅ What you should do#

  1. Upgrade to @supabase/supabase-js@2.75.1 (or higher)
  2. Ensure you are using the CLI at version > 2.53.1 so that SetofOptions metadata is correctly included in the generated types.
  3. Regenerate your types (e.g., npx supabase gen types typescript …) after introspection so that functions with SETOF return types are captured with the proper metadata.

🔗 References#

Build in a weekend, scale to millions