@dani-builder/strapi-client
TypeScript client for the Daniworks Builder
CMS (Strapi v5). Hand-curated types, deep-populate builders, and SSR-safe API
functions for every content type — pairs with
@dani-builder/strapi-plugin-builder.
Install
pnpm add @dani-builder/strapi-client
Existing client entries are ESM-only ("type": "module"). The server-only
/preview entry supports ESM and CommonJS, with no Strapi runtime or peers.
TypeScript consumers require moduleResolution: "bundler", "node16", or "nodenext"; legacy "node10" resolution supports only /preview declarations through typesVersions.
Usage
Every API function takes an AxiosInstance (no singleton — SSR-safe):
import { createStrapiClient } from "@dani-builder/strapi-client/client";
import { getPageBySlug, getGlobal } from "@dani-builder/strapi-client/api";
const strapi = createStrapiClient({
baseUrl: process.env.STRAPI_URL,
apiToken: process.env.STRAPI_TOKEN,
});
const page = await getPageBySlug(strapi, "about");
const global = await getGlobal(strapi);
// Editor preview: pass status: "draft" (authenticated requests only).
const draft = await getPageBySlug(strapi, "about", { status: "draft" });
Page list/detail and Global endpoints use fixed server-side populate.
getPageBySlug resolves the slug with a Smart list read, then fetches Smart detail.
Legacy listPagesRest and getGlobalRest send populate parameters that these
handlers ignore. Other collections use Builder core REST with client-side populate.
Site settings and analytics
getSiteSettings populates analytics settings automatically. For a custom REST
request, include the shared populate builder:
import { getSiteSettingsPopulate } from "@dani-builder/strapi-client/populate";
import type {
SiteSettingsDocument,
StrapiResponse,
} from "@dani-builder/strapi-client/types";
const { data } = await strapi.get<StrapiResponse<SiteSettingsDocument>>(
"/api/builder/site-setting",
{ params: { populate: getSiteSettingsPopulate() } },
);
const configs = data.data.analyticsSettings;
// Pass configs to AnalyticsProvider from @dani-builder/blocks/analytics.
Sub-exports
| Import | Contents |
|---|---|
@dani-builder/strapi-client/types | Hand-curated content-type & block types |
@dani-builder/strapi-client/client | createStrapiClient |
@dani-builder/strapi-client/api | 21 API functions |
@dani-builder/strapi-client/populate | 9 populate builders + coreBlockPopulates, seoPopulate, footerPopulate |
@dani-builder/strapi-client/utils | Media URL, SEO, verification, JSON-LD and FAQ helpers |
@dani-builder/strapi-client/preview | Node.js server-only verifyPreviewToken, isSafePreviewPath, PREVIEW_TTL_SECONDS |
See docs/block-reference.md for the full block schema.
License
MIT © HJ-company
Preview verification requires client 0.12.0 or later. Import it only in Node.js
server code and keep PREVIEW_SECRET private. The shared verifier binds the signed
path and draft/published status and rejects tokens at expiry (300 seconds).
The existing plugin /preview verifier remains compatible.
Agent browser client
createAgentClient({ baseUrl, apiPrefix?, fetch? }) is exported from the package
root. It provides session() issuance, session(credentials) restoration,
history(credentials), chat(credentials, request, { onEvent? }) and
cancel(credentials, requestId). A chat handle exposes its stable requestId,
result promise and cancel() immediately. Streaming events use the shared
@dani-builder/agent-contract parser. Keep the issued token with the session ID
in caller-owned storage and handle both result and cancellation rejections. Tokens
are sent only in Bearer headers; cookies are omitted. Existing Axios APIs continue
to work independently.
See Agent sessions and conversation context for browser usage, error/cancellation behavior, retries and the CMS origin allowlist.
SEO and structured data
These helpers return plain data and have no framework dependency. Import them and
their public types from the package root or /utils; adapt the result to your
framework's metadata or HTML renderer.
import {
resolveSeo,
siteVerificationMeta,
faqPageJsonLd,
mergeStructuredData,
serializeJsonLd,
} from "@dani-builder/strapi-client/utils";
const seo = resolveSeo(page.seo, {
title: "About us",
description: "Meet our team",
url: "https://example.com/about",
ogImageUrl: "https://example.com/default-og.png",
mediaHost: "https://cms.example.com",
indexingEnabled: true,
});
// seo: {
// title, description?, canonical?, robots: { index, follow },
// openGraph: { title, description?, url?, images: string[] },
// twitter: { title, description?, url?, images: string[], card }
// }
const verification = siteVerificationMeta(siteSettings);
// [{ name: "google-site-verification", content: "configured token" }, ...]
const faq = faqPageJsonLd(faqs);
// { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: [...] }
// or null when no usable question/answer pairs exist.
const { data, excluded } = mergeStructuredData(faq, page.seo?.structuredData);
const jsonLd = serializeJsonLd(data);
// Use jsonLd as the text of a script with type="application/ld+json".
// Inspect excluded for invalid CMS entries before publishing.
resolveSeo prefers trimmed, nonblank CMS titles and descriptions, then caller
fallbacks. Its CMS canonical must be an explicit absolute HTTP(S) URL with a
hostname; relative, protocol-relative and malformed values fall back to url.
Caller defaults are used as provided. Indexing defaults to enabled. A global
indexingEnabled: false makes both robots flags false; otherwise noindex and
nofollow independently disable their respective flags. CMS OG media uses
getMediaUrl(url, mediaHost) and falls back to ogImageUrl for missing or blank
media. Both social outputs share those resolved values; Twitter uses
summary_large_image with an image and summary without one. Optional metadata
fields are omitted when no fallback exists, and image arrays are then empty.
siteVerificationMeta accepts nullable settings with siteVerifications. It maps
Google to google-site-verification, Bing to msvalidate.01, Naver to
naver-site-verification, and custom services to their configured name. It trims
names/content, omits blank content, custom names and unknown services, and preserves
configured order and duplicate tags. Render each entry through your framework's
normal attribute escaping.
serializeJsonLd uses JSON.stringify, then escapes every < as \u003c so
CMS strings containing </script> cannot end a script element. The resulting
string parses back to the same JSON value. It throws TypeError for top-level
undefined, functions, symbols, or values whose toJSON returns undefined. Native
JSON errors for circular references and BigInt propagate. It serializes data;
it does not validate Schema.org vocabulary or emit a script tag.
mergeStructuredData(auto, cms) accepts single objects or arrays. Null/undefined
inputs are absent. Each accepted object must have a nonblank string @type or a
nonempty array of nonblank strings. Invalid entries are omitted and reported in
excluded as { source: "auto" | "cms", index, reason }; reasons are
"not-object" and "invalid-type", with zero-based indexes (zero for single
inputs). The automatic entries establish order. A CMS entry with an equal @type
replaces the existing object in place; new types append. Repeated types use the
last entry, including automatic duplicates. Type arrays match by their exact
sorted unique set: ["Thing", "Product"] equals ["Product", "Thing"], but
neither equals "Product". A single-element array equals its string form. Type
values retain their original spelling; whitespace-only values are invalid.
The helper preserves object references and does not mutate, deep-merge, expand
@graph, or validate properties beyond @type.
blocksToPlainText(blocks) extracts text and link children from Strapi Blocks,
concatenating inline text and separating paragraphs, headings, quotes, code and
list items with newlines. It ignores images, unknown node kinds and malformed
nodes, and returns an empty string for absent/non-array input. The outer result
is trimmed. faqPageJsonLd accepts a readonly array of question/answer records,
uses that extraction for answers, trims questions and omits blank pairs. The result
uses Question and Answer entities; return null means no FAQ script is needed.
robots.txt
Site Settings has optional robotsTxt text. Saves accept User-agent, Allow,
Disallow, Sitemap, and Crawl-delay directive names (case insensitive), followed
by a colon, plus comments and blank lines. Invalid lines return a validation error
with the field name and original one-based line number. This is basic directive
syntax validation; directive values and crawler behavior remain author-owned.
resolveRobotsTxt(siteSetting, { indexingAllowed, siteUrl, defaultRules }) is
exported from the root and /utils. Null, absent, empty, or whitespace-only CMS
text uses the consumer's defaultRules. With indexing enabled, it preserves those
rules and appends Sitemap: <siteUrl>/sitemap.xml unless an active, case-insensitive
Sitemap directive already exists. Trailing slashes are removed from siteUrl;
commented sitemap directives do not count. With indexingAllowed: false, it always
returns exactly User-agent: *\nDisallow: /, without a sitemap.
Serve the returned string from a plain-text route, for example in Next.js at
app/robots.txt/route.ts:
import { getSiteSettings, resolveRobotsTxt } from "@dani-builder/strapi-client";
import { client } from "@/lib/strapi";
import { indexingAllowed, siteUrl } from "@/lib/site-config";
export async function GET() {
const siteSetting = await getSiteSettings(client);
const body = resolveRobotsTxt(siteSetting, {
indexingAllowed,
siteUrl,
defaultRules: "User-agent: *\nAllow: /",
});
return new Response(body, {
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
The consumer controls indexing policy and must supply its own client and site
configuration. Avoid also defining a framework robots.ts route at the same URL.
Agent consumers require @dani-builder/agent-contract >=1.0.0 <2.
The shared parser ignores removed inquiry_draft extension frames and filters them
from completed output while retaining valid siblings. Use a consumer-owned action
and renderer for contact form preparation; see the
output migration guide.