Template API Reference

All .marko files expose the same API on their default export. These methods are used to generate an HTML string on the server, and to modify the DOM in the browser.

Targeted compilation puts render in server output and mount in browser output, so each build carries the method for its environment.

Template.render(input)

ParameterDefaultDetails
input{}The input object for the template. May also include $global for global state

For use on the server, the .render() API on a Marko template provides an object containing a variety of ways to generate an HTML string. Its first parameter becomes the input available within the template.

Warning

A render result holds a single render and the first consumer takes it. Every consumer after that fails with Cannot read from a consumed render result, thrown, rejected, or raised as a stream error depending on the consumer. Awaiting more than once is the exception, since the first await caches the promise and later ones resolve with the same string.

Async Iterator

The render result contains an async iterator, which allows consumption through a for await statement.

import Template from "./template.marko";

for await (const chunk of Template.render({})) {
  // send the html chunk somewhere.
}

Each iteration yields the HTML flushed since the previous one; a render with no asynchronous content yields a single chunk.

Abandoning the loop with break, return, or a thrown exception aborts a pending render with the error Iterator returned before consumed., and a later pull rejects with it. The iterator's throw(reason) method aborts with the given reason instead.

Pipe

The .pipe() method in the render result object sends the HTML into a NodeJS stream.Writable. Any object with write(chunk) and end() serves as a target, and a flush() method, if present, is called after every chunk to keep a buffering transform such as zlib.createGzip() streaming.

import Template from "./template.marko";
import http from "node:http";

http
  .createServer((req, res) => {
    // Stream rendered html into the server response.
    Template.render({}).pipe(res);
  })
  .listen(3000);

ReadableStream

The .toReadable() method in the render result object returns a WHATWG ReadableStream. This can be used in environments that support web apis, eg in a web worker.

const webHTMLResponse = new Response(Template.render({}).toReadable(), {
  headers: { "content-type": "text/html" },
});

Reading is deferred to the stream's first pull, so wrapping it in a Response that is never read leaves the result unconsumed. Cancelling the stream aborts the render with the cancellation reason.

Thenable

The render result is a thenable, so the .then(), .catch() or .finally() methods return a Promise<string> that resolves with a buffered HTML string. This may be handled implicitly with the await keyword.

const html = await Template.render({});
Note

Awaiting buffers the entire document into a string, opting out of streaming.

toString

The result implements a toString() that returns the buffered html synchronously if possible.

const html = Template.render({}).toString();
Caution

Any async behavior (i.e. an <await> tag) makes this method throw Cannot consume asynchronous render with 'toString' instead of returning partial HTML. An already aborted render, for example through an aborted $global.signal, throws the abort reason.

Template.mount(input, node, position?)

ParameterDefaultDetails
input{}The input object for the template. May also include $global for global state
nodeundefinedA reference to the DOM node where the template will be rendered
position"beforeend"Location to render the template, relative to node. Value follows the Element.insertAdjacentHTML API

For use in the browser/client, The .mount() API on a Marko template builds up a reactive DOM and inserts it at the specified node and position. The input argument becomes the input available within the template.

template.mount({}, document.body); // append to the body.

Or with a position override

template.mount({}, document.body, "afterbegin"); // prepended to the body
Note

Valid values for position are based on insertAdjacentHTML():

  • "beforebegin": Before the element.
  • "afterbegin": Just inside the element, before its first child.
  • "beforeend": Just inside the element, after its last child.
  • "afterend": After the element.

which, if the element is this <p>, can be visualized as

<!-- "beforebegin" -->
<p>
  <!-- "afterbegin" -->
  existing body content
  <!-- "beforeend" (default) -->
</p>
<!-- "afterend" -->

Render Result

The .mount() API returns an object with helpers used to update and destroy the instance of the template and DOM that was built, and to access its return value.

const instance = template.mount({ name: "foo" }, document.body);
Warning

This API is not the recommended way to update/destroy Marko templates. It is primarily intended to be used in exclusively client rendered environments and/or while testing. Instead the reactive system should be used.

instance.update(input)

The .update() method allows providing new input to the instance of the template with a reactive update.

instance.update({ name: "bar" });

This update to the input is applied synchronously. The instance's $global is fixed at mount, so a $global on the update input is stripped and ignored.

instance.destroy()

The .destroy() method causes every $signal to be aborted and runs cleanup for the instance.

instance.destroy();

instance.value

The .value property reflects the tag variable exposed by the template's <return> tag.

color-picker.marko
<let/color="#ff8000">
<input type="color" value:=color>
<return=color valueChange(newColor) {
  color = newColor;
}>
<let/color="#ff8000">
<input type="color" value:=color>
<return=color valueChange(newColor) { color = newColor }>
let/color="#ff8000"
input type="color" value:=color
return=color valueChange(newColor) {
  color = newColor;
}
let/color="#ff8000"
input type="color" value:=color
return=color valueChange(newColor) { color = newColor }
import ColorPicker from "./color-picker.marko";

const instance = ColorPicker.mount({}, document.body);

instance.value; // The currently selected color

When the <return> has an assignable value, assigning to .value updates the template through its valueChange.

instance.value = "#0080ff";

input.$global

When a template is rendered via the render or mount APIs, the input object may specify a $global property which will be stripped off and used as $global within all rendered .marko templates.

Some properties on the $global are picked up by Marko itself and have predefined functionality. Application specific properties sit alongside them, typed by extending Marko.Global.

$global.serializedGlobals

string[] | Record<string, boolean> | undefined

$global stays on the server. Naming a property here also writes its value into the page, which makes it readable as $global from client code such as an event handler.

Template.render({
  $global: {
    locale: "en-GB",
    apiToken: "secret",
    serializedGlobals: ["locale"],
  },
});

Above, $global.locale can be read in the browser and $global.apiToken cannot. An object selects the same properties and suits a list assembled in more than one place, which is how Marko Run exposes it as ctx.serializedGlobals.

serializedGlobals: { locale: true, apiToken: false }

A named property holding undefined is left out.

Warning

Serialized values are written into the HTML and can be read by anyone who loads the page. Secrets belong in properties left off the list.

$global.signal

AbortSignal | undefined

When signal is included in $global, Marko will listen to it and automatically clean up any pending async rendering activity when it is aborted.

This is used to, for example, prevent continued rendering after an incoming request is aborted.

$global.cspNonce

string | undefined

Marko writes this CSP nonce as the nonce attribute on the <script> and <style> elements it renders: the <html-script> and <html-style> tags, the <style> element rendered for a <style> tag with dynamic values, and the inline scripts written to stream and resume the page.

An explicit nonce, written on the element or supplied by a spread, takes precedence over the injected value.

A <script> or <style> rendered in the browser reads cspNonce from the client $global, which holds the properties named in serializedGlobals.

const cspNonce = crypto.randomUUID();

res.setHeader(
  "Content-Security-Policy",
  `script-src 'nonce-${cspNonce}'; style-src 'nonce-${cspNonce}'`,
);

Template.render({
  $global: { cspNonce, serializedGlobals: ["cspNonce"] },
}).pipe(res);

$global.renderId

string | undefined

The renderId isolates one render from every other render sharing a runtime in the same document. It always has a value, "_" by default.

A template with no html, head, or body tag, compiled with the linkAssets compiler option that @marko/vite configures, instead gets a fresh random value on every render() call, so such renders never collide in one document. mount() always defaults to "_".

Set an explicit value when several renders of a page template share a document, so each one resumes against its own data.

Template.render({
  $global: { renderId: "cart" },
});
Warning

renderId and runtimeId become JavaScript identifiers in the inline resume-data scripts, so each must start with a letter or underscore and contain only letters, numbers, and underscores. A UUID, or a hyphenated name such as my-app, is not a valid value.

$global.runtimeId

string | undefined

The runtimeId names the global variable holding the resume data for every render in the document, and defaults to "M". Overriding it isolates multiple copies of Marko sharing a page. It follows the same identifier rule as renderId.

Server and browser builds must agree on the value, so it belongs in the bundler configuration rather than an individual render. @marko/vite accepts a runtimeId option and bakes it into the generated entries, which apply it to $global.runtimeId.


Contributors

Helpful? You can thank these awesome people! You can also edit this doc if you see any issues or want to improve it.