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:
- Create a bucket. See Bucket Management.
- Add an
INSERTpolicy onstorage.objectsto allow uploads. - 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.
| Scenario | Recommended path | Common 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 attachments | articles/<article_id>/<filename> | JOIN articles |
| Public assets | assets/<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 CHECKfor new objects;UPDATE USINGandUPDATE WITH CHECKfor 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:
| Source | Example |
|---|---|
| Bucket file size | storage.buckets.file_size_limit |
| Bucket MIME whitelist | storage.buckets.allowed_mime_types |
RLS INSERT / UPDATE | Path, owner, team membership |
| Object naming rules | Non-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
| Symptom | Likely cause | What to check |
|---|---|---|
| Upload returns 403 | INSERT WITH CHECK failed | bucket_id, first path segment, owner_id, current auth.uid() |
| Overwrite fails | Missing UPDATE policy | Add FOR UPDATE USING + WITH CHECK |
| File too large | Exceeds file_size_limit | Adjust the bucket limit or use another bucket |
| MIME type rejected | Not in allowed_mime_types | Check contentType and bucket whitelist |
| Object already exists | upsert: true not set | Use 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