Menu

Config script (microhost.config.js)

Instead of writing microhost.json by hand, you can ship a microhost.config.js at the root of your release. MicroHost runs it once per deploy, takes the object it exports, validates it with the same rules as microhost.json (Manifest), and seals the result into the release. You never write or see the resolved JSON; an administrator can view it read-only ("Effective manifest", GET /admin/api/v1/sites/{id}/manifest).

Use a script when the manifest is repetitive (many routes, a large admin extension), or when routes are derived from your content files. Use microhost.json for small static manifests. If both exist, the script wins and the deploy warns manifest_json_ignored.

Status. Implemented and used by examples/blog. It runs in a separate, deterministic Wasm guest, so the server needs the build guest module; if it is missing, a release that contains microhost.config.js is rejected with config_js_unavailable (releases with JSON or defaults still deploy).

Minimal example

// microhost.config.js
import { defineSite, route } from "microhost";

export default defineSite({
  sdk: ["db"],
  schemaCompat: { min: 1, max: 1 },
  routes: [
    route("/posts", "api/posts.js"),
    route("/posts/:slug", "api/posts.js"),
    route("/api/contact", "api/contact.js", { methods: ["POST"] }),
  ],
  permissionsRequested: { database: true },
});

The build environment

The script runs in a capability-free guest:

microhost exports

import { defineSite, route, defineExtension, field, files } from "microhost";

defineSite(value?)

Returns the manifest object. The default export of microhost.config.js must be this object (a plain object; a function, undefined or a BigInt is not serializable). Accepted keys are those of microhost.json plus two spellings:

Key Notes
sdk List of module names, as in the manifest.
schemaCompat { min, max }. Omitted: the server's release default is used.
static { root, spaFallback } (recorded, not applied when serving; see Manifest).
errorPages { "404": "errors/not-found.html", "5xx": "errors/server.html", _: "errors/page.js" }; see Error pages.
healthCheck { path: "/status" }; see Health check.
jobs { cleanup: { script: "jobs/cleanup.js", schedule: "0 3 * * *" } }; see Scheduled jobs.
routes Shorthand for entrypoints.routes; an array of route(...) values or plain { path, script, methods } objects.
entrypoints The long form { routes: [...] }.
permissionsRequested database, email, storage, secrets, externalHttp, workspaceDatabases.
limitsRequested timeoutMs, memoryMb.
admin An array of defineExtension(...) values (the shorthand), or the long form { schemaVersion: 1, extensions: [...] }.

schemaVersion is set to 1. Any key that microhost.json would reject is rejected here as well. Omitted values get the defaults from Manifest. The convention /api/<name> routing is a runtime rule; it is not expanded into the route list.

route(path, script, { methods }?)

Returns { path, script, methods }. methods defaults to ["GET"], unlike a handwritten JSON route where it is required. Path grammar and precedence are in Routing.

files

Generate routes from content files:

import { defineSite, route, files } from "microhost";

const posts = files.list("content/posts/").filter(path => path.endsWith(".json"));

export default defineSite({
  routes: posts.map(path => route(`/posts/${path.split("/").at(-1).slice(0, -5)}`, "api/post.js")),
  permissionsRequested: { database: true },
});

Routes derived from data that changes at runtime still need a pattern route such as /posts/:slug and a handler; the config only sees files that are part of the release.

defineExtension(value) and field

These build admin extension declarations. defineExtension({ id, label, icon?, queries?, actions?, pages? }) fills defaults for operations:

Property Query default Action default
method GET POST
minRole viewer editor
timeoutMs 5000 5000
write false true
input {} {}
output { kind: "object", fields: {} } same

route is required on every operation (there is no guessed handler), pages defaults to [], and explicit values always win. A contradictory override (for example a GET action) fails validation.

field.string, field.number, field.status, field.date, field.boolean, field.text, field.textarea are shorthands: field.string({ required: true, maxLength: 120 }) returns { type: "string", required: true, maxLength: 120 }, and values: [...] is spelled enum in the output. A field value can be reused in several places (for example one definition in an operation's input and another in output).

The builders do not translate keys per position. They emit their options as given, and each position has its own legal keys, so an illegal key makes the whole extension invalid (it is dropped with a warning). Keep to the allowed keys for the place you use the field: operation input accepts type (string, number, boolean, date), required, minLength, maxLength, minimum, maximum, enum; output fields accept type (string, number, boolean, date, status), maxLength, enum, nullable. Form field entries need id, type and label, so write form fields as plain objects (the blog does). Details in Admin extensions.

Build limits and errors

Limit Value
Wall time 2 seconds
Fuel 50,000,000 units
Guest memory 32 MiB
Serialized manifest 64 KiB
Files listed 1,000
File reads 1 MiB per read and in total

Exceeding any limit, throwing, or producing an invalid manifest fails the deploy; the active release stays unchanged, and nothing is created. Errors are bounded and safe (no host paths or stack traces) and look like code: file:line: message, for example syntax_error: microhost.config.js:7: unexpected token. Codes you can meet: syntax_error, import_error, runtime_error, missing_default_export, not_serializable, output_too_large, read_too_large, memory, fuel, timeout, config_js_unavailable, and invalid_manifest when the output fails manifest validation.

Equivalence and determinism

The builder produces canonical JSON with sorted keys, independent of the order you wrote keys in. Output that is semantically equal gives the same bytes. The extension descriptor hash is the SHA-256 of the canonical validated descriptor, so an equivalent config keeps the hash of an extension that an administrator already enabled. The blog example keeps microhost.json in the repository only as a golden reference for its tests; its ZIP ships just microhost.config.js (and the local admin-pages.js module it imports).

Types

sdk/microhost.d.ts declares defineSite, route, files, BuildSite and BuildRoute for editors. It does not yet declare defineExtension, field, or the sdk property of BuildSite, although they work.