SDKs

PackageWhat
@blokcms/clientTyped Delivery API client, link and image URL helpers, editor ticket verification, createFormController.
@blokcms/reactBlocks, RichText, BlokImage, BlokForm, editable(), editableField(), editableList(), useLiveEntry, useLiveGlobals, useBlokForm. @blokcms/react/server: the rendering for React Server Components.
@blokcms/vueBlokBlocks, BlokRichText, BlokImage, v-editable, editableField(), editableList(), useLiveEntry, useLiveGlobals.
@blokcms/svelteSvelte 5: BlokBlocks, BlokRichText, BlokImage, use:editable, editableFieldAttrs(), editableListAttrs(), liveEntry and liveGlobals stores.
@blokcms/richtextRich text to safe HTML or plain text, for any framework.
@blokcms/bridgeThe 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>` });