Output handles — a named projection as a value that knows its address and can render itself.
A content type declares outputs — named projections of the same
content: a document (the bare URL), a fragment (.htmx), data (.json),
a PDF. From a template each is a handle: a value that knows its address
and can render itself.
cms.asset.output('card') one handle by output name (null if the type has no such output)
cms.asset.outputs every declared output as handles, in declaration order
row.output('card') the same accessor on any query row or child
handle
├─ name the output's name — the `--` address token
├─ format html · json · pdf …
├─ kind document · fragment · data · null
├─ url the projection's address (`{path}--card.html`, `{path}.json`, …)
└─ html the projection's markup, rendered inline — null where inline render is unavailable
url and html describe the same render: the address is the htmx or
wire transport of what html inlines. A handle stringifies to its markup,
so <%- row.output('card') %> works, but .html is the spelled idiom.
<div class="mk-grid">
<% cms.assets.find({ type: 'product', under: cms.asset.path }).forEach(function (row) { %>
<%- row.output('card').html %>
<% }) %>
</div>
<% var pdf = cms.asset.output('brochure') %>
<% if (pdf) { %><a href="<%= pdf.url %>">Download the brochure</a><% } %>
The first snippet is a listing that delegates each card to the product type's own renderer — the page never learns what a product looks like. Swap the type's fragment renderer and every listing follows.
An output marked internal never enumerates on a public render, and a record with no published projection has no representation at all — the same gate the served address applies. Depth is capped: an inline render that renders an inline render stops at three levels.
-- addressing