Skip to main content

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:

BucketSuggested limitNotes
avatars2 MB - 5 MBUser avatars
public-assets10 MB - 50 MBPublic images and static assets
private-filesBusiness-dependentUser documents and archives
team-filesPlan-dependentTeam 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.

ScenarioRecommended pathReason
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 attachmentsarticles/<article_id>/<filename>Can join article table
Public static assetsassets/<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.
ScenarioRecommendation
User avatarFixed path is fine; add a version parameter to display URL
Article cover / product imageUpload to a new path and update business reference
Static assetUse versioned paths, such as logo.v2.png
Collaborative fileAvoid 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)