Skip to main content

Upload Files

This guide explains how to upload files in Postgres-Native Cloud Storage, including contentType, custom metadata, overwrite uploads, path design, and common failures.

Prerequisites

Before you start:

  1. Create a bucket. See Bucket Management.
  2. Add an INSERT policy on storage.objects to allow uploads.
  3. Make sure the client is signed in and auth.uid() can identify the user in RLS.

Basic upload

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

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

const { data, error } = await bucket.upload('user-123/avatar.png', file, {
contentType: file.type || 'image/png',
});

if (error) {
throw error;
}

console.log(data.id);
console.log(data.path);
console.log(data.fullPath);

user-123/avatar.png is the object name inside the selected bucket. Do not pass a cloud:// fileID to native Postgres-Native APIs.

Path design

Object paths participate directly in RLS checks. Design the path first, then write the policy.

ScenarioRecommended pathCommon policy condition
User avatars<uid>/avatar.png(storage.foldername(name))[1] = auth.uid()
Private user files<uid>/<filename>owner_id = auth.uid() or first folder equals auth.uid()
Team files<team_id>/<filename>JOIN team_members
Article attachmentsarticles/<article_id>/<filename>JOIN articles
Public assetsassets/<version>/<filename>Public read, server-side write

The path is part of the permission model. Avoid mixing unrelated path semantics in the same bucket.

Specify Content-Type

For File objects, use file.type when available. For Blob, ArrayBuffer, or extensionless files, specify contentType explicitly.

await bucket.upload('user-123/avatar.webp', file, {
contentType: 'image/webp',
});

contentType affects browser preview/download behavior and is checked against allowed_mime_types.

Custom metadata

const { error } = await bucket.upload('user-123/avatar.png', file, {
contentType: 'image/png',
metadata: {
usage: 'avatar',
source: 'profile-page',
},
});

if (error) {
throw error;
}

System metadata such as size, mimetype, and cacheControl is written by the Storage API. Application metadata is stored in user_metadata and can be queried or used in RLS.

Overwrite uploads

By default, uploading to an existing path fails. Pass upsert: true only when you intentionally want to overwrite:

const { error } = await bucket.upload('user-123/avatar.png', file, {
contentType: 'image/png',
upsert: true,
});

Overwrite uploads typically require both:

  • INSERT WITH CHECK for new objects;
  • UPDATE USING and UPDATE WITH CHECK for existing objects.

If only an INSERT policy exists, the first upload may succeed but overwrite uploads will fail.

Cache risks when overwriting

If the file is already cached by the browser, CDN, or an intermediate proxy, users may briefly see the old content after an overwrite.

For public assets, article images, and product images, prefer new paths:

assets/logo.v2.png
articles/123/cover-20260614.png
uploads/<uid>/<uuid>.jpg

For fixed-path assets such as avatars, upsert: true is acceptable, but add a version or update timestamp to the display URL when needed.

Upload restrictions

Uploads are checked by multiple rules:

SourceExample
Bucket file sizestorage.buckets.file_size_limit
Bucket MIME whiteliststorage.buckets.allowed_mime_types
RLS INSERT / UPDATEPath, owner, team membership
Object naming rulesNon-empty, not starting with /, no consecutive /

See Limits and Best Practices.

Common upload policies

One user folder per user

CREATE POLICY user_files_insert ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()
);

Uploader must be the owner

CREATE POLICY private_insert ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'private-files'
AND owner_id = auth.uid()
);

Team members can upload to the team folder

CREATE POLICY team_files_insert ON storage.objects
FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'team-files'
AND EXISTS (
SELECT 1 FROM public.team_members tm
WHERE tm.team_id = (storage.foldername(name))[1]
AND tm.user_id = auth.uid()
)
);

More templates: RLS policy patterns.

Troubleshooting

SymptomLikely causeWhat to check
Upload returns 403INSERT WITH CHECK failedbucket_id, first path segment, owner_id, current auth.uid()
Overwrite failsMissing UPDATE policyAdd FOR UPDATE USING + WITH CHECK
File too largeExceeds file_size_limitAdjust the bucket limit or use another bucket
MIME type rejectedNot in allowed_mime_typesCheck contentType and bucket whitelist
Object already existsupsert: true not setUse a new path or explicitly overwrite

AI-friendly prompt

When asking AI to generate upload code or RLS, provide structured input:

Bucket: user-files
Path template: <uid>/<filename>
Uploader: authenticated user
Allowed operations: upload, overwrite, delete own files
Limits: 20 MB; image/png, image/jpeg, application/pdf
Forbidden: uploading to another user's folder; anonymous upload