# 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:

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

const kiln = createKiln({
	loader,
	filters: {
		markdown: (text: string) => safe(renderMarkdown(text)),
	},
});
```

```kiln
<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:

```ts
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);
	}
}
```

```kiln
<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.

```ts
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.
