Skip to content
Console →
Website →
Asking an AI? Paste this URL https://keelson.dev/llms.txt

Files and media

Use the Files SDK or Media SDK to save files that need to survive app restarts and redeployments. For an overview, see Keelson SDK.

Use Files for files your app reads and writes internally, and Media for files displayed in the app or opened by users. Media provides URLs that browsers can open directly, making it suitable for images and attachments.

Files SDKMedia SDK
Main useSave and update files used in internal app processingDisplay images and let users open PDFs or attachments
ExamplesConfiguration JSON, lists of processed URLs, intermediate summary filesProduct photos, application PDFs, user-facing reports
ReadingServer-side code reads through the SDK. No serving URL is providedServer-side code can read files, and browsers can open them by URL
UpdatesOverwrite the same name (key). Aim for roughly one update per key per secondEach newly saved file gets an ID; existing files cannot be overwritten
Maximum file size10 MiB50 MiB

Choose based on how a file is used, not just its format. A CSV used internally for calculations fits Files; a CSV report delivered to users fits Media.

Media URLs are not public links that anyone can open. Only members who can view the app can open them after signing in to Keelson. Neither Files nor Media requires additional keelson.yaml configuration.

Media access is controlled at the app level. It does not automatically enforce per-file permissions, such as allowing users to see only their own attachments. For those cases, consider storing files in Files and having the app check permissions before returning them.

Data written to local files while the app runs does not survive restarts or redeployments. Writing to /tmp or the app directory alone does not preserve files for later use.

Once a Files / Media SDK save completes, the file survives app restarts. No later synchronization step is needed. Use Managed SQLite for customer records, projects, and other data you search and update by field.

Follow Keelson SDK installation to add Files or Media to your app’s dependencies. The examples below run on the server. You can usually ask your AI agent to implement storage by explaining what files you want to save and how they will be used.

Files works well for files that your app saves under a name and updates under that same name. Saved files can be shared between the web app and its scheduled jobs.

The storage name is called a “key,” such as settings.json or reports/2026-08.csv. The main Node.js / Python operations are write to save, read to read, delete to remove, and list to list keys. If a file does not exist, read returns null in Node.js or None in Python.

This example saves and reads settings, lists reports, and deletes an old report.

import * as files from "@keelsonhq/files";
await files.write("settings.json", JSON.stringify({ theme: "dark" }));
const raw = await files.read("settings.json"); // Uint8Array | null
const 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 json
from keelson import files
files.write("settings.json", json.dumps({"theme": "dark"}))
settings = json.loads(files.read("settings.json") or "{}") # read() -> bytes | None
keys = files.list("reports/")
files.delete("reports/2026-07.csv")

When moving from local files, change both reads and writes to use the SDK. Files does not provide serving URLs. To deliver a saved file to users, implement a download handler in your app or use Media.

If multiple processes read and update the same key, one update may overwrite another. Use database transactions for processing status, counters, or other data that needs concurrent updates.

Media is suitable for images displayed in the app and PDFs or attachments users open. Keelson serves saved files through URLs, so your app does not need its own file-serving handler.

  1. Call put in server-side code to save the file.
  2. Store the returned file ID in the database alongside the relevant project, product, or other record.
  3. Get the URL with url(id) and use it in an image or link. When the browser opens the URL, Keelson checks authentication and returns the file.

The following server-side function saves an image. Pass the uploaded image bytes as its argument.

import * as media from "@keelsonhq/media";
async function savePhoto(imageBytes) {
const id = await media.put(imageBytes, {
contentType: "image/png",
filename: "photo.png",
});
// Store id in the database and return url to the frontend.
return { id, url: media.url(id) };
}
from keelson import media
def save_photo(image_bytes):
file_id = media.put(
image_bytes,
content_type="image/png",
filename="photo.png",
)
# Store file_id in the database and return url to the frontend.
return {"id": file_id, "url": media.url(file_id)}

On Keelson, URLs have the form /__keelson/media/<file-id>. For example, set an image’s URL as an <img> element’s src, or a PDF’s URL as a link’s href.

<!-- Example URLs for saved files -->
<img src="/__keelson/media/image-file-id" alt="Product photo" />
<a href="/__keelson/media/pdf-file-id">Open application form</a>

Keelson handles the file read. Knowing the URL is not enough to open it; the user needs permission to view the app.

Saved files cannot be overwritten. To replace a file, save a new one and update the ID in the database. Delete the old file separately when it is no longer needed.

  • Use get(id) to read contents on the server, and stat(id) to check the Content-Type and size.
  • Use exists(id) to check existence. Delete with del(id) in Node.js or delete(id) in Python.
  • If you omit Content-Type, it is inferred from the filename extension. If it cannot be inferred, it defaults to application/octet-stream.

Without Keelson connection settings, both SDKs save files in local folders. These local files are not automatically transferred to Keelson Files / Media when you deploy.

  • Files writes actual files under ./.keelson/files/. Add .keelson/ to .gitignore.
  • Media writes under ./media/ (configurable with MEDIA_DIR).

On Keelson, the platform sets the required environment variables.

In Media local mode, the default URL is /media/<file-id>. To preview files in a browser, add a handler to your development server that serves them. media.url() returns a URL; it does not start a local HTTP server.

The Files and Media SDKs support local mode on Linux, macOS, and Windows for Node.js, Python, and Go.

The Files SDK maps keys directly to local paths. On Windows and macOS filesystems configured as case-insensitive, keys that differ only by case, such as Report and report, cannot coexist. Path-based backends also have a race if another process replaces a checked component with a symlink or junction before the operation completes. Use them for trusted, single-user local development. The production GCS path does not have this limitation.

  • The console and CLI do not provide file listing, downloading, or replacement for app files. Build a file list in your app for users. For Media, create links from saved IDs; for Files, implement a download handler when needed.
  • Store files that exceed the size limits directly in external object storage, such as S3-compatible storage. Outbound connections are unrestricted, except for the SMTP ports.

Files / Media files count toward workspace storage capacity (Plans and limits).

Managed SQLite backups do not include Files / Media file contents. Restoring a database does not restore deleted files.