Home

Swift Client Library

@supabase-community/supabase-swift

This reference documents every object and method available in Supabase's Swift library, supabase-swift. You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.

We also provide a supabase package for non-Swift projects.

The Swift client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.

Huge thanks to official maintainer, Maail.

Initializing

You can initialize Supabase with the SupabaseClient by passing your Project URL and Project Key. You can find these under your Project SettingsAPI Settings The Supabase client is your entrypoint to the rest of the Supabase functionality and is the easiest way to interact with everything we offer within the Supabase ecosystem.


let client = SupabaseClient(supabaseURL: "https://xyzcompany.supabase.co'", supabaseKey: "public-anon-key")

Fetch data

  • 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.
  • select() can be combined with Modifiers
  • select() can be combined with 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.
  • The recommended solution for getting data is to use the value property which will return a decoded model. Create a struct codable model of the of your database to get this easily decoded.

let city: CityModel = try await client.database
            .from("cities")
            .execute().value

Insert data

  • By default, every time you run insert(), the client library will make a select to return the full record. This is convenient, but it can also cause problems if your policies are not configured to allow the select operation. If you are using Row Level Security and you are encountering problems, try setting the returning param to minimal.

let city = CityModel(name:"The Shire", country_id: 554)
try await client.database
      .from("cities")
      .insert(city)
      .execute()

Update data

  • update() should always be combined with Filters to target the item(s) you wish to update.

let toUpdate = CountryModel(name: "Australia")
try await client.database
                .from("countries")
                .update(toUpdate)
                .eq(column: "id", value: 1)
                .execute()

Upsert data

  • 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 for surrogate primary keys.
  • If you need to insert new data and update existing data at the same time, use Postgres triggers.

let toUpsert = MessageModel(id:3, message: "foo", username: "supabot")
try await client.database
                .from("messages")
                .upsert(toUpsert)
                .execute()

Delete data

  • delete() should always be combined with filters to target the item(s) you wish to delete.
  • If you use delete() with filters and you have RLS enabled, only rows visible through SELECT policies are deleted. Note that by default no rows are visible, so you need at least one SELECT/ALL policy that makes the rows visible.

try await client.database
      .from("cities")
      .delete()
      .eq(column: "id", value: 666)
      .execute()

Call a Postgres function

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.


try await client.database
      .rpc(fn: "hello_world")
      .execute()

Using filters

Filters allow you to only return rows that match certain conditions.

Filters can be used on select(), update(), and delete() queries.

If a database function returns a table response, you can also apply filters.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .eq(column: "name", value: "The Shire")
      .execute()

Column is equal to a value

Finds all rows whose value on the stated column exactly matches the specified value.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .eq(column: "name", value: "The Shire")
      .execute()

Column is not equal to a value

Finds all rows whose value on the stated column doesn't match the specified value.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .neq(column: "name", value: "The Shire")
      .execute()

Column is greater than a value

Finds all rows whose value on the stated column is greater than the specified value.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .gt(column: "country_id", value: 250)
      .execute()

Column is greater than or equal to a value

Finds all rows whose value on the stated column is greater than or equal to the specified value.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .gte(column: "country_id", value: 250)
      .execute()

Column is less than a value

Finds all rows whose value on the stated column is less than the specified value.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .lt(column: "country_id", value: 250)
      .execute()

Column is less than or equal to a value

Finds all rows whose value on the stated column is less than or equal to the specified value.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .lte(column: "country_id", value: 250)
      .execute()

Column matches a pattern

Finds all rows whose value in the stated column matches the supplied pattern (case sensitive).


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .like(column: "name", value: "%la%")
      .execute()

Column matches a case-insensitive pattern

Finds all rows whose value in the stated column matches the supplied pattern (case insensitive).


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .ilike(column: "name", value: "%la%")
      .execute()

Column is a value

A check for exact equality (null, true, false), finds all rows whose value on the stated column exactly match the specified value.

is_ and in_ filter methods are suffixed with _ to avoid collisions with reserved keywords.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .is(column: "name", value: "null")
      .execute()

Column is in an array

Finds all rows whose value on the stated column is found on the specified values.


try await client.database
      .from("cities")
      .select(columns: "name, country_id")
      .in(column: "name", value: ["Rio de Janeiro", "San Francisco"])
      .execute()

Column contains every element in a value


try await client.database
      .from("cities")
      .select(columns: "name, id, main_expor")
      .contains(column: "main_exports", value: "oil")
      .execute()

Greater than a range

Only relevant for range columns. Match only rows where every element in column is greater than any element in range.


  try await client.database
        .from("reservations")
        .select()
        .rangeGt(column: "during", value: "[2000-01-02 08:30, 2000-01-02 09:30]")
        .execute()

Greater than or equal to a range

Only relevant for range columns. Match only rows where every element in column is either contained in range or greater than any element in range.


  try await client.database
        .from("reservations")
        .select()
        .rangeGte(column: "during", value: "[2000-01-02 08:30, 2000-01-02 09:30]")
        .execute()

Less than a range

Only relevant for range columns. Match only rows where every element in column is less than any element in range.


  try await client.database
        .from("reservations")
        .select()
        .rangeLt(column: "during", value: "[2000-01-02 08:30, 2000-01-02 09:30]")
        .execute()

Less than or equal to a range

Only relevant for range columns. Match only rows where every element in column is either contained in range or less than any element in range.


try await client.database
      .from("reservations")
      .select()
      .rangeLte(column: "during", value: "[2000-01-02 08:30, 2000-01-02 09:30]")
      .execute()

Mutually exclusive to a range

Only relevant for range columns. Match only rows where column is mutually exclusive to range and there can be no element between the two ranges.


try await client.database
      .from("reservations")
      .select()
      .rangeAdjacent(column: "during", value: "[2000-01-02 08:30, 2000-01-02 09:30]")
      .execute()

With a common element

Only relevant for array and range columns. Match only rows where column and value have an element in common.


try await client.database
      .from("issues")
      .select(columns: "title")
      .overlaps(column: "tags", value: "['is:closed', 'severity:high']")
      .execute()

Match a string

Only relevant for text and tsvector columns. Match only rows where columnmatches the query string in query.

For more information, see Postgres full text search.

Match an associated value

Finds all rows whose columns match the specified query object.


  try await client.database
      .from("countries")
      .select(columns: "name")
      .match(query: ["id" : 2, "name": "Albania"])
      .execute()

Don't match the filter

Finds all rows that don't satisfy the filter.

  • .not() expects you to use the raw PostgREST syntax for the filter names and values.

    .not('name','eq','Paris')
    .not('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains.
    .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column.
    .not('id','in','(6,7)')  // Use Postgres list () and 'in' for in_ filter.
    .not('id','in','(${mylist.join(',')})')  // You can insert a Dart list array.
    

try await client.database
      .from("countries")
      .select()
      .not(column: "name", operator: .is, value: "")
      .execute()

Match at least one filter

Finds all rows satisfying at least one of the filters.

  • .or() expects you to use the raw PostgREST syntax for the filter names and values.

    .or('id.in.(6,7),arraycol.cs.{"a","b"}')  // Use Postgres list () and 'in' for in_ filter. Array {} and 'cs' for contains.
    .or('id.in.(${mylist.join(',')}),arraycol.cs.{${mylistArray.join(',')}}') // You can insert a Dart list for list or array column.
    .or('id.in.(${mylist.join(',')}),rangecol.cs.(${mylistRange.join(',')}]') // You can insert a Dart list for list or range column.
    

try await client.database
      .from("countries")
      .select(columns: "name")
      .or(filters: "id.eq.2,name.eq.Algeria")
      .execute()

Match the filter

filter() expects you to use the raw PostgREST syntax for the filter values.

.filter('id', 'in', '(5,6,7)')  // Use `()` for `in` filter
.filter('arraycol', 'cs', '{"a","b"}')  // Use `cs` for `contains()`, `{}` for array values

try await client.database
      .from("countries")
      .select()
      .filter(column: "name", operator: .in, value: "('Algeria','Japan')")
      .execute()

Using Modifiers

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. returning a CSV string).

Modifiers must be specified after filters. Some modifiers only apply for queries that return rows (e.g., select() or rpc() on a function that returns a table response).

Return data after inserting

Perform a SELECT on the query result.


  try await client.database
      .from("countries")
      .upsert(CountryModel(id: 1, name: "Algeria"))
      .select()
      .execute()

Order the results

Order the query result by column.


try await client.database
      .from("countries")
      .select(column: "id, name")
      .order(column: "id", ascending: false)
      .execute()

Limit the number of rows returned

Limit the query result by count.


try await client.database
      .from("countries")
      .select(column: "id, name")
      .limit(count: 1)
      .execute()

Limit the query to a range

Limit the query result by from and to inclusively.


try await client.database
      .from("countries")
      .select(column:
              """
              name,
              cities (
                name
              )
              """)
      .limit(count: 1, foreignTable: "cities")
      .execute()

Overview

  • The auth methods can be accessed via the Supabase GoTrue Auth client.

let supabase = SupabaseClient(supabaseURL: "https://xyzcompany.supabase.co'", supabaseKey: "public-anon-key")
let auth = supabase.auth

Create a new user

  • By default, the user needs to verify their email address before logging in. To turn this off, disable Confirm email in your project.
  • 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 by default. You can modify your SITE_URL or add additional redirect URLs in your project.
  • If signUp() is called for an existing confirmed user:
    • If Confirm email is enabled in your project, an obfuscated/fake user object is returned.
    • If Confirm email is disabled, the error message, User already registered is returned.
  • To fetch the currently logged-in user, refer to getUser().

try await supabase.auth.signUp( email: "example@email.com",
                      password: "example-password")

Sign in a user

  • Requires either an email and password or a phone number and password.

try await supabase.auth.signIn( email: "example@email.com",
                      password: "example-password" )

Sign in a user through OTP

  • 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 the user doesn't exist, signInWithOtp() will signup the user instead. To restrict this behavior, you can set shouldCreateUser in SignInWithPasswordlessCredentials.options to false.
  • 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.
  • See redirect URLs and wildcards to add additional redirect URLs to your project.
  • Magic links and OTPs share the same implementation. To send users a one-time code instead of a magic link, modify the magic link email template to include {{ .Token }} instead of {{ .ConfirmationURL }}.

try await supabase.auth.signInWithOTP(email: "example@email.com",
                            redirectTo: URL(string: "https://example.com/welcome")!)

Sign in a user through OAuth

  • This method is used for signing in using a third-party provider.
  • Supabase supports many different third-party providers.

let url = try await supabase.auth
        .getOAuthSignInURL(
            provider: .github
          )

Sign out a user

  • In order to use the signOut() method, the user needs to be signed in first.

let url = try await supabase.auth.signOut()

Verify and log in through OTP

  • 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.

try await supabase.auth.verifyOTP(phone: "+13334445555",
                        token: "123456",
                        type: .sms)

Retrieve a session

  • Returns the session, refreshing it if necessary. The session returned can be null if the session is not detected which can happen in the event a user is not signed-in or has logged out.

try await supabase.auth.session

Retrieve a new session

  • This method will refresh the session whether the current one is expired or not.

try await supabase.auth.refreshSession(refreshToken: "refreshToken")

Retrieve a user

  • This method gets the user object from the current session.
  • Fetches the user object from the database instead of local session.
  • Should be used only when you require the most current user data. For faster results, getSession().session.user is recommended.

try await supabase.auth.session.user

Update a user

  • 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.

try await supabase.auth.update(user: UserAttributes(email: "new@email.com"))

Set the session data

  • setSession() takes in a refresh token and uses it to get a new session.
  • The refresh token can only be used once to obtain a new session.
  • Refresh token rotation is enabled by default on all projects to guard against replay attacks.
  • You can configure the REFRESH_TOKEN_REUSE_INTERVAL which provides a short window in which the same refresh token can be used multiple times in the event of concurrency or offline issues.

  try await supabase.auth.setSession(accessToken: "access_token", refreshToken: "refresh_token")

Listen to auth events

  • Types of auth events: SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED, PASSWORD_RECOVERY
  • Currently, onAuthStateChange() does not work across tabs. For instance, in the case of a password reset flow, the original tab which requested for the password reset link will not receive the SIGNED_IN and PASSWORD_RECOVERY event when the user clicks on the link.

for await event in supabase.auth.authEventChange {
      let event = event // types of Auth Even
      let session = try? await client.session
}

Invoke a function

Invokes a Supabase Function. See the guide for details on writing Functions.

  • Requires an Authorization header.
  • Invoke params generally match the Fetch API spec.

try await supabase.functions.invoke(
   functionName: "hello",
   invokeOptions: .init(body: Foo(foo: "baa"))) {
     data, response in
   }

Subscribe to channel

Subscribe to realtime changes in your database.

  • Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by managing replication.
  • 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;

import RealTime

var client = RealtimeClient(endPoint: "https://yourcompany.supabase.co/realtime/v1", params: ["apikey": "public-anon-key"])
client.connect()

Unsubscribe from a channel

Unsubscribes and removes Realtime channel from Realtime client.

  • 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.

let channel = client.channel(.table("users", schema: "public"))
client.removeChannel(channel)

Create a bucket

  • RLS policy permissions required:
    • buckets table permissions: insert
    • objects table permissions: none
  • Refer to the Storage guide on how access control works

try await supabase.storage.createBucket(id: "avatars")

Retrieve a bucket

  • RLS policy permissions required:
    • buckets table permissions: select
    • objects table permissions: none
  • Refer to the Storage guide on how access control works

try await supabase.storage.getBucket(id: "avatars")

List all buckets

  • RLS policy permissions required:
    • buckets table permissions: select
    • objects table permissions: none
  • Refer to the Storage guide on how access control works

let bucke: [Bucket] = try await supabase.storage.listBuckets()

Delete a bucket

  • RLS policy permissions required:
    • buckets table permissions: select and delete
    • objects table permissions: none
  • Refer to the Storage guide on how access control works

try await supabase.storage.deleteBucket(id: "avatars")

Empty a bucket

  • RLS policy permissions required:
    • buckets table permissions: select
    • objects table permissions: select and delete
  • Refer to the Storage guide on how access control works

try await supabase.storage.emptyBucket(id: "avatars")

Upload a file

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: insert
  • Refer to the Storage guide on how access control works

import SupabaseStorage

let file = File(name: "avatar1",
                data: fileData,
                fileName: "avatar1.png",
                contentType: "png")

try await supabase.storage.from(id: "avatars").upload(
                path: "public/avatar1.png",
                file: file,
                fileOptions: FileOptions(cacheControl: "3600")
            )

Download a file

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: select
  • Refer to the Storage guide on how access control works

try await supabase.storage.
        .from(id: "avatars")
        .download(path: "public/avatar1.png")

List all files in a bucket

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: select
  • Refer to the Storage guide on how access control works

try await supabase.storage.
        .from(id: "avatars")
        .list()

Replace an existing file

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: update and select
  • Refer to the Storage guide on how access control works

import SupabaseStorage

let file = File(name: "avatar2",
                data: fileData,
                fileName: "avatar2.png",
                contentType: "png")

try await supabase.storage.from(id: "avatars").update(
                path: "public/avatar1.png",
                file: file,
                fileOptions: FileOptions(cacheControl: "3600")
            )

Move an existing file

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: update and select
  • Refer to the Storage guide on how access control works

try await supabase.storage.
        .from(id: "avatars")
        .move(fromPath: "public/avatar1.png",
              toPath: "public/avatar2.png")

Delete files in a bucket

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: delete and select
  • Refer to the Storage guide on how access control works

try await supabase.storage.
        .from(id: "avatars")
        .remove(paths: ["avatar1.png"])

Create a signed URL

  • RLS policy permissions required:
    • buckets table permissions: none
    • objects table permissions: select
  • Refer to the Storage guide on how access control works

try await supabase.storage.
        .from(id: "avatars")
        .createSignedURL(path: "avatar1.png", expiresIn: 60)