Storage
S3-compatible object storage with Row Level Security, ownership-aware access, and on-the-fly image transforms.
Model
Each project has its own storage namespace. File metadata lives in a system table (_atelier_files) with RLS, so the same authz model that protects your database protects your files.
Upload
import { atelier } from '@atelier/sdk';
const { fileId, url } = await atelier.storage.upload(file, {
bucket: 'avatars',
visibility: 'private', // or 'public'
});
Read
Public files: a stable URL is returned at upload. Private files: request a short-lived signed URL when needed.
// Public
<img src={atelier.storage.url(fileId)} />
// Private — short-lived signed URL
const url = await atelier.storage.sign(fileId, { ttlSec: 300 });
RLS for files
Each file row has an owner_id column populated from the OIDC sub. Default policies let owners read and write their own files; override these with custom policies the same way as for any other table.
-- Example: team-shared files
CREATE POLICY files_team_read ON _atelier_files
FOR SELECT
USING (
owner_id = current_setting('atelier.user_id')::uuid
OR EXISTS (
SELECT 1 FROM team_members
WHERE team_id = _atelier_files.team_id
AND user_id = current_setting('atelier.user_id')::uuid
)
);
Image transforms
Request transforms inline; Base resizes, re-encodes, and caches at the edge:
atelier.storage.url(fileId, { w: 400, h: 400, format: 'webp', quality: 82 });
atelier.storage.url(fileId, { w: 1200, format: 'avif' });
atelier.storage.url(fileId, { fit: 'cover', w: 600, h: 400 });
- Supported:
w,h,fit(cover/contain),format(webp, avif, jpeg, png),quality(1–100) - Transforms cached per variant. First request transforms; subsequent reads are CDN-served.
Logical buckets
Group files by bucket for clarity and per-bucket policies (max size, allowed mimetypes, public/private default). Buckets are namespaces, not separate stores.
// atelier.config.ts
export default {
storage: {
buckets: {
avatars: { maxSizeMb: 5, mimeTypes: ['image/*'], default: 'public' },
reports: { maxSizeMb: 100, default: 'private' },
},
},
};
Storage is S3-compatible (S3 in production, MinIO in dev). Bring your own bucket if you need raw S3 access — including Cloudflare R2 for zero-egress.