Supabase.Postgrest 8.1.0

dotnet add package Supabase.Postgrest --version 8.1.0
                    
NuGet\Install-Package Supabase.Postgrest -Version 8.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Supabase.Postgrest" Version="8.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Supabase.Postgrest" Version="8.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Supabase.Postgrest" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Supabase.Postgrest --version 8.1.0
                    
#r "nuget: Supabase.Postgrest, 8.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Supabase.Postgrest@8.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Supabase.Postgrest&version=8.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Supabase.Postgrest&version=8.1.0
                    
Install as a Cake Tool

Supabase.Postgrest

Build and Test NuGet License: MIT

A C# client for PostgREST — query your Supabase database through the auto-generated REST API, with strongly-typed models and LINQ. It is a C#-ification of postgrest-js.

Part of the Supabase C# SDK. Most projects use it through the Supabase meta-package (supabase.From<T>()); reference this package directly to use PostgREST on its own. It also works outside the Supabase ecosystem against any PostgREST server.

Installation

dotnet add package Supabase.Postgrest

Targets .NET Standard 2.0.

Getting started

Every table maps to a model deriving from BaseModel. Use Table, PrimaryKey, and Column attributes to map C# names to their database counterparts:

using Supabase.Postgrest.Attributes;
using Supabase.Postgrest.Models;

[Table("messages")]
public class Message : BaseModel
{
    [PrimaryKey("id")]
    public int Id { get; set; }

    [Column("username")]
    public string UserName { get; set; }

    [Column("channel_id")]
    public int ChannelId { get; set; }
}

Create a client and work with a model through Table<T>():

using Supabase.Postgrest;

var client = new Client("http://localhost:3000");

// Read
var response = await client.Table<Message>().Get();
List<Message> messages = response.Models;

// Insert
await client.Table<Message>().Insert(new Message { UserName = "acupofjose", ChannelId = 1 });

// Update (via a fetched model)
var message = response.Models.First();
message.UserName = "elrhomariyounes";
await message.Update<Message>();

// Delete
await response.Models.Last().Delete<Message>();

Against the Supabase hosted service, pass your keys as headers when constructing the client:

var options = new ClientOptions();
var client = new Client("https://PROJECT_ID.supabase.co/rest/v1", options)
{
    GetHeaders = () => new Dictionary<string, string>
    {
        { "apikey", SUPABASE_PUBLIC_KEY },
        { "Authorization", $"Bearer {SUPABASE_USER_TOKEN}" }
    }
};

LINQ

Filters, ordering, and projections are expressed as LINQ over your model:

await client.Table<Movie>()
            .Select(x => new object[] { x.Id, x.Name, x.Tags, x.ReleaseDate })
            .Where(x => x.Tags.Contains("Action") || x.Tags.Contains("Adventure"))
            .Order(x => x.ReleaseDate, Ordering.Descending)
            .Get();

await client.Table<Movie>()
            .Set(x => x.WatchedAt, DateTime.Now)
            .Where(x => x.Id == "11111-22222-33333-44444")
            .Update();

Full generated API reference: Supabase.Postgrest.

Foreign keys, join tables, and relationships

PostgREST introspects relationships between tables and can return related rows inline. Foreign key constraints are required for PostgREST to detect these relationships. Mark a related property with the Reference attribute to include it:

  • One-to-one — detected from a unique constraint on a foreign key.
  • One-to-many — the inverse of a foreign key reference.
  • Many-to-many — detected via a join table whose composite key contains foreign keys to both tables.
[Table("movie")]
public class Movie : BaseModel
{
    [PrimaryKey("id")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }

    [Reference(typeof(Person))]
    public List<Person> Persons { get; set; }

    [Column("created_at")]
    public DateTime CreatedAt { get; set; }
}

[Table("person")]
public class Person : BaseModel
{
    [PrimaryKey("id")]
    public int Id { get; set; }

    [Column("first_name")]
    public string FirstName { get; set; }

    [Column("last_name")]
    public string LastName { get; set; }

    [Reference(typeof(Profile))]
    public Profile Profile { get; set; }

    [Column("created_at")]
    public DateTime CreatedAt { get; set; }
}

[Table("profile")]
public class Profile : BaseModel
{
    [PrimaryKey("person_id")]
    public int PersonId { get; set; }

    [Column("email")]
    public string Email { get; set; }
}

Each related model must inherit BaseModel and specify its Table and Column attributes. By default a Reference is included in all GET queries on the table (this can be disabled in its constructor). Querying Movie given the above returns each movie with its person array, and each person with their nested profile.

Circular references

Circular relations are allowed but are only parsed one level deep. Given circular models, a Person returned under a Movie will itself carry the root Movie, and a Person -> Profile carries its root Person. If that is undesirable, define dedicated join models without the circular references.

Top-level filtering

By default a relation acts as a top-level (inner-join) filter: a Movie with no related Person would not be returned. Set useInnerJoin: false to keep returning the parent even when the relation is empty:

[Table("movie")]
public class Movie : BaseModel
{
    [PrimaryKey("id")]
    public string Id { get; set; }

    [Column("name")]
    public string? Name { get; set; }

    [Reference(typeof(Person), useInnerJoin: false)]
    public List<Person> People { get; set; } = new();
}

PostgREST does not support nested inserts or upserts — a request writes to exactly one table. So Reference properties are ignored on insert, update, and upsert, for every relationship type. Inserting a Movie with its Persons list populated persists the movie and writes nothing else, without raising an error.

To create a relationship, write the foreign key where it lives in the database:

One-to-one / many-to-one — the foreign key is a column on the row being inserted. Expose it on the model (as Profile does with person_id) and set it directly:

// `profile.person_id` references `person.id` — setting the column creates the relationship.
await client.Table<Profile>().Insert(new Profile { PersonId = person.Id, Email = "tom.cruise@supabase.io" });

One-to-many — the foreign key is a column on each child row. Insert the parent first (by default the response contains the inserted record, including database-generated values such as its primary key), then bulk-insert the children with their foreign key column set to the parent's key:

[Table("review")]
public class Review : BaseModel
{
    [PrimaryKey("id")]
    public int Id { get; set; }

    [Column("movie_id")]
    public int MovieId { get; set; }

    [Column("content")]
    public string Content { get; set; }
}
// 1. Insert the parent and retrieve its database-generated key.
var response = await client.Table<Movie>().Insert(new Movie { Name = "Top Gun: Maverick" });
var movie = response.Model!;

// 2. Bulk-insert the children with their foreign key column set.
var reviews = contents
    .Select(content => new Review { MovieId = movie.Id, Content = content })
    .ToList();
await client.Table<Review>().Insert(reviews);

Many-to-many — the foreign keys live in a join table that has no counterpart in your domain model, so it must be modeled and written explicitly:

[Table("movie_person")]
public class MoviePerson : BaseModel
{
    [Column("movie_id")]
    public int MovieId { get; set; }

    [Column("person_id")]
    public int PersonId { get; set; }
}
// 1. Insert the root record and retrieve its database-generated key.
var response = await client.Table<Movie>().Insert(new Movie { Name = "Top Gun: Maverick" });
var movie = response.Model!;

// 2. Insert all join rows in a single bulk request.
var moviePersons = persons
    .Select(person => new MoviePerson { MovieId = movie.Id, PersonId = person.Id })
    .ToList();
await client.Table<MoviePerson>().Insert(moviePersons);

Writes across multiple requests are not atomic — if a later request fails, the earlier records exist without their relationships. When atomicity matters, wrap the writes in a database function and call it through Rpc:

await client.Rpc("insert_movie_with_persons", new Dictionary<string, object>
{
    { "name", "Top Gun: Maverick" },
    { "person_ids", persons.Select(p => p.Id).ToList() }
});

Observability (OpenTelemetry)

The client emits traces and metrics through System.Diagnostics, so you can wire them into OpenTelemetry (or any ActivityListener / MeterListener) without taking a dependency on the OpenTelemetry packages. Emission is zero-cost while nothing is listening, so it is always on and stays silent until you subscribe.

Register the client's ActivitySource and Meter by name. Use the PostgrestDiagnostics.SourceName constant rather than hardcoding the string, so a typo becomes a compile error instead of a silent no-op:

using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using Supabase.Postgrest;

// Requires OpenTelemetry.Extensions.Hosting and an exporter package (e.g. OTLP) in your app.
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(PostgrestDiagnostics.SourceName)
        .AddOtlpExporter())
    .WithMetrics(metrics => metrics
        .AddMeter(PostgrestDiagnostics.SourceName)
        .AddOtlpExporter());

Once subscribed you get:

  • A client span per request, named {METHOD} {path} and following OpenTelemetry HTTP conventions (method, status code, and a sanitized URL). The query string is never recorded — in PostgREST it carries the column filters and their values, which are potential PII. A db.operation tag (select, insert, update, upsert, delete, count, rpc) distinguishes the logical operation, since several map to the same HTTP verb.
  • A supabase.postgrest.http.request.duration histogram (seconds), tagged with method, host, path, operation, and status code.

If you are not using the OpenTelemetry SDK, a raw listener works too:

using System.Diagnostics;
using Supabase.Postgrest;

using var listener = new ActivityListener
{
    ShouldListenTo = source => source.Name == PostgrestDiagnostics.SourceName,
    Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllData,
    ActivityStopped = activity => Console.WriteLine($"{activity.OperationName} {activity.Duration.TotalMilliseconds}ms {activity.Status}")
};
ActivitySource.AddActivityListener(listener);

Contributing

Contributions are welcome. See the repository root for how to build and test the SDK.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (5)

Showing the top 5 NuGet packages that depend on Supabase.Postgrest:

Package Downloads
Supabase.Realtime

Realtime-csharp is written as a client library for supabase/realtime.

Supabase

A C# implementation of the Supabase client

AIC.Core.Data.Supabase

Shared .NET library for Aerospace, Intelligence, and Cyber solutions.

NWERP.Infrastructure

Package Description

benxu.AppPlatform.Supabase.PostgreSQL

Supabase PostgreSQL implementation for benxu App Platform. Provides typed PostgREST query, CRUD, and RPC access with optional IAuthService token integration for .NET MAUI applications.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
8.1.0 152 9/7/2026
8.0.0 363 9/3/2026
4.4.0 19,283 7/22/2026
4.3.0 15,030 7/20/2026
4.2.0 3,045 7/15/2026
4.1.0 45,863 2/28/2025
4.0.3 708,393 5/23/2024
4.0.2 14,635 5/16/2024
4.0.1 4,024 5/7/2024
4.0.0 38,155 4/21/2024