Skip to main content

Migrating from Supabase

CloudBase PG is intentionally aligned with Supabase on many key design decisions: the same auth / storage schema division, aligned JWT claims (sub / role / aud), the same anon / authenticated / service_role three-role model, and the same "GRANT + RLS" two-tier permission paradigm. The vast majority of Supabase projects can be migrated with minimal changes.

This document focuses on alignment and differences at the "code and SQL level" and does not cover cloud resource migration tools or billing.

Mental Model Comparison

ConceptSupabaseCloudBase PGNotes
Database Rolesanon / authenticated / service_roleanon / authenticated / service_role✅ Same names and semantics
Account Schemaauthauth✅ Same name
Cloud Storage Schemastoragestorage✅ Same name
JWT subuuid stringvarchar(64) string✅ Directly compatible
JWT roleanon / authenticated / service_roleanon / authenticated / service_role✅ Directly compatible
Client SDK@supabase/supabase-js@cloudbase/js-sdkAPI style is similar, main method names are aligned
Row-Level PermissionsRLS PolicyRLS Policy✅ Standard PostgreSQL feature, identical syntax
Auto-generated REST APIPostgRESTPostgREST✅ Same open-source project, query syntax is essentially identical
RPC Calls.rpc().rpc()✅ Directly compatible
File Storage PermissionsRLS over storage.objectsRLS over storage.objects✅ Same approach
anon Tokenanon API keyanon Publishable Key✅ Same concept
service Tokenservice_role API keyservice_role API Key✅ Same concept

Key Differences

DifferenceSupabaseCloudBase PGMigration Action
REST Endpoint Path/rest/v1/<table>/v1/rdb/rest/<table>Update base URL
Auth Endpoint Path/auth/v1/.../auth/v1/...✅ Consistent
Domain<project>.supabase.co<envId>.api.tcloudbasegateway.comReplace domain
Realtime (Live Subscriptions)✅ Built-in❌ Not supportedSwitch to polling / custom WebSocket
GraphQL APIpg_graphql❌ Not supportedUse RESTful only
Database Branching❌ Not supportedUse environment isolation (dev / staging / prod)
DDL ChannelDirect database connection / StudioConsole SQL Editor / Cloud API ExecutePGSqlUse ExecutePGSql to replace direct SQL client workflows

Three-Step Migration Process

Step 1: Understand the Built-in Helper Functions (No Action Required)

CloudBase PG environments come pre-installed with helper functions such as auth.uid(), auth.jwt(), auth.role(), and auth.email(), which are semantically identical to Supabase's built-in functions. Existing RLS Policy SQL can be reused without modification.

Below are the definitions of these pre-installed functions (for reference only — they are already built into the environment, no manual execution is needed):

-- Current user ID (corresponds to JWT sub)
CREATE OR REPLACE FUNCTION auth.uid() RETURNS text
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'sub'
$$;

-- Current role
CREATE OR REPLACE FUNCTION auth.role() RETURNS text
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'role'
$$;

-- Full JWT claims
CREATE OR REPLACE FUNCTION auth.jwt() RETURNS jsonb
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::jsonb
$$;

-- Current email (if available)
CREATE OR REPLACE FUNCTION auth.email() RETURNS text
LANGUAGE sql STABLE AS $$
SELECT current_setting('request.jwt.claims', true)::json->>'email'
$$;

Step 2: Migrate Table Structures and RLS Policies

Almost directly applicable:

  • CREATE TABLE / ALTER TABLE: Fully compatible
  • CREATE POLICY with auth.uid() / auth.role(), etc.: The environment already has these functions pre-installed, no modification needed
  • Foreign keys REFERENCES auth.users(id): In CloudBase, auth.users.id is of type varchar(64) (compatible with Supabase's uuid string) and can be referenced directly; you can also drop the foreign key to maintain decoupling

Points to note:

-- Common pattern in Supabase (directly applicable)
CREATE TABLE public.todos (
id bigserial PRIMARY KEY,
title text NOT NULL,
owner_id varchar(64) NOT NULL DEFAULT auth.uid(),
created_at timestamptz DEFAULT now()
);

ALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;

GRANT SELECT, INSERT, UPDATE, DELETE ON public.todos TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;

CREATE POLICY todos_owner_all ON public.todos
FOR ALL TO authenticated
USING (owner_id = auth.uid())
WITH CHECK (owner_id = auth.uid());

The SQL above works for both Supabase and CloudBase PG with zero changes.

Step 3: Replace Client Code

Initialization

- import { createClient } from '@supabase/supabase-js';
- const supabase = createClient(
- 'https://<project>.supabase.co',
- '<anon-key>'
- );

+ import cloudbase from '@cloudbase/js-sdk';
+ const app = cloudbase.init({
+ env: '<envId>',
+ accessKey: '<Publishable Key>', // Equivalent to Supabase's anon key
+ });
+ const auth = app.auth;
+ const db = app.rdb();

Querying

The vast majority of the chained API is identical:

// Common: Identical syntax for both Supabase and CloudBase
const { data, error } = await db
.from('todos')
.select('id, title, is_completed')
.eq('is_completed', false)
.order('created_at', { ascending: false })
.limit(20);

Insert / Update / Delete

// INSERT
await db.from('todos').insert({ title: '...' });

// UPDATE
await db.from('todos').update({ is_completed: true }).eq('id', 1);

// DELETE
await db.from('todos').delete().eq('id', 1);

// UPSERT
await db.from('todos').upsert({ id: 1, title: '...' });

The API is consistent with Supabase.

Authentication

OperationSupabaseCloudBase PG
Sign Upsupabase.auth.signUp({ email, password })Three-step: send SMS → verify → auth.signUp(...) (username/password or phone), see HTTP API - Account Sign Up
Email/Phone Password Sign Insupabase.auth.signInWithPassword({ email, password })auth.signInWithPassword({ username, password })
Anonymous Sign Insupabase.auth.signInAnonymously()auth.signInAnonymously()
Third-Party Sign Insupabase.auth.signInWithOAuth({ provider })auth.signInWithProvider({ provider_token }), see Auth.signInWithProvider
Sign Outsupabase.auth.signOut()auth.signOut()
Get Sessionsupabase.auth.getSession()auth.getSession()
Listen to Auth Statesupabase.auth.onAuthStateChange(cb)auth.onAuthStateChange(cb)

RPC

// Common: Identical syntax
const { data } = await db.rpc('place_order', { p_product_id: 1, p_quantity: 2 });

Cloud Storage

- const { data } = await supabase.storage
- .from('avatars').upload('public/file.png', file);

+ // Upload (using CloudBase SDK; file permissions in PG mode are controlled by RLS)
+ await app.uploadFile({
+ cloudPath: 'public/file.png',
+ filePath: file,
+ });

CloudBase will provide a Supabase Storage-compatible HTTP API in phase two, which will further reduce migration costs. See PG Mode Cloud Storage.

Data Migration

For data migration and SQL compatibility, refer to the standard PostgreSQL migration process:

  1. Export Supabase Data: Use pg_dump --data-only --no-owner --no-privileges to export business data
  2. Import to CloudBase: Execute statements one by one via the Cloud API ExecutePGSql (note: only one SQL statement per call, split by line as needed)
  3. Sync auth.users: Since the auth schema is managed by the platform, do not dump/restore directly. It is recommended to have users re-activate their accounts through CloudBase's authentication API during the migration period, or coordinate with platform operations for batch import solutions

For large tables, using PostgreSQL's COPY command with ExecutePGSql is typically much more efficient than INSERT.

Common Migration Questions

Realtime Subscriptions Cannot Be Migrated

PG mode does not currently support Realtime. Alternatives:

  • Short term: Client-side polling
  • Medium term: Start a WebSocket service in a Cloud Function and connect directly to the database to subscribe to LISTEN/NOTIFY
  • Long term: Wait for PG mode Realtime support

My RLS Policy Works in Supabase but Errors in CloudBase

Troubleshoot in this order:

  1. Check if the role has table-level GRANT (behavior is consistent between CloudBase and Supabase, but easy to overlook)
  2. Check if SEQUENCE permissions for serial primary keys are granted

See Common Error Reference.

Compatibility of db.rpc() with Supabase's .rpc()

Chained API, filters, .single() / .limit() / .order(), etc. are all aligned. For details, see JS SDK - RPC Calls.

Next Steps