Skip to content
instancezinstancez

Supabase SDK Compatibility

Which Supabase client SDK features instancez supports.

instancez implements the Supabase wire protocol. Any official Supabase SDK works against it — with the gaps noted below.

Feature Status Notes
Database — supabase.from() ✅ Full select, insert, update, upsert, delete. All PostgREST filter operators (eq, neq, gt, gte, lt, lte, like, ilike, is, in, contains, containedBy, overlaps, …). Embeds (!inner, !left, FK hints, many-to-many through junction tables, 300 PGRST201 when ambiguous). order, limit, offset, Range-header pagination with PostgREST’s statuses: 206 for a partial counted page, 416 PGRST103 past the end (data and count are null). Prefer: return, count, resolution, missing, max-affected, tx. .single() on insert, upsert, update and delete returns 406 PGRST116 and rolls back unless exactly one row is affected. Row values serialized like PostgREST (to_json), see Value formats. CSV responses (Accept: text/csv). HEAD requests. The secret key can use .explain() (text or JSON, with analyze, verbose, settings, buffers, wal) on reads. The plan runs in a transaction that is always rolled back. Other callers get 406 PGRST107. .explain() on insert, upsert, update, delete and rpc returns 406 PGRST107 for every caller, the secret key included, and nothing runs.
Auth — supabase.auth.* ✅ Full Email + password, magic link / OTP, anonymous sign-in, session refresh, updateUser, resetPasswordForEmail, identity linking/unlinking (linkIdentity needs the frontend on the API’s origin, see Auth), PKCE.
OAuth — signInWithOAuth ⚠️ Google, GitHub and Apple only The provider field accepts google, github and apple. Other providers return a 400. Apple is configured in YAML only.
OAuth — signInWithIdToken ⚠️ Google and Apple only Reads id_token like GoTrue (token also works). Verifies the token against the provider’s keys, with nonce (a token that has one needs one in the request, and the reverse). Google compares it raw. Apple compares the claim to the hex SHA-256 of the request nonce. Apple takes several audiences in client_id (comma-separated) for iOS bundle IDs.
Auth Admin — supabase.auth.admin.* ✅ Full createUser, listUsers (paginated), getUserById, updateUserById, deleteUser, inviteUserByEmail, generateLink, signOut (user), deleteFactor.
MFA — supabase.auth.mfa.* ⚠️ TOTP only enroll, challenge, verify, challengeAndVerify, unenroll, listFactors and getAuthenticatorAssuranceLevel all work for TOTP, with Supabase’s aal/amr JWT claims. Phone/SMS factors are not supported.
Storage — supabase.storage.* ✅ Full RLS expressions can use storage.foldername(), storage.filename(), storage.extension() and the owner / owner_id columns. Upload (with metadata), download, move, copy, remove (batch and DELETE /object/<bucket>/<path>, 403 when RLS denies, 404 when missing), list, listV2, info, exists. Guest (publishable-key) calls run as anon under the bucket’s rls:, as in Supabase. Public URLs. Signed URLs (download, with the download or transform option, and upload). /render/image/{public,authenticated,sign} serve transform URLs. Bucket management (create, update, delete, empty). Image transforms: resize (cover, contain, fill), quality, format (jpeg, png); WebP and AVIF output are not supported. Transform limits match Supabase: 1-2500px, 25MB and 50MP sources.
Edge Functions — supabase.functions.invoke() ✅ Full Calls code functions at /functions/v1/<name>.
RPC — supabase.rpc() ✅ Full Calls SQL functions declared under rpc: in instancez.yaml. setof results take filters, embeds and aggregates like tables.
Type generation — supabase gen types ⚠️ TypeScript only, differences listed inz gen types writes a Database type and Constants for createClient<Database>() from the yaml, with no database needed. See TypeScript types.
Realtime — supabase.channel() ❌ Not supported yet instancez has no pub/sub listener. For event-driven patterns in the meantime, use a code function with Postgres LISTEN/NOTIFY or a webhook receiver.

instancez’s auth server matches Supabase’s GoTrue on the wire, with a few deliberate differences:

  • A banned user gets 403 user_banned on every path, including the password grant. GoTrue returns 400 there and 403 elsewhere.
  • A TOTP code is rejected if its 30-second step was already used on that factor, even within the normal replay window GoTrue allows.
  • Linking an OAuth identity to an account that has a password but isn’t verified is refused (422 email_exists). GoTrue strips the password and links instead; on an instance with verify_email: false, every password user is unverified, so GoTrue’s behavior would silently drop their password.
  • /otp with allow_signup: false returns an empty 200 for an unknown address, not GoTrue’s 422, so the response never reveals whether the account exists.
  • With allow_signup: false, /signup and first-time signInWithIdToken return 422 signup_disabled (the browser OAuth callback redirects with unexpected_failure), and anonymous sign-in off returns 422 anonymous_provider_disabled when signup is allowed, as in GoTrue. Earlier releases returned 403.
  • /otp and /recover return a silent empty 200 on a repeat within 60 seconds, so neither reveals whether the account exists. /resend instead returns 429 over_email_send_rate_limit on the same cooldown — matching GoTrue, but inconsistent with instancez’s own /otp and /recover.
  • An access token issued before a ban or a password change stays valid until it expires; it isn’t revoked early.
  • JWTs are only accepted if signed RS256 or HS256.
  • CSV cells differ from PostgREST’s CSV; see CSV output. rpc ignores Accept: text/csv.
  • Rows from Accept: text/csv, application/geo+json and RPC calls with count=exact list keys in alphabetical order, not select order.

When using the S3 provider, you can bypass the SDK entirely and upload files straight to S3 via a presigned URL — useful in serverless environments where routing bytes through the server is expensive:

// Get a presigned upload URL
const { id, upload_url } = await fetch('/storage/avatars/sign', {
method: 'POST',
headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ content_type: file.type, size: file.size, path: `${userId}/${file.name}` }), // path is optional
}).then(r => r.json())
// Upload directly to S3 — instancez is not in this path
await fetch(upload_url, { method: 'PUT', headers: { 'Content-Type': file.type }, body: file })

Uploads through supabase.storage.upload() are proxied and capped at about 4.5MB on Instancez Cloud. This direct flow is the way past that cap. See Storage for the full spec.

The integration test suite runs @supabase/supabase-js against a live instancez instance on every commit. If you find a gap, open an issue.