Managed files
OpenCloud Files stores browser uploads behind opaque IDs. Applications do not receive buckets, object paths, S3 credentials, or Storage request headers.
Declare access
files:
access: user
maxUploadBytes: 52428800userisolates files by the current user and is the safe default.appshares files between authenticated members of the app.maxUploadBytesaccepts 1 byte through 100 MiB and defaults to 50 MiB.
Omit files when the app has no file workflow; app.info() then reports the capability as unavailable.
Upload directly
const file = await opencloud.files.upload({
data: fileInput.files[0],
name: fileInput.files[0].name,
onProgress: ({ percent }) => renderUploadProgress(percent),
});
await opencloud.files.save(file);The SDK checks the configured size limit before sending bytes, normalizes the name and content type, creates an idempotency key, retries one transient failure with that same private key, and returns { id, name, contentType, size, createdAt, updatedAt }. Application code does not create retry keys or replay an ambiguous upload.
Upload and create metadata together
Create an RLS-protected attachment table with conventional columns:
create table item_attachments (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null default auth.uid(),
item_id uuid not null references items(id) on delete cascade,
file_id uuid not null unique,
file_name text not null,
file_type text not null,
file_size bigint not null check (file_size >= 0),
created_at timestamptz not null default now()
);
create policy item_attachments_owner_access
on item_attachments for all
using (owner_id = auth.uid())
with check (owner_id = auth.uid());Then use the compound helper:
await opencloud.auth.requireUser();
const { file, record } = await opencloud.files.attach({
data: selectedFile,
name: selectedFile.name,
table: "item_attachments",
values: { item_id: itemId },
});attach writes file_id, file_name, file_type, and file_size. It reconciles an ambiguous metadata write by opaque file ID and removes the upload after a definite row failure. A FILE_ATTACHMENT_INCOMPLETE error includes the file ID when cleanup must be resolved explicitly.
Lifecycle
const download = await opencloud.files.download(file);
useBlob(download.data);
const replacement = await opencloud.files.replace(file, {
data: newBlob,
name: "replacement.pdf",
});
await opencloud.files.remove(replacement.id);Use the returned object when a filename matters. Treat the ID as opaque and store it in RLS data. Never derive ownership from a filename, construct a bucket/object path, call Storage endpoints, or put secrets in metadata.
See and delete files in the dashboard
People who can edit the app see every file its users uploaded under Files → Application files on the app's dashboard page, whoever uploaded it. From there they can preview images and text files, download any file, and delete one. Deleting removes the file for everyone; rows in your tables that still hold its ID then get FILE_NOT_FOUND, so show a missing file gracefully. The Agent files tab lists the files sent in the app's Agent chat.
