Loading article…

DocsLoading article…
QB Flex no longer bundles or starts MinIO. Provision RustFS or another S3-compatible provider separately, then configure the API with server-only operator credentials:
QB_S3_ENDPOINT=http://127.0.0.1:9000
QB_S3_REGION=us-east-1
QB_S3_BUCKET=qb-flex
QB_S3_ACCESS_KEY=replace_with_provider_access_key
QB_S3_SECRET_KEY=replace_with_provider_secret_keyThe endpoint above is for native development. Inside Compose, 127.0.0.1 points to that container, not the host; use a reachable service/remote endpoint or http://host.docker.internal:9000 with the host-gateway mapping. Use HTTPS for a remote production backend. Operator credentials never enter the browser SDK.
The storage-init service attempts physical-bucket provisioning. If your provider's credentials cannot create buckets, provision the physical bucket separately and grant the required object access. A logical bucket in Studio is not the physical backend bucket. Provider compatibility must be verified on your actual deployment.
Create logical buckets in Studio with name, visibility, size limit and optional MIME allowlist.
Studio refreshes file lists and related cached workspaces automatically after successful uploads/deletions. File-action menus remain non-modal; destructive confirmation dialogs still trap focus and support Escape. Cancellation restores focus to the opener; deleting its row moves focus to file search. Closing or submitting a confirmation must leave the rest of Studio interactive. See submission refresh behavior.
const result = await qb.storage.upload(bucketId, 'notes/report.txt', file);
console.log(result.data.id);The SDK creates an intent and uploads bytes through the authenticated gateway. Both management and application paths stream to private disk staging with backpressure and incremental native SHA-256, then stream validated bytes to S3. The object becomes ready only after backend persistence and expected-size verification. Failed/pending objects remain unavailable; retry/reconcile uncertain remote effects rather than assuming success.
Uploads are limited to 100 MiB, with at most four concurrent transfers and a 120-second deadline. Staging needs the expected size plus 64 MiB disk headroom. Files use mode 0600 in private directories (0700). Recognized abandoned staging is cleaned on startup only after exclusive volume ownership. Disk/WAL/backup capacity still needs operator monitoring.
Resumable upload sessions and bounded image transforms are available on the storage API. Transforms use the runtime image decoder when it is present and reject oversize dimensions. A CDN in front of public objects is not configured.
storage.upload accepts a lowercase 64-character SHA-256 digest. For a small browser file, this example uses Web Crypto; it buffers the file locally and is not a large-file streaming hash implementation.
const digest = await crypto.subtle.digest('SHA-256', await file.arrayBuffer());
const checksum = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
const upload = await client.storage.upload(bucketId, 'notes/report.txt', file, {
checksum,
signal: controller.signal,
});
console.log(upload.data.id);Abort the controller to cancel. Size mismatch returns UPLOAD_MISMATCH; a digest mismatch returns CHECKSUM_MISMATCH before S3 PUT. Staging is removed on cancellation/failure. Existing clients without checksum metadata remain compatible. Browser hash-wasm workers and image optimization are planned optional capabilities, not requirements for uploading today.
Only authenticated application users can upload. Private objects are read/modified by their owners; public buckets allow reads of ready objects but retain owner-restricted writes. The current session and metadata owner check apply on every request. Studio uses separate audited organization capabilities, not application identity.
const blob = await qb.storage.download(objectId);
const url = URL.createObjectURL(blob);
// Use the URL in your UI, then release it:
URL.revokeObjectURL(url);
await qb.storage.remove(objectId);Deletion marks metadata as deleting before removing backend bytes; it is finalized only after cleanup succeeds. Private responses use no-store. Keys are normalized and project/branch object prefixes are independent.
Database backups include object metadata, not bytes. Protect/version/back up your object provider separately. Restored metadata cannot assume the new branch prefix contains source bytes; perform explicit object recovery before expecting availability. See backups.