
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/astro
Advanced tools
Read published Mapled content in an Astro site — a server-side client with a tag cache, the publish webhook (purge or deploy hook), preview and live updates.
Read published Mapled content in an Astro site — a server-side client with a tag cache, a publish webhook that purges it (or rebuilds a static site), preview of drafts, and live updates for the tabs that are already open.
Mapled is a hosted headless CMS built for sites created with AI. Your agent (or you) models collections and fills in content; this package is how an Astro site reads what's published. It is @mapled/client with Astro's idioms around it: the same reads, the same types from mapled types generate.
npm install @mapled/astro
Grab the delivery key in Mapled: project settings (the gear in the project's header) → Integrations → Your site, and put it in your site's env. The adapter reads it on the server, so no PUBLIC_ prefix is needed (the delivery key is the one key that may be public; the webhook secret and a server key never are):
MAPLED_KEY=mk_live_…
MAPLED_WEBHOOK_SECRET=whsec_…
Two places hold a site's env, and the adapter looks in both. A .env file (and astro dev) fills the site's own import.meta.env, when the site is built — the snippets below hand those values over. A host's environment variables are the server's process.env, which the adapter reads itself when a request arrives — so a site built without a value still finds it where it runs.
Pages that read Mapled render on demand: output: "server" in astro.config.mjs (with an adapter), or export const prerender = false on those pages. A static build works too — the webhook then asks your host to rebuild (Refresh on publish).
One client for the whole site, typed by the schema npx @mapled/cli types generate writes:
// src/lib/mapled.ts
import { mapled } from "@mapled/astro";
import type { MapledSchema } from "../mapled-types";
/** Pass `Astro` (or an endpoint's context) so the request's preview cookie is seen; without it, published content only. */
export const cms = (from?: Request | { request: Request }) => mapled<MapledSchema>(from, { key: import.meta.env.MAPLED_KEY });
mapled() takes the key it is handed — the one of a .env file, named as import.meta.env.MAPLED_KEY so the build keeps it — else MAPLED_KEY of the server's environment (apiUrl and MAPLED_API_URL likewise). In a page's frontmatter:
---
// src/pages/index.astro
import { cms } from "../lib/mapled";
const home = await cms(Astro).getSingle("homepage");
const { records: posts } = await cms(Astro).getRecords("posts", { sort: "-date", limit: 10 });
---
<h1>{home?.data.headline}</h1>
<ul>{posts.map((p) => <li><a href={`/blog/${p.data.slug}`}>{p.data.title}</a></li>)}</ul>
---
// src/pages/blog/[slug].astro
export const prerender = false;
import { cms } from "../../lib/mapled";
const post = await cms(Astro).getRecordBySlug("posts", Astro.params.slug!);
if (!post) return Astro.redirect("/404");
---
<article><h1>{post.data.title}</h1></article>
The reads are those of @mapled/client: getRecords(collection, { filter, sort, limit, offset, fields, expand, search, locale }), getRecord, getRecordBySlug, getSingle, getRelease, isPreview. Each published answer is kept in the adapter's cache — in the server process, under the tags mapled and mapled:<collection> — for an hour, or for as long as you say:
// src/lib/reads.ts
import { cms } from "./mapled";
export async function homepage() {
// kept until the next publish purges it; `revalidate: 60` keeps it a minute, `revalidate: 0` never
return cms().getSingle("homepage", { revalidate: false, tags: ["layout"] });
}
A page and its layout asking for the same record during one render make one request. A prerendered page reads published content: mapled(Astro) leaves its request unread (Astro 5+ marks it isPrerendered), so a static build stays quiet. The cache is this process's: a long-running server (the Node adapter) holds one, serverless functions hold one each — keep revalidate short there, or build statically. createClient({ key, apiUrl, draft, cache: false }) makes a client by hand, with the cache off when you say so.
rich_text values are Markdown — render them with a Markdown component, never as raw HTML. Image values are asset ids: assetUrl(id, { width, format }) gives the URL of a resized variant.
// src/lib/images.ts
import { assetUrl } from "@mapled/astro";
export const cover = (id: string) => assetUrl(id, { width: 1280, format: "webp" });
Mount the webhook and point the project at it (Integrations → Your site → Revalidation webhook, or let your agent call configure_revalidation). Every publish is signed with the project's secret; the handler purges the cache, so the next render reads the new release:
// src/pages/api/mapled/revalidate.ts
import { createRevalidateHandler } from "@mapled/astro/server";
export const prerender = false;
export const POST = createRevalidateHandler({ secret: import.meta.env.MAPLED_WEBHOOK_SECRET });
import.meta.env holds the secret of a .env file; on a site built without it the handler reads MAPLED_WEBHOOK_SECRET from the server's environment when the first webhook arrives. Until either has it, it answers 503 and Mapled retries.
A statically built site can't re-render on its own: set MAPLED_DEPLOY_HOOK_URL to your host's build hook (Vercel, Netlify, Cloudflare Pages…) and the same handler asks it for a build. The route itself still runs on a server — output: "static" with an adapter prerenders the pages and keeps the routes marked prerender = false as functions; a host with no functions at all has nothing to take the webhook. The handler calls the hook once per publish — a retry of the same publish doesn't build twice, and a hook that refuses fails the delivery so Mapled retries. The URL is read from the environment on the first webhook and never written anywhere. A handler of your own can verify a request with verifySignature(secret, body, header), drop what it needs with purge(["mapled:posts"]), and answer with the header x-mapled-cache: cacheMark() so live updates follow it.
Mapled opens the site through a one-time link to the preview route, which trades it for a draft session, keeps the session in the mapled_draft cookie (HttpOnly, Secure, SameSite=Lax) and redirects into the site. From then on mapled(Astro) reads drafts for that visitor, uncached, until the exit route clears the cookie — published content for everyone else.
// src/pages/api/mapled/preview.ts
import { createPreviewHandler } from "@mapled/astro/server";
export const prerender = false;
export const GET = createPreviewHandler();
(It trades the link at MAPLED_API_URL when the site's env has one — the API the reads go to.)
// src/pages/api/mapled/preview/exit.ts
import { createExitPreviewHandler } from "@mapled/astro/server";
export const prerender = false;
export const GET = createExitPreviewHandler();
A draft page must never be cached by your host or a CDN. The middleware marks it private, no-store while the cookie is there:
// src/middleware.ts
export { onRequest } from "@mapled/astro/middleware";
(Next to middleware of your own: sequence(onRequest, yours).) draftOf(request) answers the draft session of a request, or null — what mapled(Astro) uses; pass it as draft to createClient when you build the client yourself. Preview links point at /api/mapled/preview by default; both routes redirect only to a path of the site itself.
Open tabs can follow Publish. Mount the release route and render the component once, in the layout — it asks the route every few seconds and reloads the page when a publish lands:
// src/pages/api/mapled/release.ts
import { createReleaseHandler } from "@mapled/astro/server";
export const prerender = false;
export const GET = createReleaseHandler();
---
// src/layouts/Layout.astro
import MapledLive from "@mapled/astro/MapledLive.astro";
---
<html><body><slot /><MapledLive /></body></html>
The route names the release this server last acknowledged through the webhook — never one its cache hasn't been purged for — so a tab that reloads on it renders the new content, not the old one again. It keeps the delivery key on the server and never asks Mapled. A site without a server (a static build) passes the delivery key instead, <MapledLive key={import.meta.env.PUBLIC_MAPLED_KEY} /> — the one key that is safe in a page; a PUBLIC_ variable for it, never for a secret — and the tab asks Mapled directly, once a minute. From a script of your own: startLive({ endpoint }) (or { key }) from @mapled/astro/live reloads on a publish and returns a stop function; mountLive() is what the component's script calls; watchRelease is the client's own, for anything else.
Operational collections — content the site's backend writes, which takes effect without a publish — are createAppClient({ serverKey }) from the same package, server-side only. Its calls, Site Identity and the data of signed-in people are documented in @mapled/client.
// src/pages/api/orders.ts
import { createAppClient } from "@mapled/astro";
export const prerender = false;
// the .env file's value, else the server's own environment — a build made without the key still finds it where it runs
const db = createAppClient({ serverKey: import.meta.env.MAPLED_SERVER_KEY ?? process.env.MAPLED_SERVER_KEY! });
export async function POST({ request }: { request: Request }) {
const body = (await request.json()) as { email: string };
const order = await db.create("orders", { email: body.email });
return Response.json({ id: order.id }, { status: 201 });
}
| Import | What it is |
|---|---|
mapled(from?, { key, apiUrl }?) from @mapled/astro | The site's client from the environment; with Astro, a request or an endpoint's context it reads drafts under the preview cookie |
createClient({ key, apiUrl, release, locale, draft, fetch, cache }) | A client by hand |
draftOf(from) | The draft session a request carries, or null |
assetUrl, createAppClient, MapledError, watchRelease | Re-exported from @mapled/client |
createRevalidateHandler({ secret, tags, deployHook, fetch }) from @mapled/astro/server | The publish webhook: purges the cache, calls the deploy hook, answers with x-mapled-cache; without a secret, MAPLED_WEBHOOK_SECRET of the server |
createPreviewHandler({ apiUrl, appUrl }), createExitPreviewHandler() | Into preview and out of it |
createReleaseHandler({ ttl }) | The release route open tabs ask |
verifySignature, purge, cacheMark | For a webhook handler of your own |
onRequest from @mapled/astro/middleware | A draft page answers private, no-store |
startLive, mountLive from @mapled/astro/live; MapledLive.astro | Open tabs follow Publish |
Errors are MapledError with the API's status and code. Every handler takes a Request or Astro's context ({ request }) and returns a Response, so it is an Astro endpoint as it is.
FAQs
Read published Mapled content in an Astro site — a server-side client with a tag cache, the publish webhook (purge or deploy hook), preview and live updates.
The npm package @mapled/astro receives a total of 9 weekly downloads. As such, @mapled/astro popularity was classified as not popular.
We found that @mapled/astro 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.