Storage & Files
Overview
Section titled “Overview”The client.storage module provides methods for file management — upload, download, listing, and deletion. It works with both local disk and S3-compatible storage backends, depending on your server configuration.
All storage methods use the shared transport, so authentication tokens are injected automatically.
Upload a File
Section titled “Upload a File”Use putObject() to upload a file. It accepts a File or Blob object along with an optional storage key and metadata:
const result = await client.storage.putObject({ file: fileObject, // File or Blob key: "products/images/camera.jpg", // Storage path (optional) bucket: "uploads", // Bucket name (optional) public: false, // Store public (permanent token-less URL) — optional, default false metadata: { // Custom metadata (optional) description: "Product photo", uploadedBy: "user-123" }});
// result: { key: string; bucket: string; storageUrl: string }A file over 8 MiB goes up through the server’s resumable (TUS)
endpoint, 8 MiB per request, instead of as one multipart request the server
parses whole — so a large upload costs the server one chunk of memory, not three
to five times the file. A chunk that fails on the way is sent again from where
the server got to. The result, the stored object and the server’s checks are
the same either way. When the resumable endpoint cannot take the file (an older
server, or several instances that do not share uploads), it goes as one
POST /api/storage/upload, and so does every later file from that client. See
Resumable uploads (TUS).
From a File Input
Section titled “From a File Input”const input = document.querySelector<HTMLInputElement>("#file-input");const file = input?.files?.[0];
if (file) { const result = await client.storage.putObject({ file, key: `avatars/${userId}/${file.name}` }); console.log("Uploaded to:", result.key);}Get a Signed URL
Section titled “Get a Signed URL”Retrieve a download URL and metadata for a stored file:
const { url, metadata, fileNotFound } = await client.storage.getSignedUrl( "products/images/camera.jpg");
if (url) { console.log("Download URL:", url); console.log("Content type:", metadata?.contentType);} else { console.log("File not found");}With a specific bucket:
const { url } = await client.storage.getSignedUrl( "camera.jpg", "product-images" // bucket);The SDK caches signed URLs to avoid redundant server calls, until each one’s
download token expires. Lookups made together — a page of previews
calling getSignedUrl() in the same tick — go to the server as one
POST /api/storage/metadata per 100 files, which the rate limiter counts as a
tenth of the requests it replaces; a file that one caller may not read fails
that call alone. The cache belongs to the credential that asked: when
the access token changes — a sign-out, another user signing in, a refresh — it
starts over, so one user is never handed a URL minted for another.
Private vs. public URLs
Section titled “Private vs. public URLs”- Private files get a URL with a short-lived, path-scoped download token (
?token=…, 5 minutes unless the server setsSTORAGE_DOWNLOAD_TOKEN_TTL, which 0.23 does not read) — never your access token. Because it expires, don’t persist a private URL; store the file path and callgetSignedUrl()again when you render it. - Public files (stored under the
public/prefix — setstorage: { public: true }on the property, or passpublic: truetoputObject) get a stable, token-less, permanent, CDN-cacheable URL with no server round-trip. These are safe to store in a database and hotlink.
Files inside text
Section titled “Files inside text”Text that embeds a file — the images in a markdown field — cannot hold a
private URL either, so it holds a storage reference instead:
rebase-storage:posts/cover.png, with ?storageId=media when the file is in a
named source. That is what the admin panel’s markdown editor writes for an
uploaded image. Exchange the references for URLs when you render the text:
import { resolveStorageReferences } from "@rebasepro/client";
const post = await client.data.collection("posts").findById("post-1");const body = await resolveStorageReferences(String(post?.body ?? ""), client);// `body` is the same markdown, each reference replaced by a fresh URL.Each object is signed once per call, from its own source, so a private image
keeps working however long ago it was uploaded. A reference that names an
object that no longer exists is left as it is. storageReference(key, storageId?) builds one, for text you write yourself.
Text saved before references existed holds a private file’s download URL
itself, …/storage/file/posts/cover.png?token=…, whose token expired minutes
after it was written. resolveStorageReferences renews those too: the token
still names the object, so the URL is replaced by a fresh one. To store
references instead, rewrite the text once with
storageReferencesForDownloadUrls(text), which leaves every other URL as it
is.
Download a File
Section titled “Download a File”Retrieve a file as a File object:
const file = await client.storage.getObject("products/images/camera.jpg");
if (file) { console.log("File name:", file.name); console.log("File type:", file.type); console.log("File size:", file.size);
// Create a download link const url = URL.createObjectURL(file); window.open(url);} else { console.log("File not found");}With a specific bucket:
const file = await client.storage.getObject("camera.jpg", "product-images");Delete a File
Section titled “Delete a File”await client.storage.deleteObject("products/images/camera.jpg");
// With bucketawait client.storage.deleteObject("camera.jpg", "product-images");Deleting a non-existent file does not throw an error.
List Files
Section titled “List Files”List files by prefix, with optional pagination:
const result = await client.storage.listObjects("products/images/", { bucket: "uploads", maxResults: 50, pageToken: undefined // for pagination});
for (const item of result.items) { console.log(item.fullPath, item.name, item.size, item.updated, item.contentType);}
// Paginateif (result.nextPageToken) { const nextPage = await client.storage.listObjects("products/images/", { pageToken: result.nextPageToken });}Each item carries its size in bytes and updated (an ISO 8601
timestamp) on local storage, S3 and GCS, and its contentType on local storage
and GCS — enough to show a folder without a getSignedUrl() per file. An S3
listing has no content type; folders (result.prefixes) carry none of the
three.
Storage Key Formats
Section titled “Storage Key Formats”The SDK transparently handles storage key prefixes. You can pass keys with or without the protocol prefix:
// All equivalent — the SDK strips the prefix internallyawait client.storage.getSignedUrl("local://products/image.jpg");await client.storage.getSignedUrl("s3://products/image.jpg");await client.storage.getSignedUrl("products/image.jpg");API Reference
Section titled “API Reference”| Method | Description | Returns |
|---|---|---|
putObject({ file, key?, bucket?, metadata? }) |
Upload a file | UploadFileResult |
getSignedUrl(key, bucket?) |
Get download URL + metadata | DownloadConfig |
getObject(key, bucket?) |
Download as File object |
File | null |
deleteObject(key, bucket?) |
Delete a file | void |
listObjects(prefix, options?) |
List files by prefix | StorageListResult |
Next Steps
Section titled “Next Steps”- Storage Configuration — Configure S3 or local storage on the server
- Querying Data — CRUD operations and query builder
- Authentication — Sign in and session management