Skip to content

Search tags, filters, functions and guides.

JavaScript API / Safety and limits

JavaScript API

Safety and limits

Run templates you didn't write.

Kiln is built to run templates written by other people, such as editors in a CMS or AI agents. A template can only read what you give it, and limits stop templates that would run for too long.

What templates can read

  • Plain objects: their own properties only. Nothing inherited from Object.prototype.
  • Arrays: items by index, and length.
  • Strings: no properties. Use filters such as length instead.
  • Class instances: only the members their class exposes.
  • Never: constructor, prototype or __proto__, even when they are an object's own properties or a Map's keys. Loops and filters skip them too.

Templates can only call functions you registered, the built-ins, macros, their own arrow functions and exposed methods. A function stored in your data can't be called. That includes a then method: Kiln never calls one on the data you pass to render or on your globals, even while it waits for a promise. A thenable that one of your functions or filters returns, such as a query builder, is awaited like a promise.

Limits

Every render has limits. Exceeding one stops the render with an error.

LimitDefaultWhat it counts
maxOutputLength5,000,000Characters of output
maxSteps1,000,000Tags, outputs, loop passes and calls
maxDepth64Nested includes, components, macro calls and layouts
const kiln = createKiln({
	loader,
	limits: { maxSteps: 200_000, maxDepth: 32 },
});

Leave a limit out, or set it to undefined, to keep its default. Each limit must be a number above 0, or createKiln throws a TypeError.

Built-in filters and functions count the items they process, so one call to sort, merge or max can't do unlimited work. in counts the items it searches, and reading a generator or other iterable counts each item it gives. Spreads count the items they copy, and json, urlEncode and attrs count every item they convert. An include or component counts each variable it passes with with. Escaping stops as soon as the escaped text is too long to output, and streamed islands count towards the page's limits.

Pass an AbortSignal as the signal render option to stop a render from outside, for example when the visitor disconnects. The render stops as soon as the signal aborts, even while it waits for one of your async functions. That function carries on unless it listens to the signal too, which it can read with withRender.

The limits count steps, output and nesting, but not the memory a template keeps alive, such as a list holding many long strings. Rely on your platform's memory limits as well. On Cloudflare Workers, the platform's CPU limit is a final backstop.

Your host functions are responsible for bounding concurrent I/O and for timeouts on their own work. Async map and filter start every callback at once, so use shared pools or queues for calls to databases and APIs. See Async in filters.

Other safeguards

  • Output is escaped by default.
  • Templates can't build regular expressions, so they can't cause slow pattern matching.
  • Templates can't compile other templates from strings.
  • Kiln never generates code while rendering. Precompiled templates are JavaScript you build and ship, so only precompile templates you trust.
  • dump outputs nothing unless you turn on debug.
  • A failed render or renderBlock returns no partial output. A stream may already have sent output when it fails.