Files and media
The local filesystem is ephemeral, so save files that need to persist through an SDK. There are two SDKs for different purposes.
| Files SDK | Media SDK | |
|---|---|---|
| What to store | Files that the app names and updates itself (settings, state, caches, generated CSV files) | Images, PDFs, and attachments uploaded by users or generated by the app |
| Who can read them | Only the app. They have no URL and are not served over HTTP | Members of the app. They are served at URLs |
| Overwriting | Supported (about once per second per key) | Not supported (one ID per file, write once) |
| Limit | 10 MiB per file | 50 MiB per file |
Neither SDK requires a declaration in keelson.yaml. A file is persistent when the save completes; there is no background synchronization. For structured data that is read and written on each request, use Managed SQLite.
Installation
Section titled “Installation”| Language | Package |
|---|---|
| Node.js | npm install @keelsonhq/files @keelsonhq/media |
| Python | pip install keelson-sdk (from keelson import files, media) |
| Go | go get github.com/keelsonhq/go-sdk (.../go-sdk/files, .../go-sdk/media) |
Files SDK
Section titled “Files SDK”Keys are slash-separated relative paths (settings.json, reports/2026-08.csv). The SDK has four operations: write, read, delete, and list. If a key does not exist, read returns null / None.
import * as files from "@keelsonhq/files";
await files.write("settings.json", JSON.stringify({ theme: "dark" }));const raw = await files.read("settings.json"); // Uint8Array | nullconst settings = raw ? JSON.parse(new TextDecoder().decode(raw)) : {};const keys = await files.list("reports/"); // ["reports/2026-08.csv", ...]await files.delete("reports/2026-07.csv");import jsonfrom keelson import files
files.write("settings.json", json.dumps({"theme": "dark"}))settings = json.loads(files.read("settings.json") or "{}") # read() -> bytes | Nonekeys = files.list("reports/")files.delete("reports/2026-07.csv")A common use is to replace writes to local files with SDK calls. Change open("seen_urls.json", "w") to files.write("seen_urls.json", ...), and the Scheduled Job and web app can share the file.
Media SDK
Section titled “Media SDK”Uploading with put returns an ID (ULID). Store the ID in the database, then use url(id) to construct a URL when displaying the file.
import * as media from "@keelsonhq/media";
// Uploadconst id = await media.put(req.file.buffer, { contentType: req.file.mimetype, filename: req.file.originalname,});await db.execute({ sql: "UPDATE items SET photo_id = ? WHERE id = ?", args: [id, itemId] });
// Displayconst src = media.url(id); // "/__keelson/media/<id>"from keelson import media
file_id = media.put(upload.read(), content_type=upload.mimetype, filename=upload.filename)src = media.url(file_id) # "/__keelson/media/<id>"- The URL is
/__keelson/media/<id>. It uses the same authentication as the app, so only members with view permission for that app can open it. It is not a public URL - The SDK provides
get(id)for content,stat(id)for the Content-Type and size, as well asexists(id)anddelete(id) - If no Content-Type is specified, it is determined from the filename extension
Local development
Section titled “Local development”Both SDKs automatically use local mode outside Keelson.
- Files: writes actual files under
./.keelson/files/. Add.keelson/to.gitignore - Media: writes files under
./media/(configurable withMEDIA_DIR)
The SDKs use the KEELSON_MODE environment variable to determine whether they are running on Keelson. No configuration is required.
The Node.js Files SDK does not support local mode on Windows. Use WSL2 or a Linux devcontainer. Python and Go do not have this restriction.
What you cannot do
Section titled “What you cannot do”- You cannot list, download, or replace an app’s files from the console or CLI. If users need to retrieve files, implement an authenticated download endpoint in the app (Keelson authentication applies to regular routes)
- Save files that exceed the limits directly from the app to an external S3-compatible object store (outbound traffic is unrestricted)
Capacity
Section titled “Capacity”Files stored with Files / Media count toward the workspace storage capacity described in Plans and limits.
Related pages
Section titled “Related pages”- Database (Managed SQLite)
- Scheduled Jobs — use the Files SDK from a Scheduled Job