Limits and Best Practices
This guide summarizes naming rules, upload restrictions, path design, permission practices, cache behavior, and AI-friendly engineering conventions for Postgres-Native Cloud Storage.
Naming rules
Bucket names
Use stable, readable English identifiers such as:
avatars
public-assets
private-files
team-files
article-assets
Recommendations:
- Use lowercase letters, digits, and
-. - Avoid spaces, Chinese characters, and special symbols.
- Make the name describe the business purpose.
- Do not rename production buckets frequently.
Object paths
Object names must follow these rules:
- Must not be empty.
- Must not start with
/. - Must not contain consecutive
/. - Recommended characters: letters, digits, and
-,!,_,.,*. - Chinese file or folder names are percent-encoded when requested.
- Avoid special characters:
` ^ " \\ { } [ ] ~ % # > <and ASCII decimal 128-255. - Characters such as
,,:,;,=,&,$,@,+,?, spaces, and ASCII control characters may need extra handling.
Upload restrictions
File size
Use storage.buckets.file_size_limit to set a per-file limit in bytes:
UPDATE storage.buckets
SET file_size_limit = 20 * 1024 * 1024
WHERE id = 'user-files';
Suggested values:
| Bucket | Suggested limit | Notes |
|---|---|---|
avatars | 2 MB - 5 MB | User avatars |
public-assets | 10 MB - 50 MB | Public images and static assets |
private-files | Business-dependent | User documents and archives |
team-files | Plan-dependent | Team collaboration files |
MIME types
Use storage.buckets.allowed_mime_types to whitelist allowed MIME types:
UPDATE storage.buckets
SET allowed_mime_types = ARRAY['image/png', 'image/jpeg', 'image/webp']
WHERE id = 'avatars';
Enable MIME allowlists for high-risk buckets, especially buckets where users can upload publicly visible files.
Path design best practices
A path should be easy to understand, easy to express in RLS, and hard for AI/code generators to misuse.
| Scenario | Recommended path | Reason |
|---|---|---|
| Private user files | <uid>/<filename> | First folder isolates users |
| User avatars | <uid>/avatar.<ext> | Fixed path for overwrite and lookup |
| Team files | <team_id>/<filename> | Can join team membership table |
| Article attachments | articles/<article_id>/<filename> | Can join article table |
| Public static assets | assets/<version>/<filename> | Cache and version friendly |
Avoid mixing unrelated path semantics in the same bucket, such as:
<uid>/<filename>
public/<filename>
team/<team_id>/<filename>
This makes RLS harder to audit and AI-generated policies more error-prone.
RLS best practices
Restrict bucket first
Every policy should usually start with bucket_id:
CREATE POLICY user_files_select ON storage.objects
FOR SELECT TO authenticated
USING (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()
);
Avoid broad policies without a bucket condition:
-- Not recommended: may affect every bucket
CREATE POLICY unsafe_select ON storage.objects
FOR SELECT TO authenticated
USING (owner_id = auth.uid());
Separate read, write, update, and delete
Prefer separate policies for:
FOR SELECT: read, download, list, signed URL;FOR INSERT: create object;FOR UPDATE: overwrite object or metadata;FOR DELETE: delete object.
Be careful with anonymous writes
Public read is not public write. In production, avoid:
FOR INSERT TO anon
WITH CHECK (true)
If anonymous upload is required, restrict bucket, path, size, MIME type, and add application-level risk controls.
Use service_role only on trusted backends
service_role has BYPASSRLS and skips all policies. Never expose it in browsers, Mini Programs, or mobile clients.
Overwrite best practices
Overwriting the same path can cause cache consistency issues:
- browsers may keep old cache;
- CDN nodes may briefly return old content;
- concurrent uploads may overwrite each other.
| Scenario | Recommendation |
|---|---|
| User avatar | Fixed path is fine; add a version parameter to display URL |
| Article cover / product image | Upload to a new path and update business reference |
| Static asset | Use versioned paths, such as logo.v2.png |
| Collaborative file | Avoid direct overwrite; use versions or business metadata |
For CDN cache hit rates, cacheControl, and Public / Private Bucket choices, see CDN Acceleration and Caching.
Query and index recommendations
If many policies or queries depend on the first path segment, use path_tokens and an index:
CREATE INDEX idx_objects_bucket_first_folder
ON storage.objects (bucket_id, (path_tokens[1]));
Policy condition:
bucket_id = 'user-files'
AND path_tokens[1] = auth.uid()
This is more index-friendly than repeatedly calling (storage.foldername(name))[1].
Join business tables
For team files, order attachments, or article covers, let file access follow the business table instead of encoding everything in the path.
CREATE POLICY article_asset_read ON storage.objects
FOR SELECT TO anon, authenticated
USING (
bucket_id = 'article-assets'
AND EXISTS (
SELECT 1 FROM public.articles a
WHERE a.cover_key = storage.objects.name
AND (a.is_public OR a.owner_id = auth.uid())
)
);
AI-friendly best practices
Maintain a structured contract for every bucket.
Bucket contract template
Bucket: <bucket-id>
Purpose: <what this bucket stores>
Path template: <path rule>
Read permission: <who can read>
Upload permission: <who can upload>
Overwrite permission: <who can overwrite>
Delete permission: <who can delete>
Size limit: <per-file size>
MIME limit: <allowed MIME types>
Public access: <whether getPublicUrl is allowed>
Business tables: <team_members / articles / ...>
Example
Bucket: team-files
Purpose: team collaboration attachments
Path template: <team_id>/<filename>
Read: members in team_members can read
Upload: members in team_members can upload
Delete: members with team_members.role = 'admin' can delete
Size limit: 50 MB
MIME: image/png, image/jpeg, application/pdf, application/zip
Public access: no; use download or createSignedUrl
Business table: public.team_members(team_id, user_id, role)