
Security News
Rust 1.100 Will Add Dependency Cooldowns to Cargo
Rust 1.100 will add opt-in dependency cooldowns to Cargo, giving security scanners and maintainers time to catch malicious crate releases.
@mapled/vanilla
Advanced tools
The browser runtime of Mapled, the hosted headless CMS for sites built with AI — published content in a plain HTML site: one script tag and data-mapled attributes, no framework and no build step.
Mapled — the hosted headless CMS for sites built with AI — in a plain HTML site: one script tag, data-mapled-* attributes on the elements it should fill, no framework and no build step. The HTML you already have stays in the page as the fallback; editors change the words, pictures and lists in Mapled and press Publish.
@mapled/react, a Next.js site @mapled/next, any other JavaScript @mapled/client — this package is for pages that have no JavaScript of their own to speak of.<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<title data-mapled-bind="home.headline">Studio Nord</title>
<script src="https://cdn.jsdelivr.net/npm/@mapled/vanilla@0.3.0/dist/mapled.js" data-mapled-key="mk_live_…" defer></script>
</head>
<body>
<h1 data-mapled-bind="home.headline">Websites, built by hand</h1>
</body>
</html>
home.headline is the field headline of the single home. When the page loads, the script reads the release that is live and puts the headline into both elements; until then — and whenever Mapled can't be reached — the page shows what you wrote.
The key is the project's delivery key (Mapled: Integrations → Your site). It reads published content of collections with access Public and nothing else, so it is the one key that belongs in a page. The script refuses to start with anything else: a server key or a signing secret never goes into HTML.
Pin the version. A version on a CDN never changes; an address without one (…/@mapled/vanilla/dist/mapled.js, @latest) runs whatever is published next. Better still, add integrity="sha384-…" crossorigin="anonymous" — the value ships in the package as dist/mapled.js.sha384 — or copy dist/mapled.js next to your pages and load it from there. The file is about 12 KB over the wire and has no dependencies.
<!-- landing.html -->
<header>
<h1 data-mapled-bind="home.headline">Websites, built by hand</h1>
<!-- rich text: Markdown from Mapled, built into elements -->
<div data-mapled-bind="home.intro" data-mapled-format="markdown"><p>We make small sites.</p></div>
<!-- a picture (an asset of the project) at the width the layout needs, and its description -->
<img data-mapled-bind="home.hero" data-mapled-width="1280" data-mapled-bind-alt="home.hero-alt" src="/img/hero.jpg" alt="The studio">
<!-- an attribute: href, src, alt, title, aria-label, placeholder, datetime -->
<a data-mapled-bind="home.cta-label" data-mapled-bind-href="home.cta-link" href="/contact.html">Get in touch</a>
<!-- dates and numbers in the words of the page's language -->
<p>Since <time data-mapled-bind="home.founded" data-mapled-format="date" data-mapled-bind-datetime="home.founded">2019</time></p>
<!-- shown only while the field has a value (data-mapled-hide is the opposite) -->
<p data-mapled-show="home.notice" data-mapled-bind="home.notice" hidden></p>
</header>
<!-- a repeatable group of the single: the template once per item -->
<ul data-mapled-each="home.features">
<template>
<li><strong data-mapled-bind="title"></strong> <span data-mapled-bind="text"></span></li>
</template>
<li><strong>Designed</strong> by people</li>
</ul>
<!-- a components field: the blocks in their order, each from the template of its type -->
<main data-mapled-each="home.sections" data-mapled-expand="sections.picks">
<template data-mapled-type="hero">
<section class="hero">
<h2 data-mapled-bind="heading"></h2>
<a data-mapled-bind="button-label" data-mapled-bind-href="button-link"></a>
</section>
</template>
<template data-mapled-type="text">
<section data-mapled-bind="body" data-mapled-format="markdown"></section>
</template>
<!-- a block's relation: the posts it picks, read along by data-mapled-expand -->
<template data-mapled-type="featured">
<section>
<h2 data-mapled-bind="heading"></h2>
<ul data-mapled-each="picks">
<template>
<li><a data-mapled-link="/post.html?slug={slug}" data-mapled-bind="title"></a></li>
</template>
</ul>
</section>
</template>
<!-- every other type of block -->
<template>
<section><h2 data-mapled-bind="heading"></h2></section>
</template>
<section><h2>Studio Nord</h2></section>
</main>
<!-- a collection: the template once per record -->
<ul data-mapled-list="posts" data-mapled-sort="-date" data-mapled-limit="5" data-mapled-filter="featured=true">
<template>
<li>
<a data-mapled-link="/post.html?slug={slug}" data-mapled-bind="title"></a>
<time data-mapled-bind="date" data-mapled-format="date"></time>
</li>
</template>
<li>Journal entries appear here.</li>
<li data-mapled-empty hidden>Nothing published yet.</li>
</ul>
home.headline, home.seo.title for a field inside a group. Inside data-mapled-list, data-mapled-each and data-mapled-record it starts at the record: title, author.name for a linked record — name the relation in data-mapled-expand="author" on the list or the record element (on the element itself for a single). data-mapled-each="related" repeats the records a relation the read expanded links to, the template once per record.data-mapled-bind fills the element with text — on an <img>, with the picture. data-mapled-format says how: markdown for a rich text field, date, datetime and number for those, written the way the nearest lang attribute says.data-mapled-bind-… fills an attribute: href (on a, area), src (img, video, audio, source), alt, title, aria-label, placeholder, datetime. Nothing else — no style, no class, no on….data-mapled-list reads a collection: data-mapled-sort="-date", data-mapled-limit (1–100), data-mapled-filter="featured=true; category=news". Whatever else sits inside the element is its fallback and leaves when records arrive; an element with data-mapled-empty shows when there are none.data-mapled-link builds an address from a template. Every {field} is percent-encoded before it goes in, so a value fills its place and nothing more.data-mapled-each repeats them: each block takes the <template data-mapled-type="…"> of its type (the type's key, as MAPLED.md lists it), or else the <template> without a type. A block the page has neither for is left out, and the report says no-template. Inside, paths start at the block: heading, button-link. A block's group — a pricing block's tiers — repeats inside the block's template with a data-mapled-each of its own, data-mapled-each="tiers", and its paths start at the item: name, price. A block's relation — the posts a featured block picks — comes along when the read names the field and the sub-field, data-mapled-expand="sections.picks" on the each (on the list or the record element for a collection's records): inside the block's template data-mapled-each="picks" repeats the linked records, their paths starting at the record (title, slug), and lead.title reads a relation to one record. A JSON field's objects that carry a _type pick a template the same way; a list's records, a group's items and a relation's records have no type and take the <template> without one.<!-- post.html -->
<article data-mapled-record="posts" data-mapled-slug-param="slug">
<h1 data-mapled-bind="title">Journal</h1>
<div data-mapled-bind="body" data-mapled-format="markdown"></div>
</article>
post.html?slug=hello-world shows the post whose slug field is hello-world. data-mapled-slug="about-us" names the record in the page instead. Tell Mapled the page's address as /post.html?slug=[slug] in the site manifest (with a route_param binding on the slug field) — npx @mapled/cli scan --write writes it from the attributes — and Preview in the record's editor opens the right page.
<!-- contact.html -->
<form data-mapled-form="contact">
<input name="name" required>
<input name="email" type="email" required>
<input name="_gotcha" hidden tabindex="-1" autocomplete="off">
<label><input type="checkbox" name="consent" data-mapled-consent required> I agree to be contacted about my request.</label>
<button>Send</button>
<p data-mapled-form-sent hidden>Thanks — we'll write back.</p>
<p data-mapled-form-failed hidden></p>
</form>
contact is the form's key in Mapled (Forms → New form, npx @mapled/cli forms create, or your AI agent's create_form). The script posts the fields, shows one of the two messages — an empty data-mapled-form-failed gets Mapled's own words — and sets data-mapled-form-state on the form to sending, sent or failed. Keep the hidden _gotcha input: people leave it empty, and what bots put there is filed as spam. The label of a ticked data-mapled-consent checkbox is kept with the submission. Submissions never wait for Publish.
| The report says | What happened | What the page shows |
|---|---|---|
missing | No such single, record or collection in the release — or nothing in the address to find a record by | The HTML you wrote |
empty | The record is there, a value at this path isn't: an editor left the field empty — or the key is misspelled | Nothing: the text is emptied, a picture is hidden, an attribute is removed (alt becomes "") |
failed | The read didn't come back | The HTML you wrote |
unsafe-url | An address that isn't http:, https: — or mailto:, tel: in a link — or that carries credentials; a value that can't go into a link template | The HTML you wrote |
type | A value that has no text: a group bound as text, a word where a date goes, a relation an each repeats that the read didn't expand | The HTML you wrote |
refused-element | <script>, <style>, <iframe>, <object>, <embed>, <base>, <link>, <meta>, <template>, <noscript> are never written into | The HTML you wrote |
unsupported | An attribute Mapled doesn't read — data-mapled-bind-style, a misspelled name — or a list without a <template> | The HTML you wrote |
bad-path | A path, a key or an option that isn't one | The HTML you wrote |
no-template | A block of a type the page has no <template data-mapled-type> for, and no <template> without a type to take it | Nothing for that block; the other blocks show |
<!-- debug.html -->
<script>
document.addEventListener("mapled:ready", (event) => {
const { state, release, preview, bound, issues } = event.detail;
console.table(issues); // path, reason, on — never a value
});
</script>
state is ready, unpublished (nothing is published yet) or fallback (Mapled couldn't be reached — or preview just ended and what is published is on its way); the same word is on <html data-mapled-state="…">, loading before the first fill — a hook for CSS, set by the script only, so a page whose script never loaded hides nothing. mapled:update follows every later fill. In the console, Mapled.page.report is the last report and Mapled.page.refresh() fills again — call it after your own script adds elements with attributes. Every fill starts from the HTML you wrote, so a value that stops fitting gives its place back to your fallback, not to the previous release.
Nothing to set up in the page. Preview in Mapled opens the page with a one-time token — https://your-site/post.html?slug=hello&mapled-preview=mpv_… — and the script, before its first read, takes the token out of the address bar, trades it for a draft session and keeps the session for the tab. The page shows drafts until the tab closes or the session ends, and <html> carries data-mapled-preview meanwhile; a form posted from that tab is filed as a test submission — not counted, nobody notified, no workflow started:
<!-- preview-bar.html -->
<p class="preview-bar">You are looking at a preview. <button data-mapled-end-preview>Show what is published</button></p>
<style>
html:not([data-mapled-preview]) .preview-bar { display: none; }
</style>
Mapled has to know where the site lives and that it renders in the browser: the site manifest's framework is "plain-html" (your AI agent's push_site_manifest, or npx @mapled/cli scan --write and npx @mapled/cli bindings push — the scan reads the pages' attributes into the manifest) — or the project was created as plain HTML — and Production domain in Project settings holds the site's public address (an agent sets it with set_site_url). npx @mapled/cli doctor checks all of it, the script tag's version included.
A publish shows when the page is loaded again. To follow publishes while a page is open, add data-mapled-live to the script tag — data-mapled-live="30" for the seconds between checks (60 by default, 15 at least). A check is a request of the delivery key's budget (1,200 a minute), made only while the tab is visible; a quiet one costs next to nothing. The page knows the release it filled from, and the watch starts there: a publish that landed between the fill and the first check, or while a tab opened in the background waited to be shown, fills the page at once — and a page left as written because Mapled was out of reach fills itself from the first release the watch hears.
An editor changes what the site says, never what it runs:
http: and https:, plus mailto: and tel: in links — read by the browser's own URL parser, so java script: in any disguise is refused;<script>, <style>, <iframe> and the like;eval and no HTML-parsing call, and works under a strict Content-Security-Policy and Trusted Types. A site with a CSP adds connect-src https://api.mapled.io and img-src https://api.mapled.io https://media.mapled.io.Don't undo this with your own innerHTML = record.data.… — if you need content in JavaScript, read it with @mapled/client and put it in as text.
// src/main.ts
import { mount } from "@mapled/vanilla";
const page = mount({ key: "mk_live_…", live: true });
const report = await page.ready;
if (report.state === "fallback") console.info("Showing the page as written.");
mount({ key, apiUrl?, locale?, root?, live?, fetch? }) answers { ready, report, refresh(), endPreview(), stop(), client } — client is the @mapled/client the page reads through, for reads of your own. The script tag does the same from its attributes (data-mapled-key, data-mapled-api, data-mapled-live, data-mapled-locale) and leaves the result at Mapled.page; a tag without a key only defines Mapled.mount. VERSION is the package's version, Mapled.version in a page.
| Attribute | On | Says |
|---|---|---|
data-mapled-key, data-mapled-api, data-mapled-live, data-mapled-locale | the script tag | the delivery key; another API origin; follow publishes; read in one of the project's languages (data-mapled-locale="ru" — a translated field shows in it, or in the first language of the project's fallback chain that has a value) |
data-mapled-bind | an element | the path whose value fills it |
data-mapled-format | with data-mapled-bind | text (default), markdown, date, datetime, number |
data-mapled-width | an <img> | the width of the picture to ask for, 16–4096 |
data-mapled-bind-href, -src, -alt, -title, -aria-label, -placeholder, -datetime | an element | the path whose value fills the attribute |
data-mapled-show, data-mapled-hide | an element | shown (hidden) while the path has a value; false counts as none |
data-mapled-link | a, area | an address template with {field} places |
data-mapled-list | an element with a <template> | the collection to repeat the template for |
data-mapled-sort, data-mapled-limit, data-mapled-filter | with data-mapled-list | the order, how many, which |
data-mapled-each | an element with a <template> | the path of a group, a components field or an expanded relation to repeat the template for |
data-mapled-type | a <template> of an each | the type of block it shows |
data-mapled-empty | inside a list or an each | shown when there is nothing to repeat |
data-mapled-record | an element | the collection of the one record its content shows |
data-mapled-slug, data-mapled-slug-param | with data-mapled-record | the record's slug; the query parameter that holds it |
data-mapled-expand | a list, a record, a bound element, an each | relation fields to read along — sections.picks for a relation inside the blocks of sections |
data-mapled-form | a <form> | the key of the Mapled form it posts to |
data-mapled-consent, data-mapled-form-sent, data-mapled-form-failed, data-mapled-end-preview | inside a form; anywhere | the consent checkbox; the two outcomes; a way out of preview |
data-mapled-state, data-mapled-preview, data-mapled-form-state | set by the script | <html>: how the fill went, whether drafts are shown; a form: sending, sent, failed |
FAQs
The browser runtime of Mapled, the hosted headless CMS for sites built with AI — published content in a plain HTML site: one script tag and data-mapled attributes, no framework and no build step.
The npm package @mapled/vanilla receives a total of 167 weekly downloads. As such, @mapled/vanilla popularity was classified as not popular.
We found that @mapled/vanilla demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Rust 1.100 will add opt-in dependency cooldowns to Cargo, giving security scanners and maintainers time to catch malicious crate releases.

Security News
Cloudflare is acquiring Deno’s team, which will provide another year of bug fixes and security updates before ending support for the runtime.

Security News
Anthropic’s free OSS Scanner uses Mythos and other Claude models to find open source vulnerabilities and send maintainers reports with proposed fixes.