supabase-swift: a typed client for every Supabase service on Apple platforms
A Swift SDK for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse Swift examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
At a glance
- What is it?
- The Supabase Swift SDK wraps Auth, PostgREST, Realtime, Storage and Edge Functions behind one client, generates @Table structs for your schema, and can return typed JSON from an edge function. Its release cadence is fast and its support policy is unusually explicit about what will not count as a breaking change.
- Who is it for?
- supabase-swift fits an Apple platform team that has already chosen Supabase and wants typed calls, generated database types and session handling without writing the plumbing. It does not fit a team that wants to stay independent of a hosted backend, and it does not fit a project that cannot accept a fast-moving 2.x SDK with an aggressive minimum toolchain.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One product, or five libraries you can take separately
The libraries table in the README is a short map of the SDK, and it is worth reading as an architecture rather than a feature list. The Supabase library is the full client and includes all the others. Auth is user authentication and session management. PostgREST queries your Postgres database via REST. Realtime subscribes to database changes over a WebSocket. Storage manages files and objects. Functions invokes Supabase Edge Functions.
The last line of the installation section is the part that matters for sizing the dependency. You can also add individual libraries, Auth, Realtime, Storage, PostgREST and Functions, instead of the full Supabase product.
That matters because a client that carries Auth, a REST layer, a WebSocket layer, a storage layer and a functions layer is a large dependency for an app that needs two of them. Taking PostgREST and Auth alone is a different proposition from taking everything, and the SDK supports both without any change to how you configure it.
The client itself is initialised with a URL and a key, and the key in the example is a publishable one:
let client = SupabaseClient(
supabaseURL: URL(string: "https://xyzcompany.supabase.co")!,
supabaseKey: "your-publishable-key"
)The options block shows how far that goes. The db layer takes a schema, the auth layer takes a storage implementation for the session and a flow type such as .pkce, and the global layer takes default headers and a custom HTTP transport built on a URLSession you provide.
Edge functions return typed JSON, or raw bytes if you ask
The Edge Functions interface is the part of the SDK with the most interesting shape, because it offers two different contracts for the same call.
struct Answer: Decodable { let text: String }
// JSON in, typed JSON out
let answer: Answer = try await client.functions.invoke("ask", body: .json(["prompt": "hi"]))
// Raw response: status, headers, bytes
let response = try await client.functions.invoke("report", options: .init(method: .get))
print(response.status, response.body.count)The typed form decodes into your own Decodable struct, so the function's response shape is checked by the compiler against a type you wrote. The raw form gives you the status, the headers and the body bytes, for the cases where the function returns something that is not a model, an error body, or a file.
That is a better API than the usual choice, where you get one string and decode it yourself, and it comes from the same observation that runs through the SDK: the value of a client is that the shapes are known at compile time, and where they cannot be, the escape hatch is explicit rather than the default.
The body is built with a .json convenience, so a function call reads as a dictionary literal in Swift, and the options type carries the HTTP method for the raw form.
Streaming an edge function leaves the framing to you
There is a streaming variant of the same call, and it comes with a caveat that the README states in a code comment rather than in prose.
let response = try await client.functions.stream("chat", body: .json(["prompt": "hi"]))
for try await chunk in response.body {
// chunk boundaries follow the network; frame server-sent events yourself
}The behaviour is the kind that saves an afternoon if you know it and costs an afternoon if you do not. The chunks you receive are the chunks the network delivered, which is not the same thing as the events the server sent. A server-sent events stream has framing of its own, and the SDK hands you the bytes in the order they arrived without re-chunking them to match the event boundaries.
So if your function emits server-sent events and you want to treat each event as a message, you have to do the framing. The comment says so in one line, and the loop shape makes it clear that the sequence you read is arbitrary with respect to your protocol.
That is a defensible design, because a transport that tried to guess your event framing would have to understand your protocol, and a general SDK cannot. It does mean that streaming code needs a buffer and a parser, and a team that has never had to write one will find this the sharpest edge in the SDK.
Type generation downloads the generator that matches your SDK
The database type story is the part with the most moving parts, and the README explains it in unusual detail.
The command supabase gen types --lang swift writes @Table structs for your schema. It runs the supabase-typegen executable, which ships with each supabase-swift release. The CLI reads your supabase-swift version from Package.resolved and downloads the matching build itself. You need to install it by hand only when the CLI cannot do that, for example without network access, and then it uses the supabase-typegen it finds on your PATH.
The manual installation is a download and an extract:
VERSION=2.55.1 # your supabase-swift version
ASSET=supabase-typegen-macos-universal.tar.gz # or supabase-typegen-linux-x86_64.tar.gz
mkdir -p ~/.local/bin
curl -fsSL "https://github.com/supabase/supabase-swift/releases/download/v$VERSION/$ASSET" | tar -xz -C ~/.local/bin
supabase-typegen --versionThe version in that snippet is the supabase-swift version your project uses, and the asset name differs by platform, with a macOS universal build and a Linux x86-64 build.
The type generation step itself then writes the file:
supabase gen types --lang swift --local > Sources/App/Database.swiftThe --local flag is what tells the CLI to use the binary you installed rather than downloading one, and the output is redirected into a source file, which means the generated types are a build artefact you can check in or regenerate in CI, and the whole thing runs against your own resolved SDK version rather than a floating download.
One macOS wrinkle is documented. A binary you download with a browser is quarantined and Gatekeeper blocks it, while curl does not set the quarantine flag, and to clear it from a browser download you run xattr -d com.apple.quarantine on the binary.
The support policy says what a breaking change will not be
The Support Policy section is short and it is unusual, so it is worth quoting rather than paraphrasing.
On Xcode, the project only supports versions that are currently eligible for submitting apps to the App Store, and once a specific version of Xcode is no longer supported, its removal from Supabase will not be treated as a breaking change and will occur in a minor release.
On Swift, the minimum supported Swift version corresponds to the minor version released with the oldest-supported Xcode, and when a Swift version reaches its end of support it will be dropped in a minor release, which will not be considered a breaking change.
On platforms, the maintainers support the four latest major versions of each platform, including the current version, and when a platform version is no longer supported it will be dropped in a minor release. The example given is that iOS 14 will no longer be supported after the release of iOS 18. For macOS, the named yearly releases are treated as major versions for this policy regardless of their version numbering.
The practical value is that you can plan an upgrade. Your toolchain floor moves on a schedule you can see, and it moves in a minor version of the SDK, so a dependency bump is enough to pick it up. The requirements section states the current floor plainly: iOS 16.0+, macOS 13.0+, tvOS 16+, watchOS 9+, visionOS 1+, with Xcode 26.0+ and Swift 6.2+.
What belongs in the app, and what must not
The .env.example file in the repository is three lines, and one of them deserves a careful look.
SUPABASE_URL=https://mysupabasereference.supabase.co
SUPABASE_ANON_KEY=my.supabase.anon.key
SUPABASE_SERVICE_ROLE_KEY=my.supabase.service.role.keyThe file says to get these from the API settings in the Supabase dashboard. The SDK's own initialisation takes a publishable key, and the anonymous key is the client-facing one, so those two belong in an app. The service role key is the one that bypasses row level security, and a service role key inside a shipped iOS or macOS binary is a key that every user of the app can read out of the app bundle.
This is not a criticism of the project. The file is an example for the backend side of a project, where a service role key is legitimately used, and the SDK's public API takes a publishable key as its credential. The point is that a team copying the example file wholesale into a mobile project can end up with an elevated key in the app, and nothing in the example will stop them.
So the guidance for an adopter is simple and worth stating once. The client is initialised with a publishable key, the service role key stays on a server, and the .env.example file is a template for whichever side of the wire you are building.
Beyond that, the repository is a well-organised SDK project. There is a Package.swift and a [email protected] for toolchain variants, an ARCHITECTURE.md, a CODE_STYLE.md, a DEVELOPMENT.md, a RELEASE.md, a V3_MIGRATION.md, an AGENTS.md and a CLAUDE.md, a .spi.yml for the Swift Package Index, release-please configuration, and an Examples directory with an OpenTelemetry demo, a Slack clone, a user management sample and a Supabase sample. The releases are regular: v2.55.2, v2.55.3 and v2.55.4 landed on 9 September, 29 September and 13 October 2026, with the last push on 2026-10-15, and the licence is MIT.
Editorial conclusion
supabase-swift fits an Apple platform team that has already chosen Supabase and wants typed calls, generated database types and session handling without writing the plumbing. It does not fit a team that wants to stay independent of a hosted backend, and it does not fit a project that cannot accept a fast-moving 2.x SDK with an aggressive minimum toolchain. Before you add it, check the Swift and Xcode floors of 6.2 and 26.0 against your build machines, and make sure only the publishable key ever reaches the app binary.
Frequently asked questions
What are the requirements for the Supabase Swift SDK?
iOS 16.0+, macOS 13.0+, tvOS 16+, watchOS 9+ and visionOS 1+, with Xcode 26.0+ and Swift 6.2+. The support policy says dropping a platform, Swift or Xcode version happens in a minor release and is not treated as a breaking change.
How do I add supabase-swift to a Swift project?
Add it as a Swift Package Manager dependency pointing at https://github.com/supabase/supabase-swift.git from version 2.0.0, and depend on the Supabase product, or add only the individual libraries you need such as Auth, Realtime, Storage, PostgREST and Functions.
How do I generate Swift types for my Supabase database schema?
Run supabase gen types --lang swift --local and redirect the output to a source file. The CLI normally downloads the supabase-typegen build matching your supabase-swift version from Package.resolved, and you only install it by hand when the CLI cannot download it, for example without network access.
Can I stream the response from a Supabase Edge Function?
Yes, with client.functions.stream and a for try await loop over the response body. The README notes that the chunk boundaries follow the network, so if your function emits server-sent events you have to frame them yourself.
Which key should a Swift app use to talk to Supabase?
The client is initialised with a publishable key, and the .env.example file in the repository lists a separate service role key for backend use, so the elevated key should stay on a server rather than in the app binary.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/supabase-supabase-swift)Was this useful?