Data files
ctx.data gives functions and cron jobs a simple,
file-native persistence layer under /data/. It’s the fastest way to store
settings, drafts, text, and simple event logs without provisioning a database.
Use Data files when your application state naturally belongs in a text, JSON, or JSONL file and writes are infrequent. For structured application data, queries, relationships, counters, or concurrent updates, use a Database.
Data files are ordinary site files: visible to authenticated tooling, but never served on public hosted URLs.
Storage usage and billing
File storage is measured across your account by file size and time retained.
Static files, /data files, function source, and cron source count, including
files on archived sites. Deleting a file stops its contribution.
Historical revisions are currently excluded from customer storage usage.
The retention and pricing policy for retained history is under review.
Free includes 1 GB; monthly Builder includes 25 GB. New Builder subscriptions include storage overage billing. For activated subscriptions, overage costs $0.030666… per GB-month after the included allowance, billed with your subscription. One GB is 1,073,741,824 bytes; a month is your actual subscription billing period. Request costs are included in this storage rate.
The allowance is applied once to the period’s size × time usage. For example, 100 GB retained for an entire Builder billing period produces 75 GB-month of overage, or $2.30 before taxes and discounts. Activation starts measurement from that point forward; earlier usage is not backfilled.
See Usage → File storage in the dashboard for stored bytes, included storage, estimated overage, and whether billing is active. Existing subscriptions without storage billing attached remain inactive.
Helpers
Inside a function or cron handler, reach for ctx.data:
// Text
await ctx.data.read('/data/notes.txt')
await ctx.data.write('/data/notes.txt', 'hello')
await ctx.data.delete('/data/notes.txt')
await ctx.data.info('/data/notes.txt')
// JSON
await ctx.data.readJson('/data/settings.json')
await ctx.data.writeJson('/data/settings.json', { theme: 'dark' })
// JSONL (append-only logs)
await ctx.data.appendJsonl('/data/events.jsonl', { type: 'created' })
await ctx.data.tailJsonl('/data/events.jsonl', 100)
Extension rules
The structured helpers require matching file extensions:
| Helper | Required extension |
|---|---|
readJson, writeJson |
.json |
appendJsonl, tailJsonl |
.jsonl |
read, write |
any |
Patterns
JSON document
A single evolving document — settings, a small record, aggregate state:
export async function handler(req: Request, ctx: InterfoldContext) {
const settings = await ctx.data
.readJson('/data/settings.json')
.catch(() => ({ theme: 'light' }))
return Response.json(settings)
}
JSONL event log
Append-only records — form submissions, analytics, audit trails:
export async function handler(req: Request, ctx: InterfoldContext) {
await ctx.data.appendJsonl('/data/signups.jsonl', {
email: (await req.json()).email,
at: new Date().toISOString(),
})
const recent = await ctx.data.tailJsonl('/data/signups.jsonl', 20)
return Response.json({ recent })
}
Concurrency
Data file writes create file revisions. The Files API and MCP can reject a
stale replacement when given the current version ID. ctx.data uses version
checks for its read-modify-write operations. For shared state needing
transactions across records, use a Database. For logs, drafts,
settings, and other low-contention state, ctx.data is ideal.
Inspecting data
Data lives at /data/* and is not a public URL. Read it with authenticated
tooling:
interfold files ls /data --site <site-id>
interfold files cat /data/settings.json --site <site-id>