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
| Concept | Supabase | CloudBase PG | Notes |
|---|---|---|---|
| Database Roles | anon / authenticated / service_role | anon / authenticated / service_role | ✅ Same names and semantics |
| Account Schema | auth | auth | ✅ Same name |
| Cloud Storage Schema | storage | storage | ✅ Same name |
JWT sub | uuid string | varchar(64) string | ✅ Directly compatible |
JWT role | anon / authenticated / service_role | anon / authenticated / service_role | ✅ Directly compatible |
| Client SDK | @supabase/supabase-js | @cloudbase/js-sdk | API style is similar, main method names are aligned |
| Row-Level Permissions | RLS Policy | RLS Policy | ✅ Standard PostgreSQL feature, identical syntax |
| Auto-generated REST API | PostgREST | PostgREST | ✅ Same open-source project, query syntax is essentially identical |
| RPC Calls | .rpc() | .rpc() | ✅ Directly compatible |
| File Storage Permissions | RLS over storage.objects | RLS over storage.objects | ✅ Same approach |
| anon Token | anon API key | anon Publishable Key | ✅ Same concept |
| service Token | service_role API key | service_role API Key | ✅ Same concept |
Key Differences
| Difference | Supabase | CloudBase PG | Migration 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.com | Replace domain |
| Realtime (Live Subscriptions) | ✅ Built-in | ❌ Not supported | Switch to polling / custom WebSocket |
| GraphQL API | ✅ pg_graphql | ❌ Not supported | Use RESTful only |
| Database Branching | ✅ | ❌ Not supported | Use environment isolation (dev / staging / prod) |
| DDL Channel | Direct database connection / Studio | Console SQL Editor / Cloud API ExecutePGSql | Use 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 compatibleCREATE POLICYwithauth.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.idis of typevarchar(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
| Operation | Supabase | CloudBase PG |
|---|---|---|
| Sign Up | supabase.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 In | supabase.auth.signInWithPassword({ email, password }) | auth.signInWithPassword({ username, password }) |
| Anonymous Sign In | supabase.auth.signInAnonymously() | auth.signInAnonymously() |
| Third-Party Sign In | supabase.auth.signInWithOAuth({ provider }) | auth.signInWithProvider({ provider_token }), see Auth.signInWithProvider |
| Sign Out | supabase.auth.signOut() | auth.signOut() |
| Get Session | supabase.auth.getSession() | auth.getSession() |
| Listen to Auth State | supabase.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:
- Export Supabase Data: Use
pg_dump --data-only --no-owner --no-privilegesto export business data - 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) - Sync
auth.users: Since theauthschema 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
COPYcommand withExecutePGSqlis 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:
- Check if the role has table-level GRANT (behavior is consistent between CloudBase and Supabase, but easy to overlook)
- Check if SEQUENCE permissions for
serialprimary keys are granted
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.