openref: 0.1

info:
  id: reference/csharp
  title: Supabase C# Client
  description: |

    Supabase C#.

  definition: spec/enrichments/tsdoc_v2/combined.json
  slugPrefix: '/'
  specUrl: https://github.com/supabase/supabase/edit/master/apps/docs/spec/supabase_csharp_v0.yml
  libraries:
    - name: 'C#'
      id: 'csharp'
      version: '0.8.0'

functions:
  - id: initializing
    title: Initializing
    description: |
      Initializing a new client is pretty straightforward. Find your project url and public key from the
      admin panel and pass it into your client initialization function.

      `Supabase` is heavily dependent on Models deriving from `BaseModel`. To interact with the API, one must have the associated model (see example) specified.

      Leverage `Table`, `PrimaryKey`, and `Column` attributes to specify names of classes/properties that are different from their C# Versions.

    examples:
      - id: csharp-init-standard
        name: Standard
        code: |
          ```c#
          var url = Environment.GetEnvironmentVariable("SUPABASE_URL");
          var key = Environment.GetEnvironmentVariable("SUPABASE_KEY");

          var options = new Supabase.SupabaseOptions
          {
              AutoConnectRealtime = true
          };

          var supabase = new Supabase.Client(url, key, options);
          await supabase.InitializeAsync();
          ```
      - id: csharp-init-maui
        name: Dependency Injection (Maui-like)
        code: |
          ```c#
          public static MauiApp CreateMauiApp()
          {
                // ...
                var builder = MauiApp.CreateBuilder();

                var url = Environment.GetEnvironmentVariable("SUPABASE_URL");
                var key = Environment.GetEnvironmentVariable("SUPABASE_KEY");
                var options = new SupabaseOptions
                {
                  AutoRefreshToken = true,
                  AutoConnectRealtime = true,
                  // SessionHandler = new SupabaseSessionHandler() <-- This must be implemented by the developer
                };

                // Note the creation as a singleton.
                builder.Services.AddSingleton(provider => new Supabase.Client(url, key, options));
          }
          ```
      - id: csharp-init-showing-models
        name: With Models Example
        code: |
          ```c#
          // Given the following Model representing the Supabase Database (Message.cs)
          [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; }

              public override bool Equals(object obj)
              {
                  return obj is Message message &&
                          Id == message.Id;
              }

              public override int GetHashCode()
              {
                  return HashCode.Combine(Id);
              }
          }

          void Initialize()
          {
              // Get All Messages
              var response = await client.Table<Message>().Get();
              List<Message> models = response.Models;

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

              // Update
              var model = response.Models.First();
              model.UserName = "elrhomariyounes";
              await model.Update();

              // Delete
              await response.Models.Last().Delete();

              // etc.
          }
          ```
  - id: sign-up
    title: 'SignUp()'
    description: |
      Creates a new user.
    notes: |
      - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://supabase.com/dashboard/project/_/auth/providers).
      - **Confirm email** determines if users need to confirm their email address after signing up.
        - If **Confirm email** is enabled, a `user` is returned but `session` is null.
        - If **Confirm email** is disabled, both a `user` and a `session` are returned.
      - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/guides/auth/redirect-urls) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://supabase.com/dashboard/project/_/auth/url-configuration).
      - If SignUp() is called for an existing confirmed user:
        - When both **Confirm email** and **Confirm phone** (even when phone provider is disabled) are enabled in [your project](/dashboard/project/_/auth/providers), an obfuscated/fake user object is returned.
        - When either **Confirm email** or **Confirm phone** (even when phone provider is disabled) is disabled, the error message, `User already registered` is returned.
    examples:
      - id: sign-up
        name: Sign up.
        isSpotlight: true
        code: |
          ```c#
          var session = await supabase.Auth.SignUp(email, password);
          ```
  - id: sign-in-with-password
    title: 'SignIn(email, password)'
    description: |
      Log in an existing user using email or phone number with password.
    notes: |
      - Requires either an email and password or a phone number and password.
    examples:
      - id: sign-in-with-email-and-password
        name: Sign in with email and password
        isSpotlight: true
        code: |
          ```c#
          var session = await supabase.Auth.SignIn(email, password);
          ```
      - id: sign-in-with-phone-and-password
        name: Sign in with phone and password
        code: |
          ```c#
          var session = await supabase.Auth.SignIn(SignInType.Phone, phoneNumber, password);
          ```
  - id: sign-in-with-otp
    title: 'SendMagicLink() and SignIn(SignInType, Phone)'
    notes: |
      - Requires either an email or phone number.
      - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number.
      - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP.
      - If you're using phone, you can configure whether you want the user to receive a OTP.
      - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/guides/auth/redirect-urls). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://supabase.com/dashboard/project/_/auth/settings).
    examples:
      - id: sign-in-with-email
        name: Send Magic Link.
        isSpotlight: true
        description: |
          The user will be sent an email which contains either a magiclink or a OTP or both. By default, a given user can only request a OTP once every 60 seconds.
          You can pass `emailRedirectTo` with dynamic link to bring the users back to your app after they click on the magic link.
        code: |
          ```c#
          var options = new SignInOptions { RedirectTo = "http://myredirect.example" };
          var didSendMagicLink = await supabase.Auth.SendMagicLink("joseph@supabase.io", options);
          ```
      - id: sign-in-with-sms-otp
        name: Sign in with SMS OTP.
        description: The user will be sent a SMS which contains a OTP. By default, a given user can only request a OTP once every 60 seconds.
        code: |
          ```c#
          await supabase.Auth.SignIn(SignInType.Phone, "+13334445555");

          // Paired with `VerifyOTP` to get a session
          var session = await supabase.Auth.VerifyOTP("+13334445555", TOKEN, MobileOtpType.SMS);
          ```
  - id: sign-in-with-oauth
    title: 'SignIn(Provider)'
    description: |
      Signs the user in using third party OAuth providers.
    notes: |
      - This method is used for signing in using a third-party provider.
      - Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers).
    examples:
      - id: sign-in-using-a-third-party-provider
        name: Sign in using a third-party provider
        isSpotlight: true
        code: |
          ```c#
          var signInUrl = supabase.Auth.SignIn(Provider.Github);
          ```
      - id: sign-in-with-scopes
        name: With scopes
        description: |
          If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token.
          You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider.
        code: |
          ```c#
          var signInUrl = supabase.Auth.SignIn(Provider.Github, 'repo gist notifications');

          // after user comes back from signin flow
          var session = supabase.Auth.GetSessionFromUrl(REDIRECTED_URI);
          ```
  - id: sign-out
    title: 'SignOut()'
    description: |
      Signs out the current user, if there is a logged in user.
    notes: |
      - In order to use the `SignOut()` method, the user needs to be signed in first.
    examples:
      - id: sign-out
        name: Sign out
        isSpotlight: true
        code: |
          ```c#
          await supabase.Auth.SignOut();
          ```
  - id: verify-otp
    title: 'VerifyOtp()'
    notes: |
      - The `VerifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite` or `email_change`.
      - The verification type used should be determined based on the corresponding auth method called before `VerifyOtp` to sign up / sign-in a user.
    examples:
      - id: verify-sms-one-time-password(otp)
        name: Verify Sms One-Time Password (OTP)
        isSpotlight: true
        code: |
          ```c#
          var session = await supabase.Auth.VerifyOTP("+13334445555", TOKEN, MobileOtpType.SMS);
          ```
  - id: get-session
    title: 'CurrentSession'
    description: |
      Returns the session data, if there is an active session.
    examples:
      - id: get-the-session-data
        name: Get the session data
        isSpotlight: true
        code: |
          ```c#
          var session = supabase.Auth.CurrentSession;
          ```
  - id: get-user
    title: 'CurrentUser'
    description: |
      Returns the user data, if there is a logged in user.
    examples:
      - name: Get the logged in user
        isSpotlight: true
        code: |
          ```c#
          var user = supabase.Auth.CurrentUser;
          ```
  - id: update-user
    title: 'UpdateUser()'
    description: |
      Updates user data, if there is a logged in user.
    notes: |
      - In order to use the `UpdateUser()` method, the user needs to be signed in first.
      - By Default, email updates sends a confirmation link to both the user's current and new email.
      To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://supabase.com/dashboard/project/_/auth/settings).
    examples:
      - id: update-the-email-for-an-authenticated-user
        name: Update the email for an authenticated user
        description: Sends a "Confirm Email Change" email to the new email address.
        isSpotlight: true
        code: |
          ```c#
          var attrs = new UserAttributes { Email = "new-email@example.com" };
          var response = await supabase.Auth.Update(attrs);
          ```
      - id: update-the-password-for-an-authenticated-user
        name: Update the password for an authenticated user
        isSpotlight: false
        code: |
          ```c#
          var attrs = new UserAttributes { Password = "***********" };
          var response = await supabase.Auth.Update(attrs);
          ```
      - id: update-the-users-metadata
        name: Update the user's metadata
        isSpotlight: true
        code: |
          ```c#
          var attrs = new UserAttributes
          {
            Data = new Dictionary<string, string> { {"example", "data" } }
          };
          var response = await supabase.Auth.Update(attrs);
          ```
  - id: on-auth-state-change
    title: 'StateChanged'
    description: |
      Receive a notification every time an auth event happens.
    notes: |
      - Types of auth events: `AuthState.SignedIn`, `AuthState.SignedOut`, `AuthState.UserUpdated`, `AuthState.PasswordRecovery`, `AuthState.TokenRefreshed`
    examples:
      - id: listen-to-auth-changes
        name: Listen to auth changes
        isSpotlight: true
        code: |
          ```c#
          supabase.Auth.AddStateChangedListener((sender, changed) =>
          {
              switch (changed)
              {
                  case AuthState.SignedIn:
                      break;
                  case AuthState.SignedOut:
                      break;
                  case AuthState.UserUpdated:
                      break;
                  case AuthState.PasswordRecovery:
                      break;
                  case AuthState.TokenRefreshed:
                      break;
              }
          });
          ```
  - id: auth-reset-password-for-email
    title: 'ResetPasswordForEmail()'
    description: |
      Sends a reset request to an email address.
    notes: |
      Sends a password reset request to an email address. When the user clicks the reset link in the email they are redirected back to your application. Prompt the user for a new password and call Auth.UpdateUser():

    examples:
      - id: reset-password
        name: Reset password for Flutter
        isSpotlight: true
        code: |
          ```c#
          await supabase.Auth.ResetPasswordForEmail("joseph@supabase.io");
          ```
  - id: invoke
    title: 'invoke()'
    description: |
      Invokes a Supabase Function. See the [guide](/docs/guides/functions) for details on writing Functions.
    notes: |
      - Requires an Authorization header.
      - Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec.
    examples:
      - id: basic-invocation
        name: Basic invocation.
        isSpotlight: true
        code: |
          ```c#
          var options = new InvokeFunctionOptions
          {
              Headers = new Dictionary<string, string> {{ "Authorization", "Bearer 1234" }},
              Body = new Dictionary<string, object> { { "foo", "bar" } }
          };

          await supabase.Functions.Invoke("hello", options: options);
          ```
      - id: modeled-invocation
        name: Modeled invocation
        code: |
          ``` c#
          class HelloResponse
          {
              [JsonProperty("name")]
              public string Name { get; set; }
          }

          await supabase.Functions.Invoke<HelloResponse>("hello");
          ```
  - id: select
    description: |
      Performs vertical filtering with SELECT.
    title: 'Fetch data: Select()'
    notes: |
      - **LINQ expressions do not currently support parsing embedded resource columns. For these cases, `string` will need to be used.**
      - **When using string Column Names to select, they must match names in database, not names specified on model properties.**
      - Additional information on modeling + querying Joins and Inner Joins can be found [in the `postgrest-csharp README`](https://github.com/supabase-community/postgrest-csharp/blob/master/README.md#foreign-keys-join-tables-and-relationships)
      - By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data.
      - `From()` can be combined with [Modifiers](/docs/reference/csharp/using-modifiers)
      - `From()` can be combined with [Filters](/docs/reference/csharp/using-filters)
      - If using the Supabase hosted platform `apikey` is technically a reserved keyword, since the API gateway will pluck it out for authentication. [It should be avoided as a column name](https://github.com/supabase/supabase/issues/5465).
    examples:
      - id: getting-your-data
        name: Getting your data
        isSpotlight: true
        code: |
          ```c#
          // Given the following Model (City.cs)
          [Table("cities")]
          class City : BaseModel
          {
              [PrimaryKey("id")]
              public int Id { get; set; }

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

              [Column("country_id")]
              public int CountryId { get; set; }

              //... etc.
          }

          // A result can be fetched like so.
          var result = await supabase.From<City>().Get();
          var cities = result.Models
          ```
      - id: selecting-specific-columns
        name: Selecting specific columns
        description: You can select specific fields from your tables.
        code: |
          ```c#
          // Given the following Model (Movie.cs)
          [Table("movies")]
          class Movie : BaseModel
          {
              [PrimaryKey("id")]
              public int Id { get; set; }

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

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

              //... etc.
          }

          // A result can be fetched like so.
          var result = await supabase
            .From<Movie>()
            .Select(x => new object[] {x.Name, x.CreatedAt})
            .Get();
          ```
      - id: query-foreign-tables
        name: Query foreign tables
        description: If your database has relationships, you can query related tables too.
        code: |
          ```c#
          var data = await supabase
            .From<Transactions>()
            .Select("id, supplier:supplier_id(name), purchaser:purchaser_id(name)")
            .Get();
          ```
      - id: filtering-with-inner-joins
        name: Filtering with inner joins
        description: |
          If you want to filter a table based on a child table's values you can use the `!inner()` function. For example, if you wanted
          to select all rows in a `message` table which belong to a user with the `username` "Jane":
        code: |
          ```c#
          var result = await supabase
            .From<Movie>()
            .Select("*, users!inner(*)")
            .Filter("user.username", Operator.Equals, "Jane")
            .Get();
          ```
      - id: querying-with-count-option
        name: Querying with count option
        description: |
          You can get the number of rows by using the count option.
          Allowed values for count option are [exact](https://postgrest.org/en/stable/api.html#exact-count), [planned](https://postgrest.org/en/stable/api.html#planned-count) and [estimated](https://postgrest.org/en/stable/api.html#estimated-count).
        code: |
          ```c#
          var count = await supabase
            .From<Movie>()
            .Select(x => new object[] { x.Name })
            .Count(CountType.Exact);
          ```
      - id: querying-json-data
        name: Querying JSON data
        description: |
          If you have data inside of a JSONB column, you can apply select
          and query filters to the data values. Postgres offers a
          [number of operators](https://www.postgresql.org/docs/current/functions-json.html)
          for querying JSON data. Also see
          [PostgREST docs](http://postgrest.org/en/v7.0.0/api.html#json-columns) for more details.
        code: |
          ```c#
           var result = await supabase
            .From<Users>()
            .Select("id, name, address->street")
            .Filter("address->postcode", Operator.Equals, 90210)
            .Get();
          ```

  - id: insert
    description: |
      Performs an INSERT into the table.
    title: 'Create data: Insert()'
    examples:
      - id: create-a-record
        name: Create a record
        isSpotlight: true
        code: |
          ```c#
          [Table("cities")]
          class City : BaseModel
          {
              [PrimaryKey("id", false)]
              public int Id { get; set; }

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

              [Column("country_id")]
              public int CountryId { get; set; }
          }

          var model = new City
          {
            Name = "The Shire",
            CountryId = 554
          };

          await supabase.From<City>().Insert(model);
          ```
      - id: bulk-create
        name: Bulk create
        code: |
          ```c#
          [Table("cities")]
          class City : BaseModel
          {
              [PrimaryKey("id", false)]
              public int Id { get; set; }

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

              [Column("country_id")]
              public int CountryId { get; set; }
          }

          var models = new List<City>
          {
            new City { Name = "The Shire", CountryId = 554 },
            new City { Name = "Rohan", CountryId = 553 },
          };

          await supabase.From<City>().Insert(models);
          ```
      - id: fetch-inserted-data
        name: Fetch inserted record
        code: |
          ```c#
          var result = await supabase
            .From<City>()
            .Insert(models, new QueryOptions { Returning = ReturnType.Representation });
          ```

  - id: update
    description: |
      Performs an UPDATE on the table.
    title: 'Modify data: Update()'
    notes: |
      - `Update()` is typically called using a model as an argument or from a hydrated model.
    examples:
      - id: updating-your-data-with-filter
        name: Update your data using Filter
        isSpotlight: true
        code: |
          ```c#
          var update = await supabase
            .From<City>()
            .Where(x => x.Name == "Auckland")
            .Set(x => x.Name, "Middle Earth")
            .Update();
          ```
      - id: updating-your-data
        name: Update your data
        code: |
          ```c#
          var model = await supabase
            .From<City>()
            .Where(x => x.Name == "Auckland")
            .Single();

          model.Name = "Middle Earth";

          await model.Update<City>();
          ```

  - id: upsert
    description: |
      Performs an UPSERT into the table.
    title: 'Upsert data: Upsert()'
    notes: |
      - Primary keys should be included in the data payload in order for an update to work correctly.
      - Primary keys must be natural, not surrogate. There are however, [workarounds](https://github.com/PostgREST/postgrest/issues/1118) for surrogate primary keys.
    examples:
      - id: upsert-your-data
        name: Upsert your data
        isSpotlight: true
        code: |
          ```c#
          var model = new City
          {
            Id = 554,
            Name = "Middle Earth"
          };

          await supabase.From<City>().Upsert(model);
          ```
      - id: upserting-into-tables-with-constraints
        name: Upserting into tables with constraints
        description: |
          Running the following will cause supabase to upsert data into the `users` table.
          If the username 'supabot' already exists, the `onConflict` argument tells supabase to overwrite that row
          based on the column passed into `onConflict`.
        isSpotlight: true
        code: |
          ```c#
          var model = new City
          {
            Id = 554,
            Name = "Middle Earth"
          };

          await supabase
            .From<City>()
            .OnConflict(x => x.Name)
            .Upsert(model);
          ```
      - id: return-the-exact-number-of-rows
        name: Return the exact number of rows
        description: |
          Allowed values for count option are `exact`, `planned` and `estimated`.
        code: |
          ```c#
          var model = new City
          {
            Id = 554,
            Name = "Middle Earth"
          };

          await supabase
            .From<City>()
            .Upsert(model, new QueryOptions { Count = QueryOptions.CountType.Exact });
          ```

  - id: delete
    description: |
      Performs a DELETE on the table.
    title: 'Delete data: Delete()'
    notes: |
      - `Delete()` should always be combined with [Filters](/docs/reference/csharp/using-filters) to target the item(s) you wish to delete.
    examples:
      - id: delete-records
        name: Delete records
        isSpotlight: true
        code: |
          ```c#
          await supabase
            .From<City>()
            .Where(x => x.Id == 342)
            .Delete();
          ```

  - id: rpc
    title: 'Stored Procedures: Rpc()'
    description: |
      You can call stored procedures as a "Remote Procedure Call".

      That's a fancy way of saying that you can put some logic into your database then call it from anywhere.
      It's especially useful when the logic rarely changes - like password resets and updates.
    examples:
      - id: call-a-stored-procedure
        name: Call a stored procedure
        isSpotlight: true
        description: This is an example invoking a stored procedure.
        code: |
          ```c#
          await supabase.Rpc("hello_world", null);
          ```
      - id: with-parameters
        name: With Parameters
        code: |
          ```c#
          await supabase.Rpc("hello_world", new Dictionary<string, object> { { "foo", "bar"} });
          ```

  - id: subscribe
    title: 'Realtime.Channel'
    description: |
      Subscribe to realtime changes in your database.
    notes: |
      - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/api#managing-realtime).
      - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;`
    examples:
      - id: listen-to-broadcast
        name: Listen to broadcast messages
        isSpotlight: true
        code: |
          ```c#
          class CursorBroadcast : BaseBroadcast
          {
              [JsonProperty("cursorX")]
              public int CursorX {get; set;}

              [JsonProperty("cursorY")]
              public int CursorY {get; set;}
          }

          var channel = supabase.Realtime.Channel("any");
          var broadcast = channel.Register<CursorBroadcast>();
          broadcast.AddBroadcastEventHandler((sender, baseBroadcast) =>
          {
              var response = broadcast.Current();
          });

          await channel.Subscribe();

          // Send a broadcast
          await broadcast.Send("cursor", new CursorBroadcast { CursorX = 123, CursorY = 456 });
          ```
      - id: listen-to-presence-sync
        name: Listen to presence sync
        isSpotlight: true
        code: |
          ```c#
            class UserPresence : BasePresence
            {
                [JsonProperty("cursorX")]
                public bool IsTyping {get; set;}

                [JsonProperty("onlineAt")]
                public DateTime OnlineAt {get; set;}
            }

            var channel = supabase.Realtime.Channel("any");
            var presenceKey = Guid.NewGuid().ToString();
            var presence = channel.Register<UserPresence>(presenceKey);
            presence.AddPresenceEventHandler(EventType.Sync, (sender, type) =>
            {
                Debug.WriteLine($"The Event Type: {type}");
                var state = presence.CurrentState;
            });

            await channel.Subscribe();

            // Send a presence update
            await presence.Track(new UserPresence { IsTyping = false, OnlineAt = DateTime.Now });
          ```
      - id: listening-to-a-specific-table
        name: Listening to a specific table
        isSpotlight: true
        code: |
          ```c#
          await supabase.From<City>().On(ListenType.All, (sender, change) =>
          {
              Debug.WriteLine(change.Payload.Data);
          });
          ```
      - id: listen-to-all-database-changes
        name: Listen to all database changes
        code: |
          ```c#
          var channel = supabase.Realtime.Channel("realtime", "public", "*");

          channel.AddPostgresChangeHandler(ListenType.All, (sender, change) =>
          {
              // The event type
              Debug.WriteLine(change.Event);
              // The changed record
              Debug.WriteLine(change.Payload);
          });

          await channel.Subscribe();
          ```
      - id: listening-to-inserts
        name: Listening to inserts
        code: |
          ```c#
          await supabase.From<City>().On(ListenType.Inserts, (sender, change) =>
          {
              Debug.WriteLine(change.Payload.Data);
          });
          ```
      - id: listening-to-updates
        name: Listening to updates
        description: |
          By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
          enable full replication for the table you are listening too:

          ```sql
          alter table "your_table" replica identity full;
          ```
        code: |
          ```c#
          await supabase.From<City>().On(ListenType.Updates, (sender, change) =>
          {
              Debug.WriteLine(change.Payload.Data);
          });
          ```
      - id: listening-to-deletes
        name: Listening to deletes
        description: |
          By default, Supabase does not send deleted records. If you want to receive the deleted record you can
          enable full replication for the table you are listening too:

          ```sql
          alter table "your_table" replica identity full;
          ```
        code: |
          ```c#
          await supabase.From<City>().On(ListenType.Deletes, (sender, change) =>
          {
              Debug.WriteLine(change.Payload.Data);
          });
          ```
      - id: listening-to-row-level-changes
        name: Listening to row level changes
        description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
        code: |
          ```c#
          var channel = supabase.Realtime.Channel("realtime", "public", "countries", "id", "id=eq.200");

          channel.AddPostgresChangeHandler(ListenType.All, (sender, change) =>
          {
              // The event type
              Debug.WriteLine(change.Event);
              // The changed record
              Debug.WriteLine(change.Payload);
          });

          await channel.Subscribe();
          ```

  - id: remove-channel
    description: |
      Unsubscribes and removes Realtime channel from Realtime client.
    title: 'Unsubscribe()'
    notes: |
      - Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed.
    examples:
      - id: removes-a-channel
        name: Remove a channel
        isSpotlight: true
        code: |
          ```c#
          var channel = await supabase.From<City>().On(ChannelEventType.All, (sender, change) => { });
          channel.Unsubscribe();

          // OR

          var channel = supabase.Realtime.Channel("realtime", "public", "*");
          channel.Unsubscribe()
          ```

  - id: get-channels
    description: |
      Returns all Realtime channels.
    title: 'Subscriptions'
    examples:
      - id: get-all-channels
        name: Get all channels
        isSpotlight: true
        code: |
          ```c#
          var channels = supabase.Realtime.Subscriptions;
          ```

  - id: list-buckets
    description: |
      Retrieves the details of all Storage buckets within an existing product.
    title: 'ListBuckets()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: `select`
        - `objects` permissions: none
    examples:
      - id: list-buckets
        name: List buckets
        isSpotlight: true
        code: |
          ```c#
          var buckets = await supabase.Storage.ListBuckets();
          ```

  - id: get-bucket
    description: |
      Retrieves the details of an existing Storage bucket.
    title: 'GetBucket()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: `select`
        - `objects` permissions: none
    examples:
      - id: get-bucket
        name: Get bucket
        isSpotlight: true
        code: |
          ```c#
          var bucket = await supabase.Storage.GetBucket("avatars");
          ```

  - id: create-bucket
    description: |
      Creates a new Storage bucket
    title: 'CreateBucket()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: `insert`
        - `objects` permissions: none
    examples:
      - id: create-bucket
        name: Create bucket
        isSpotlight: true
        code: |
          ```c#
          var bucket = await supabase.Storage.CreateBucket("avatars");
          ```

  - id: empty-bucket
    description: |
      Removes all objects inside a single bucket.
    title: 'EmptyBucket()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: `select`
        - `objects` permissions: `select` and `delete`
    examples:
      - id: empty-bucket
        name: Empty bucket
        isSpotlight: true
        code: |
          ```c#
          var bucket = await supabase.Storage.EmptyBucket("avatars");
          ```
  - id: update-bucket
    description: |
      Updates a new Storage bucket
    title: 'UpdateBucket()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: `update`
        - `objects` permissions: none
    examples:
      - id: update-bucket
        name: Update bucket
        isSpotlight: true
        code: |
          ```c#
          var bucket = await supabase.Storage.UpdateBucket("avatars", new BucketUpsertOptions { Public = false });
          ```

  - id: delete-bucket
    description: |
      Deletes an existing bucket. A bucket can't be deleted with existing objects inside it. You must first `empty()` the bucket.
    title: 'DeleteBucket()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: `select` and `delete`
        - `objects` permissions: none
    examples:
      - id: delete-bucket
        name: Delete bucket
        isSpotlight: true
        code: |
          ```dart
          var result = await supabase.Storage.DeleteBucket("avatars");
          ```

  - id: from-upload
    description: |
      Uploads a file to an existing bucket.
    title: 'From().Upload()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `insert`
    examples:
      - id: upload-file
        name: Upload file
        isSpotlight: true
        code: |
          ```c#
          var imagePath = Path.Combine("Assets", "fancy-avatar.png");

          await supabase.Storage
            .From("avatars")
            .Upload(imagePath, "fancy-avatar.png", new FileOptions { CacheControl = "3600", Upsert = false });
          ```
      - id: upload-file-with-progress
        name: Upload file with Progress
        code: |
          ```c#
          var imagePath = Path.Combine("Assets", "fancy-avatar.png");

          await supabase.Storage
            .From("avatars")
            .Upload(imagePath, "fancy-avatar.png", onProgress: (sender, progress) => Debug.WriteLine($"{progress}%"));
          ```

  - id: from-update
    description: |
      Replaces an existing file at the specified path with a new one.
    title: 'From().update()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `update` and `select`
    examples:
      - id: update-file
        name: Update file
        isSpotlight: true
        code: |
          ```c#
          var imagePath = Path.Combine("Assets", "fancy-avatar.png");
          await supabase.Storage.From("avatars").Update(imagePath, "fancy-avatar.png");
          ```

  - id: from-move
    description: |
      Moves an existing file, optionally renaming it at the same time.
    title: 'From().Move()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `update` and `select`
    examples:
      - id: move-file
        name: Move file
        isSpotlight: true
        code: |
          ```c#
          await supabase.Storage.From("avatars")
            .Move("public/fancy-avatar.png", "private/fancy-avatar.png");
          ```

  - id: from-create-signed-url
    description: |
      Create signed url to download file without requiring permissions. This URL can be valid for a set number of seconds.
    title: 'From().CreateSignedUrl()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `select`
    examples:
      - id: create-signed-url
        name: Create Signed URL
        isSpotlight: true
        code: |
          ```c#
          var url = await supabase.Storage.From("avatars").CreateSignedUrl("public/fancy-avatar.png", 60);
          ```

  - id: from-get-public-url
    description: |
      Retrieve URLs for assets in public buckets
    title: 'from.getPublicUrl()'
    notes: |
      - The bucket needs to be set to public, either via [UpdateBucket()](/docs/reference/csharp/storage-updatebucket) or by going to Storage on [supabase.com/dashboard](https://supabase.com/dashboard), clicking the overflow menu on a bucket and choosing "Make public"
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: none
    examples:
      - id: returns-the-url-for-an-asset-in-a-public-bucket
        name: Returns the URL for an asset in a public bucket
        isSpotlight: true
        code: |
          ```c#
          var publicUrl = supabase.Storage.From("avatars").GetPublicUrl("public/fancy-avatar.png");
          ```

  - id: from-download
    description: |
      Downloads a file.
    title: 'From().Download()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `select`
    examples:
      - id: download-file
        name: Download file
        isSpotlight: true
        code: |
          ```c#
          var bytes = await supabase.Storage.From("avatars").Download("public/fancy-avatar.png");
          ```
      - id: download-file-with-progress
        name: Download file with Progress
        code: |
          ```c#
          var bytes = await supabase.Storage
            .From("avatars")
            .Download("public/fancy-avatar.png", (sender, progress) => Debug.WriteLine($"{progress}%"));
          ```

  - id: from-remove
    description: |
      Deletes files within the same bucket
    title: 'From().Remove()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `delete` and `select`
    examples:
      - id: delete-file
        name: Delete file
        isSpotlight: true
        code: |
          ```c#
          await supabase.Storage.From("avatars").Remove(new List<string> { "public/fancy-avatar.png" });
          ```

  - id: from-list
    description: |
      Lists all the files within a bucket.
    title: 'From().list()'
    notes: |
      - Policy permissions required:
        - `buckets` permissions: none
        - `objects` permissions: `select`
    examples:
      - id: list-files-in-a-bucket
        name: List files in a bucket
        isSpotlight: true
        code: |
          ```c#
          var objects = await supabase.Storage.From("avatars").List();
          ```
  - id: using-modifiers
    title: Using Modifiers
    description: |
      Filters work on the row level—they allow you to return rows that
      only match certain conditions without changing the shape of the rows.
      Modifiers are everything that don't fit that definition—allowing you to
      change the format of the response (e.g., setting a limit or offset).

  - id: limit
    title: Limit()
    description: |
      Limits the result with the specified count.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Limit(10)
            .Get();
          ```
      - id: with-embedded-resources
        name: With embedded resources
        code: |
          ```c#
          var result = await supabase.From<Country>()
            .Select("name, cities(name)")
            .Filter("name", Operator.Equals, "United States")
            .Limit(10, "cities")
            .Get();
          ```

  - id: order
    title: Order()
    description: |
      Orders the result with the specified column.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Order(x => x.Id, Ordering.Descending)
            .Get();
          ```
      - id: with-embedded-resources
        name: With embedded resources
        code: |
          ```c#
           var result = await supabase.From<Country>()
            .Select("name, cities(name)")
            .Filter(x => x.Name == "United States")
            .Order("cities", "name", Ordering.Descending)
            .Get();
          ```

  - id: range
    title: Range()
    description: |
      Limits the result to rows within the specified range, inclusive.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select("name, country_id")
            .Range(0, 3)
            .Get();
          ```

  - id: offset
    title: Offset()
    description: |
      Specifies the offset of the queried rows to be returned (useful in pagination)
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Offset(25)
            .Get();
          ```

  - id: single
    title: Single()
    description: |
      Retrieves only one row from the result. Result must be one row (e.g. using limit), otherwise this will result in an error.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Single();
          ```

  - id: using-filters
    title: Using Filters
    description: |
      Filters allow you to only return rows that match certain conditions.

      Filters can be used on `Select()`, `Update()`, and `Delete()` queries.

      **Note: LINQ expressions do not currently support parsing embedded resource columns. For these cases, `string` will need to be used.**
    examples:
      - id: applying-filters
        name: Applying Filters
        code: |
          ```c#
          var result = await supabase.From<City>()
                .Select(x => new object[] { x.Name, x.CountryId })
                .Where(x => x.Name == "The Shire")
                .Single();
          ```
      - id: filter-by-value-within-json-column
        name: Filter by values within a JSON column
        data:
          sql: |
            ```sql
            create table
              users (
                id int8 primary key,
                name text,
                address jsonb
              );

            insert into
              users (id, name, address)
            values
              (1, 'Michael', '{ "postcode": 90210 }'),
              (2, 'Jane', null);
            ```
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Filter("address->postcode", Operator.Equals, 90210)
            .Get();
          ```
      - id: filter-foreign-tables
        name: Filter Foreign Tables
        code: |
          ```c#
          var results = await supabase.From<Country>()
            .Select("name, cities!inner(name)")
            .Filter("cities.name", Operator.Equals, "Bali")
            .Get();
          ```

  - id: or
    title: Or()
    description: |
      Finds all rows satisfying at least one of the filters.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<Country>()
            .Where(x => x.Id == 20 || x.Id == 30)
            .Get();
          ```
      - id: use-or-with-and
        name: Use `or` with `and`
        code: |
          ```c#
          var result = await supabase.From<Country>()
            .Where(x => x.Population > 300000 || x.BirthRate < 0.6)
            .Where(x => x.Name != "Mordor")
            .Get();
          ```

  - id: not
    title: Not()
    description: |
      Finds all rows which doesn't satisfy the filter.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<Country>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Where(x => x.Name != "Paris")
            .Get();
          ```

  - id: match
    title: Match()
    description: |
      - Finds a model given a class (useful when hydrating models and correlating with database)
      - Finds all rows whose columns match the specified `Dictionary<string, string>` object.
    examples:
      - id: with-model
        name: With Model
        isSpotlight: true
        code: |
          ```c#
          var city = new City
          {
              Id = 224,
              Name = "Atlanta"
          };

          var model = supabase.From<City>().Match(city).Single();
          ```
      - id: with-dictionary
        name: With Dictionary
        code: |
          ```c#
          var opts = new Dictionary<string, string>
          {
              {"name","Beijing"},
              {"country_id", "156"}
          };

          var model = supabase.From<City>().Match(opts).Single();
          ```

  - id: eq
    title: Operator.Equals
    description: |
      Finds all rows whose value on the stated `column` exactly matches the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Where(x => x.Name == "Bali")
            .Get();
          ```

  - id: neq
    title: Operator.NotEqual
    description: |
      Finds all rows whose value on the stated `column` doesn't match the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Where(x => x.Name != "Bali")
            .Get();
          ```

  - id: gt
    title: Operator.GreaterThan
    description: |
      Finds all rows whose value on the stated `column` is greater than the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Where(x => x.CountryId > 250)
            .Get();
          ```

  - id: gte
    title: Operator.GreaterThanOrEqual
    description: |
      Finds all rows whose value on the stated `column` is greater than or equal to the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select(x => new object[] { x.Name, x.CountryId })
            .Where(x => x.CountryId >= 250)
            .Get();
          ```

  - id: lt
    title: Operator.LessThan
    description: |
      Finds all rows whose value on the stated `column` is less than the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Select("name, country_id")
            .Where(x => x.CountryId < 250)
            .Get();
          ```

  - id: lte
    title: Operator.LessThanOrEqual
    description: |
      Finds all rows whose value on the stated `column` is less than or equal to the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Where(x => x.CountryId <= 250)
            .Get();
          ```

  - id: like
    title: Operator.Like
    description: |
      Finds all rows whose value in the stated `column` matches the supplied `pattern` (case sensitive).
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Filter(x => x.Name, Operator.Like, "%la%")
            .Get();
          ```

  - id: ilike
    title: Operator.ILike
    description: |
      Finds all rows whose value in the stated `column` matches the supplied `pattern` (case insensitive).
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          await supabase.From<City>()
            .Filter(x => x.Name, Operator.ILike, "%la%")
            .Get();
          ```

  - id: is
    title: Operator.Is
    description: |
      A check for exact equality (null, true, false), finds all rows whose value on the stated `column` exactly match the specified `value`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Where(x => x.Name == null
            .Get();
          ```

  - id: in
    title: Operator.In
    description: |
      Finds all rows whose value on the stated `column` is found on the specified `values`.
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Filter(x => x.Name, Operator.In, new List<object> { "Rio de Janiero", "San Francisco" })
            .Get();
          ```

  - id: contains
    title: Operator.Contains
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Filter(x => x.MainExports, Operator.Contains, new List<object> { "oil", "fish" })
            .Get();
          ```

  - id: contained-by
    title: Operator.ContainedIn
    examples:
      - id: with-select
        name: With `Select()`
        isSpotlight: true
        code: |
          ```c#
          var result = await supabase.From<City>()
            .Filter(x => x.MainExports, Operator.ContainedIn, new List<object> { "oil", "fish" })
            .Get();
          ```

  - id: text-search
    title: Operator.[FTS,PLFTS,PHFTS,WFTS] (Full Text Search)
    description: |
      Finds all rows whose tsvector value on the stated `column` matches to_tsquery(query).
    examples:
      - id: text-search
        name: Text search
        code: |
          ```c#
          var result = await supabase.From<Quote>()
            .Select(x => x.Catchphrase)
            .Filter(x => x.Catchphrase, Operator.FTS, new FullTextSearchConfig("'fat' & 'cat", "english"))
            .Get();
          ```
      - id: basic-normalization
        name: Basic normalization
        description: Uses PostgreSQL's `plainto_tsquery` function.
        code: |
          ```c#
          var result = await supabase.From<Quote>()
            .Select(x => x.Catchphrase)
            .Filter(x => x.Catchphrase, Operator.PLFTS, new FullTextSearchConfig("'fat' & 'cat", "english"))
            .Get();
          ```
      - id: full-normalization
        name: Full normalization
        description: Uses PostgreSQL's `phraseto_tsquery` function.
        code: |
          ```c#
          var result = await supabase.From<Quote>()
            .Select(x => x.Catchphrase)
            .Filter(x => x.Catchphrase, Operator.PHFTS, new FullTextSearchConfig("'fat' & 'cat", "english"))
            .Get();
          ```
      - id: web-search
        name: Websearch
        description: |
          Uses PostgreSQL's `websearch_to_tsquery` function.
          This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used
          with advanced operators.

          - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
          - `"quoted text"`: text inside quote marks will be converted to terms separated by `<->` operators, as if processed by phraseto_tsquery.
          - `OR`: the word “or” will be converted to the | operator.
          - `-`: a dash will be converted to the ! operator.

        code: |
          ```c#
          var result = await supabase.From<Quote>()
            .Select(x => x.Catchphrase)
            .Filter(x => x.Catchphrase, Operator.WFTS, new FullTextSearchConfig("'fat' & 'cat", "english"))
            .Get();
          ```
