Skip to main content

Migrate from Supabase Storage

Postgres-Native Cloud Storage is close to Supabase Storage in the storage schema, Bucket / Object metadata model, and RLS approach. If your application mainly uses Supabase File Buckets, you can migrate using this guide.

Before migration

Check your current Supabase Storage usage:

ItemDescription
Bucket typeGeneral-purpose file buckets such as images, videos, documents, archives
Access modelPublic / Private bucket, signed URL, authenticated download
RLS policiesSELECT / INSERT / UPDATE / DELETE on storage.objects
Path rules<uid>/<filename>, <team_id>/<filename>, etc.
Upload restrictionsFile size limit and MIME type allowlist
SDK APIsupload, download, list, remove, createSignedUrl, etc.

Start with a low-risk bucket, such as avatars or a test attachment bucket.

Reusable parts

The following usually map directly or with small adjustments:

Supabase StorageCloudBase
storage.bucketsstorage.buckets
storage.objectsstorage.objects
bucket_idbucket_id
name object pathname object path
owner_idowner_id
metadata / user_metadatametadata / user_metadata
storage.foldername(name)storage.foldername(name)
storage.filename(name)storage.filename(name)
Most RLS policy ideasCloudBase RLS policies

For example, user-folder isolation is usually very similar:

CREATE POLICY user_files_select ON storage.objects
FOR SELECT TO authenticated
USING (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()
);

Differences to review

Review public semantics

In CloudBase Postgres-Native mode, storage.buckets.public is metadata. It does not automatically bypass RLS. Public readability is still decided by the SELECT policy on storage.objects.

If a Supabase Public Bucket relies on "anyone with the URL can access it", add an explicit public-read policy after migration:

CREATE POLICY objects_public_read ON storage.objects
FOR SELECT TO anon, authenticated
USING (
EXISTS (
SELECT 1 FROM storage.buckets b
WHERE b.id = storage.objects.bucket_id
AND b.public
)
);

SDK initialization differs

Supabase:

import { createClient } from '@supabase/supabase-js';

const supabase = createClient('<project-url>', '<anon-key>');
const bucket = supabase.storage.from('avatars');

CloudBase:

import cloudbase from '@cloudbase/js-sdk';

const app = cloudbase.init({ env: '<env-id>' });
const bucket = app.storage.from('avatars');

Review response fields and URLs

Check these fields during migration:

  • upload result object ID, path, and full path;
  • signed URL fields such as signedUrl / fullSignedURL;
  • public URL fields;
  • error object shape;
  • pagination fields such as hasNext and nextCursor.

Redesign advanced capabilities when needed

If your Supabase project uses TUS resumable uploads, S3-compatible protocols, image transformations, specific CDN headers, or other advanced capabilities, verify the CloudBase equivalent and recommended access path before migrating that logic.

For common File Bucket scenarios, prioritize these stable capabilities:

  • Bucket creation and configuration;
  • upload / download;
  • list objects;
  • copy / move / delete;
  • signed download / signed upload;
  • RLS policies.

Recommended migration steps

1. Inventory buckets

Record a contract for each bucket:

Bucket: avatars
Purpose: user avatars
Public: false
Size limit: 5 MB
MIME: image/png, image/jpeg, image/webp
Path: <uid>/avatar.png
Permission: owner writes; read depends on business rules

2. Create buckets in CloudBase

INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'avatars',
'avatars',
false,
5 * 1024 * 1024,
ARRAY['image/png', 'image/jpeg', 'image/webp']
);

3. Migrate file bytes

Choose a method based on scale:

  • Small scale: download through SDK and upload again.
  • Medium scale: write a server-side batch migration script.
  • Large scale: use object-storage backend tooling, then backfill metadata.

Store all credentials in environment variables. Do not commit Supabase service keys, CloudBase API Keys, or Tencent Cloud secrets.

4. Migrate metadata and business references

If business tables store Supabase file URLs, migrate them to stable bucket-relative paths instead:

avatars/user-123/avatar.png

Store object paths in business data, then generate public URLs or signed URLs at display time.

5. Migrate RLS policies

Migrate SELECT, INSERT, UPDATE, and DELETE policies bucket by bucket. Verify:

  • owner can access their own files;
  • non-owner cannot access private files;
  • anonymous access matches expectations;
  • service_role is used only on trusted backends.

6. Replace SDK calls

SupabaseCloudBase
supabase.storage.from(bucket)app.storage.from(bucket)
bucket.upload(path, file, options)bucket.upload(path, file, options)
bucket.download(path)bucket.download(path)
bucket.list(prefix, options)bucket.list(prefix, options)
bucket.remove(paths)bucket.remove(paths)
bucket.createSignedUrl(path, expiresIn)bucket.createSignedUrl(path, expiresIn)
bucket.getPublicUrl(path)bucket.getPublicUrl(path)

Similar method names do not guarantee identical response shapes. Use JS SDK Storage API as the source of truth.

7. Run permission regression tests

CaseExpected result
Owner uploads own fileSuccess
Owner downloads own fileSuccess
Non-owner downloads private fileFail
Anonymous reads public fileDepends on public-read policy
Anonymous reads private fileFail
Owner overwrites own fileDepends on UPDATE policy
Non-admin deletes team fileFail
Admin deletes team fileSuccess

AI-friendly migration checklist

When asking AI to help migrate, provide a structured bucket contract:

Source: Supabase Storage
Target: CloudBase Postgres-Native Cloud Storage
Bucket: avatars
Purpose: user avatars
Path: <uid>/avatar.png
Public: no
Read: all signed-in users can read
Upload: only the owner can upload to their <uid>/ folder
Overwrite: only the owner can overwrite their avatar
Delete: only the owner can delete their avatar
Limits: 5 MB; image/png, image/jpeg, image/webp
Need: Bucket SQL, RLS policies, JS SDK upload and signed URL code