SDKs
| Package | What |
|---|---|
@blokcms/client | Typed Delivery API client, link and image URL helpers, editor ticket verification, createFormController. |
@blokcms/react | Blocks, RichText, BlokImage, BlokForm, editable(), editableField(), editableList(), useLiveEntry, useLiveGlobals, useBlokForm. @blokcms/react/server: the rendering for React Server Components. |
@blokcms/vue | BlokBlocks, BlokRichText, BlokImage, v-editable, editableField(), editableList(), useLiveEntry, useLiveGlobals. |
@blokcms/svelte | Svelte 5: BlokBlocks, BlokRichText, BlokImage, use:editable, editableFieldAttrs(), editableListAttrs(), liveEntry and liveGlobals stores. |
@blokcms/richtext | Rich text to safe HTML or plain text, for any framework. |
@blokcms/bridge | The visual editor bridge (also hosted at https://docs.blokcms.app/bridge/v1.js). |
Client
const blok = createClient({ space, token, locale: "en" });
await blok.getEntryByPath("/about", { include: ["author"] });
await blok.getEntry("ent_…");
for await (const entry of blok.allEntries({ type: "article" })) { /* … */ }
await blok.getTree({ under: "/docs", depth: 2 });
await blok.getList("countries");
await blok.search("bike repair", { limit: 5 }); // AI-enabled spaces
await blok.getGlobals({ keys: ["settings", "ui_text"] }); // site-wide content, one request
await blok.getForm("contact", { locale: "nl" });
await blok.submitForm("contact", { email: "jan@example.com", message: "Hello" });
const { redirects } = await blok.getRedirects(); // old paths → new ones (see Delivery API → Redirects)
matchRedirect(redirects, "/Old-Page/"); // { from: "/old-page", to: "/new-page", status: 301 } or null
linkHref(link, included); // entry, asset and URL links → href
assetUrl(asset, { width: 800 });
assetSrcSet(asset);
blok.setCacheVersion(cvFromWebhook); // pin a version for immutable caching
t(ui.texts, "cart.items", { count: 3 }); // a dictionary text, placeholders filled in
resolveSeo(entry.content.fields.seo, settings.content.fields, { fallbackTitle: entry.name });
isScheduledNow(announcement.content.fields); // between startsAt and endsAt
organizationJsonLd(contact.content.fields, { url: "https://www.example.com" });
Forms
Forms that editors build in the admin render with one component; see the forms guide. Submissions are sent from the browser, so use a public token there, in the query string:
import { createClient } from "@blokcms/client";
import { BlokForm, BlokProvider } from "@blokcms/react";
const blok = createClient({ space, token: "blok_pub_…", tokenInQuery: true });
<BlokProvider components={components} client={blok}>…</BlokProvider>
<BlokForm form={fields.form} messages={{ required: "{label} is verplicht" }} /> // a `form` field on a page
const { controller, state } = useBlokForm(form); // your own markup
<BlokForm> is headless: semantic HTML with data-blok-* attributes to style against, no styles of its own. It is also available in @blokcms/vue and @blokcms/svelte with the same props. Without a framework, createFormController(blok, form) from @blokcms/client holds the same state (values, conditions, steps, validation, uploads, Turnstile) for you to render:
const controller = createFormController(blok, form);
controller.subscribe(() => render(controller.getState()));
controller.setValue("email", "jan@example.com");
await controller.submit();
React Server Components
@blokcms/react is a Client Component module: context, live editing and forms. In Server Components (the Next.js App Router), import the rendering from @blokcms/react/server: Blocks, RichText, BlokImage and the editable helpers, without hooks. There is no provider there, so pass the block components and the response's included as props; a block renders its nested blocks with the same components:
import { type BlockProps, Blocks, editable, RichText } from "@blokcms/react/server";
const components = { grid: Grid, card: Card };
function Grid({ block, fields }: BlockProps<{ items: unknown[] }>) {
return <section {...editable(block)}><Blocks blocks={fields.items} components={components} /></section>;
}
<Blocks blocks={entry.content.fields.body} components={components} />
<RichText doc={fields.body} included={page.included} blocks={components} />
Pages in the visual editor update live, which takes Client Components (useLiveEntry): render the editor's preview with @blokcms/react, published pages with either. Client Components are still rendered on the server first.
Vue
import { createBlok, BlokBlocks } from "@blokcms/vue";
app.use(createBlok({ components: { hero: Hero } }));
<BlokBlocks :blocks="entry.content.fields.body" />
<section v-editable="block">…</section>
Svelte
<script>
import { setBlokContext, BlokBlocks, liveEntry } from "@blokcms/svelte";
let { data } = $props();
setBlokContext({ components: { hero: Hero } });
const entry = liveEntry(data.entry);
</script>
<BlokBlocks blocks={$entry.entry.content.fields.body} />
<!-- Hero.svelte -->
<section use:editable={block}>…</section>
Rich text
import { renderRichText } from "@blokcms/richtext";
import { richTextLinkResolver } from "@blokcms/client";
const html = renderRichText(fields.body, { resolveLink: richTextLinkResolver(included) });
Output is escaped and only http(s), mailto, tel and relative links are kept.
Rich text fields can allow inline images and embedded blocks. Images are { "type": "image", "attrs": { … } } nodes whose attrs is the asset, expanded like asset fields; they render as <figure><img>. Embedded blocks are { "type": "block", "attrs": { "block": { … } } }. RichText (React), BlokRichText (Vue, Svelte) render them with your block components (the provider's, or a blocks prop); renderRichText takes a renderBlock option.
<RichText doc={fields.body} blocks={{ quote: Quote }} />
renderRichText(fields.body, { renderBlock: (block) => `<blockquote>${escapeHtml(String(block.fields.text))}</blockquote>` });