Postgres-Native Cloud Storage
Before reading this section, see Postgres-Native overview for the high-level differences between Postgres-Native and the classic mode.
In Postgres-Native, cloud storage shares the same permission model as the database — file metadata lives in PostgreSQL's storage schema, and access control is fully described by RLS policies. There is no need for built-in permission tags such as READONLY / PRIVATE, nor for storage-specific JSON security rules. The very same SQL policies that protect your business tables also control file read/write.
CloudBase also provides Classic Cloud Storage. Pick the one that matches your environment type.
Core differences from the classic mode
Postgres-Native differs from classic mode in three fundamental ways:
1. Bucket is a first-class citizen, with SQL-level per-bucket control
- Classic mode: the environment is tightly bound to an underlying COS bucket. Developers mostly work with path prefixes rather than truly independent buckets, and permissions can only be chosen from a built-in mode at bucket creation time.
- Postgres-Native:
storage.bucketsis a regular table in PostgreSQL, and each bucket is a row. You can:- configure per-bucket metadata such as
public/file_size_limit/allowed_mime_types - reference
bucket_id = 'xxx'directly inside RLS policies - manage bucket metadata via plain SQL — no console or dedicated API required.
- configure per-bucket metadata such as
2. Object permissions are entirely RLS-based — no more JSON security rules
- Classic mode: permissions are expressed via built-in tags (
READONLY/PRIVATE/ADMINWRITE/ADMINONLY/CUSTOM) or a separate JSON security-rule DSL, with granularity limited to bucket / path prefix / simple roles. - Postgres-Native: all read/write permissions are expressed via RLS policies on
storage.objects/storage.buckets.storage.objectsisGRANT ALL TO anon, authenticated, service_roleby default — RLS is the sole gate. You can use arbitrary SQL expressions and JOIN business tables across schemas, sharing the same language and semantics as the database.
3. No direct client-to-COS upload — everything goes through the Storage API
To guarantee strong consistency across metadata, object bytes, and RLS checks, all reads and writes in Postgres-Native flow through the Storage API. It transactionally coordinates writes to storage.objects with COS operations, eliminating the "object exists in COS but no row in storage.objects" (or vice versa) orphan state.
| Dimension | Classic mode | Postgres-Native |
|---|---|---|
| Client upload path | SDK gets a temporary signature and uploads directly to COS; the backend does not handle bytes | SDK → Storage API → COS, coordinated by a single entry point |
| Metadata write timing | COS callback / application backfill | Storage API writes storage.objects inside the same transaction |
owner_id injection | Maintained by the application | Storage API injects the JWT sub automatically — cannot be forged by the client |
| RLS enforcement | N/A | WITH CHECK on INSERT blocks at the database layer; unauthorized requests never reach COS |
| Delete consistency | Self-checked by the application | The protect_delete trigger forces deletion through the Storage API: object first, then row |
| Large-file multipart | Direct COS multipart upload | Coordinated via the storage.s3_multipart_uploads table |
| Reading file metadata (size / mimetype, ...) | Extra calls to the COS API | SELECT metadata FROM storage.objects, joinable with business tables |
In Postgres-Native mode, prefer SDKs / APIs that use native bucket semantics, such as app.storage.from(bucketId) in JS SDK, the tcb storage objects command group in CLI, or the Postgres-Native Cloud Storage HTTP API. See SDK Guide for complete examples.
Differences at a glance
| Dimension | Classic cloud storage | Postgres-Native cloud storage |
|---|---|---|
| Bucket concept | Tightly bound to environment, mainly used as a path prefix | A row in storage.buckets, fully manageable via SQL |
| Permission description | Built-in modes or JSON security rules | RLS policies (SQL) |
| Granularity | Bucket / path prefix / simple roles | Any SQL expression, with cross-table JOINs |
| Metadata access | Console / SDK | Direct SQL on storage.objects |
| Linkage with business data | Synced manually | Same instance, same schema; JOIN directly |
| Upload path | Client uploads directly to COS | Client → Storage API → COS |
| How it is selected | Pick a permission mode when creating a bucket | Determined automatically by the environment type |
In a Postgres-Native environment, storage permission checks automatically go through RLS based on the environment type. Developers do not need to switch anything manually.
Architecture
Client (Web / Mini Program / Cloud Function)
│ Authorization: Bearer <Token>
▼
CloudBase Gateway
│ Parse JWT → set role + request.jwt.claims
▼
Storage API
│ Read / write metadata in the storage schema
▼
PostgreSQL · storage schema ←┐
├─ storage.buckets │ RLS policy check
├─ storage.objects │
└─ storage.s3_multipart_uploads ←┘
│
▼
Object storage backend (COS) ← actual file bytes
Key facts:
- File bytes live in the object storage backend; metadata lives in the
storageschema. storage.buckets/storage.objectsare by defaultGRANT ALL TO anon, authenticated, service_role— the only gate is the RLS policy. (This is the key difference from business tables, which use a "GRANT + RLS" two-layer model.)- You cannot run
DELETE FROM storage.objectsdirectly: the table has aprotect_deletetrigger. Deletion must go through the Storage API to avoid orphaned objects in the storage backend.
How to read this section
| Goal | Recommended reading |
|---|---|
| First time here, want to run Hello World | Quick experience |
| Design buckets, public / private access, and upload limits | Bucket Management |
| Upload files, handle overwrites and custom metadata | Upload Files |
| Download files, create signed URLs or public URLs | Access and Download Files |
| List, copy, move, or delete files | Manage Files |
| Understand the schema and permission model | Permission management |
| Copy-paste RLS templates | RLS policy patterns |
| Check naming, size, MIME, and overwrite recommendations | Limits and Best Practices |
| Understand CDN cache and Public / Private cache differences | CDN Acceleration and Caching |
| Migrate from Supabase Storage | Migrate from Supabase Storage |
| Server-side / cross-language HTTP API calls | Postgres-Native Cloud Storage HTTP API |
| Hit a 403 / authorization issue / DELETE error | FAQ |
| Content moderation, CI features, image processing | Shared with classic mode — see Classic cloud storage |
HTTP API
Postgres-Native cloud storage exposes a complete RESTful HTTP API, callable from any backend language:
- Bucket operations: create, list, get, update, delete
- Object operations: upload, download, copy, move, batch delete, create and use signed upload / download URLs
Full API list: Postgres-Native Cloud Storage HTTP API.
Next steps
- Quick experience — run "upload + RLS + denied" end-to-end in 5 minutes
- Bucket Management — bucket fields, public / private access model, upload limits
- Upload Files —
contentType,metadata,upsert, and path design - Access and Download Files —
download(), signed URLs, and public URLs - Manage Files — list, info, copy, move, delete
- Permission management —
storageschema, helper functions, the RLS-only model - RLS policy patterns — copy-pasteable templates: PUBLIC / READONLY / PRIVATE / ADMINWRITE / ADMINONLY and more
- Limits and Best Practices — naming, size, MIME, overwrites, and AI-friendly contracts
- CDN Acceleration and Caching — CDN access flow, Public / Private cache differences,
cacheControl - Migrate from Supabase Storage — migration checklist, differences, and regression tests
- Postgres-Native Cloud Storage HTTP API — server-side / cross-language API calls
- Postgres-Native: Authentication —
auth.uid()and the three roles