Skip to content

Search tags, filters, functions and guides.

JavaScript API / Safe markup and classes

JavaScript API

Safe markup and classes

Return trusted HTML and expose class instances.

Kiln escapes values from your code by default. When a function or filter returns HTML you trust, mark it as safe. When you pass class instances to templates, choose which members templates can use.

Returning safe HTML

Wrap a string in safe() to output it without escaping:

import { createKiln, safe } from "@lucidkiln/core";

const kiln = createKiln({
	loader,
	filters: {
		markdown: (text: string) => safe(renderMarkdown(text)),
	},
});
<article>{{ post.body|markdown }}</article>

Only mark output safe when your code produced it or cleaned it. The second argument sets the strategy it is safe for, such as safe(value, "js"). It defaults to "html".

isSafe(value) checks whether a value is safe markup. Safe values are instances of the Markup class, and convert to strings as normal.

Class instances

Templates can read plain objects and arrays freely. A class instance exposes nothing until its class opts in, so templates can't reach methods or internals you didn't intend. List the members templates may use with the expose symbol:

import { expose } from "@lucidkiln/core";

class Post {
	static [expose] = ["title", "url", "excerpt"] as const;

	constructor(private data: PostData) {}

	get title() {
		return this.data.title;
	}

	get url() {
		return `/blog/${this.data.slug}/`;
	}

	excerpt(length: number) {
		return this.data.body.slice(0, length);
	}
}
<a href="{{ post.url }}">{{ post.title }}</a>
<p>{{ post.excerpt(120) }}</p>
  • Listed getters and fields can be read. Listed methods can be called.
  • expose is read from the class and its parent classes. In a base class meant to be extended, type the list as readonly string[] rather than as const.
  • List toString to let templates print the object. It must return a string. Kiln calls it directly, never Symbol.toPrimitive or valueOf.
  • Dates, sets and maps work without opting in.

Dynamic fields

For fields you don't know in advance, such as custom fields from a CMS, add a lookup method. It answers any name not in expose. Return undefined for a missing field.

import { expose, lookup } from "@lucidkiln/core";

class Post {
	static [expose] = ["title"] as const;

	[lookup](name: string): unknown {
		return this.fields.get(name);
	}
}

Names such as constructor, prototype and __proto__ stay blocked whatever expose and lookup say.