File storage
Every project has a blob store alongside its database. Uploads use multipart/form-data. All other operations use JSON metadata or raw byte streams.
The JavaScript examples below use the smoldb-js SDK:
import { smoldb } from "smoldb-js";
const db = smoldb({
url: "https://your-smoldb.com",
projectId: "<projectId>",
key: "smol_...",
});Uploading
POST /files with a single file field in a FormData body. The SDK builds the form when passed a File or Blob.
const { data, error } = await db.files.upload(file); // File from <input type="file">
// data: { id: "<fileId>" }curl -X POST $SMOLDB_URL/files \
-H "x-api-key: $KEY" \
-F "file=@photo.jpg"Warning. When calling fetch directly, do not set Content-Type manually. The runtime must pick the multipart boundary. Passing Content-Type: multipart/form-data without a boundary prevents the server from parsing the body and the upload fails silently. The SDK handles this correctly.
In a browser, a File from <input type="file"> can be passed through directly:
async function onChange(e) {
const file = e.target.files[0];
await db.files.upload(file);
}Listing
GET /files returns an array of metadata rows. It does not return blob bytes.
const { data: files } = await db.files.list();
// files: [{ id, name, size, created_at }, ...]Downloading
GET /files/:fileId streams the raw bytes with Content-Disposition: attachment and X-Content-Type-Options: nosniff. The server never renders uploaded bytes inline — a malicious user could otherwise upload a text/html file and weaponise it as stored XSS against anyone who opens the URL.
Practical consequences:
<a href={url}>will download the file rather than preview it in a new tab.<img src={url}>still renders for image MIME types (browsers honour attachment headers only for top-level navigation).- To preview a PDF or text file in-app, fetch the bytes and render from a
BlobURL:
const res = await fetch(
`https://your-smoldb.com/api/projects/<projectId>/files/${fileId}`,
{ headers: { "x-api-key": "smol_..." } },
);
const blob = await res.blob();
const previewUrl = URL.createObjectURL(blob); // safe: object URL, not same-origin HTMLProgrammatic fetches must pass the API key.
Deleting
DELETE /files/:fileId removes the blob and the metadata row in a single request.
const { error } = await db.files.delete(fileId);Limits
| Plan | File storage | |------|--------------| | free | 200 MB | | pro | 2 GB |
Uploads can fail with two quota statuses:
| Status | Condition |
|--------|-----------|
| 413 | The single file is larger than the plan limit. |
| 403 | Total storage after this upload would exceed the plan limit. |
Both are surfaced as error on the SDK's { data, error } return value.