runlot.json
A configuration file placed in the project directory. It records the name, entry point, assets directory, and connected services.
web/apps/cli/src/config.ts and internal/bundle/bundle.go. The docs build generates this content from that source.{
"name": "my-app",
"org": "me",
"main": "src/index.ts",
"assets": "public"
}Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The project name. Used as the first label of the deployment URL. |
org | string | No | The organization slug. The --org option, if specified, takes precedence over this setting. |
main | string | No | The worker's entry point. If not specified, only static assets are deployed. |
assets | string | No | Static assets directory. |
notFound | "404" | "spa" | No | The response for paths not in the assets. With spa, paths without an extension get /index.html (client-side routing). Default is 404. |
runWorkerFirst | boolean | No | Who answers first when the project has both main and assets. The default is true: the worker is the entry point, and static files are not served ahead of it. Set it to false for a framework that builds its own worker and expects its client directory to be served first — React Router, Astro, SvelteKit and Nuxt all do. |
compatibilityDate | string | No | The workerd compatibility date. The format is YYYY-MM-DD. |
routes | string[] | No | The paths this app answers. A publish walks the list to check the app came up, so a page that has to work belongs in it. Absent, it is / alone. A route is a path on this app and starts with /. Nothing here reaches a deploy. |
database | ResourceBlock | boolean | No | Settings for the project's database (env.db), which every project has. None is defined yet. |
storage | ResourceBlock | boolean | No | Settings for file storage (env.storage), which every project has. None is defined yet. |
email | ResourceBlock | boolean | No | Settings for email (env.email), which every project has. None is defined yet. |
ai | ResourceBlock | boolean | No | Settings for AI (env.ai), which every project has. None is defined yet. |
auth | AuthBlock | boolean | No | Settings for login (env.auth), which every project has. None is defined yet. |
framework | "next" | No | The framework adapter. Currently the only supported value is next. |
trustedCertificates | string[] | No | Extra PEM certificate files to trust when this worker makes outbound HTTPS calls. Use it when an origin serves its certificate without the intermediate, which browsers repair on their own but a worker cannot. The system certificate authorities are always kept, so this only adds trust. Requires main. |
triggers | { crons: string[]; names?: Record<string, string> } | No | Scheduled execution. Write a 5-field cron expression in crons, in UTC. See Scheduled execution for details. |
queues | { producers?: QueueProducerConfig[]; consumers?: QueueConsumerConfig[] } | No | Queues. producers binds env.<BINDING>.send to a queue; consumers names the queues this worker's queue() handler drains, with the batch and retry settings. See Queues for details. |
durable_objects | { bindings?: DurableObjectBindingConfig[] } | No | Durable Objects. bindings is a list of {name, class_name}, where name is the env.<NAME> handle and class_name is the class in your worker. The keys are snake_case in this block. See Durable Objects for details. |
migrations | DurableObjectMigrationConfig[] | No | The migration history the Durable Object classes come from. It sits at the top level of the file and is append-only — a class stays created after the binding that reached it is removed. See Durable Objects for details. |
observability | ObservabilityConfig | No | What the platform collects from this worker. errors is Runlot Errors — uncaught exceptions, rejected waitUntil promises, console.error(err) and reports through runlot:errors become error events; it is on by default and {"errors": false} turns it off. logs (enabled, head_sampling_rate, invocation_logs) and traces (enabled, head_sampling_rate) collect each sampled invocation's console lines, exceptions and spans; both are off by default, and the dashboard's settings take precedence. tags (up to 16) are added to every env.metrics point. routes (up to 500, such as /users/:id/orders) name your route templates. browser: false turns the browser agent off. |
tail_consumers | { entrypoint: string }[] | No | Exports of your worker that receive its traces through workerd's tail mechanism, as [{"entrypoint": "MyTail"}]. Each entrypoint must be a named export of the main module. A tail consumer adds CPU to every invocation, and the number allowed depends on the plan. Requires main. |
access | AccessRuleConfig[] | No | Settings that only runlot dev reads |
dev | { database?: string; publicPrefixes?: string[] } | No | Settings only runlot dev reads; nothing here reaches a deploy. database is a PostgreSQL URL for the local env.db, so a team can share one address through the repository, and RUNLOT_DEV_DATABASE_URL takes precedence over it. publicPrefixes are the storage key prefixes env.storage.publicUrl answers for locally — production keeps that list on the server, and a local run has nothing to ask. |
name is required. You must specify at least one of main or assets. Without either, there is no worker or static asset to serve. Projects that specify framework are an exception, since the build process generates the entry point and assets.
Services
Every project has a database, file storage, email, AI and login from the start. In the worker they are env.db, env.storage, env.email, env.ai and env.auth, and none of them needs a line in this file.
database, storage, email, ai and auth are reserved for each service's settings. None is defined yet, so a key inside one of them is refused.
Turning a service off is a command — runlot pg delete, runlot storage delete, runlot email delete, runlot ai delete, runlot auth delete — and it stays off across deploys until the matching on command (runlot storage on, and so on; runlot pg on creates a new, empty database).
Older files may say "database": true. The CLI still reads it and prints a warning: true is no longer needed, and false does not turn anything off.
Bundle manifest
runlot deploy does not upload this configuration file as is. The CLI generates a separate runlot.json manifest in the bundle that references the build output. You do not need to write it yourself, but the validation rules below apply.
| Field | Description |
|---|---|
main | The worker entry module included in the bundle. If omitted, an entry point that serves only static assets is added automatically. |
assets | The asset directory included in the bundle. / and extensionless paths return index.html, and Content-Type is determined by the file extension. |
notFound | 404 (default) or spa. With spa, extensionless paths return /index.html. |
compatibilityDate | The format is YYYY-MM-DD. If not specified, 2024-09-23 is used. |
compatibilityFlags | An allowlist-based compatibility flag. Currently the only available value is nodejs_compat. |
modules | A list of additional modules. Only the wasm type is supported. |
framework | The framework adapter. Currently the only supported value is next. |
triggers | A scheduled run. crons is a 5-field cron expression (UTC), parsed at deploy time, and invalid ones are rejected. main is required. |
queues | Queues. producers is a list of {binding, queue}; consumers is a list of {queue, maxBatchSize, maxBatchTimeout, maxRetries, deadLetterQueue}. Up to 5 distinct names per project; validated at deploy time. main is required, and the project's database must not have been deleted. |
trustedCertificates | Certificate files carried in the bundle under certs/. The CLI copies the files you named in your configuration to that directory. Each one is parsed at deploy time, and an expired certificate is rejected. main is required. |
observability | errors, browser, logs, traces, tags and routes, copied from your configuration; an empty block is left out. Sampling rates must be greater than 0 and at most 1; tag keys are lower case and cannot be a platform tag such as env or route; a route starts with / and its segments are literals, :name or a final *. Runlot Errors is on unless errors is false. |
tail_consumers | Up to 8 {entrypoint} entries, each an export name of the main module that is not a Durable Object class; the plan's limit is checked at deploy. main is required. |
The worker's source map is carried as worker/index.js.map beside the worker. It is never served and is not a module; it is how error stacks are mapped back to your source.
compatibilityFlags uses an allow list. No manifest should be able to enable a flag that grants user code process-wide control. If the bundle imports Node built-in modules, it needs nodejs_compat, so the CLI sets it automatically.
You can include WASM only through the modules field. workerd does not allow compiling by calling new WebAssembly.Module(bytes) inside the bundle. The Prisma query compiler is affected by this restriction.
Bundle limits
| Item | Limit |
|---|---|
| Compressed upload size | 64 << 20 |
| Total size after decompression | 512 << 20 |
| Size of a single file | 64 << 20 |
| Number of files | 20_000 |
The bundle can contain only regular files. Symbolic links, paths that point outside the project root, and duplicate entries are all rejected.