@craftware/crafty-sdk

中文文档
0.12.0发布于 2026/11/01TypeScriptES Module零依赖

官方JavaScript SDK用于Crafty CMS公共API

中文使用文档
文档目录24 节
  1. AI代理技能
  2. 要求
  3. 创建客户端
  4. 客户端配置
  5. 可用的资源
  6. collections
  7. entries
  8. pages
  9. 本地化内容
  10. forms
  11. mediaCollections
  12. mediaFiles
  13. bookings
  14. 图片
  15. llms.txt
  16. 分析
  17. 结构化数据(JSON-LD)
  18. 请求选项(每个呼叫)
  19. 响应(当前形状)
  20. 错误
  21. 的端到端示例
  22. 页面范围(通过API密钥的站点+环境)
  23. 可视化编辑器
  24. 笔记

@craftware/crafty-sdk

官方JavaScript SDK用于Crafty公共API(当前本地实现)。

AI代理技能

这个包裹运送一个代理 技能 它教Claude, Cursor和其他代理如何通过其MCP服务器配置Crafty站点,并使用此SDK构建它。它包含在已发布的包中 skills/crafty-cms/.

最快捷的安装方式,加上CLI的 crafty-deploy 技能,是 npx @craftware/crafty-cli skills install (到 .claude/skills/; --global 用于 ~/.claude/skills/, --dir=<path> 其他代理商)。这些技能也可以作为压缩文件下载给克劳德。我和克劳德 https://docs.crafty.do/skills/crafty-cms.zip.

或者在安装SDK后将其复制到项目的技能目录中:

cp -r node_modules/@craftware/crafty-sdk/skills/crafty-cms .claude/skills/

(使用等效技能目录为您的代理,例如: .cursor/skills/)。技能是自成一体的——它包括 SKILL.md 加上 references/mcp-configuration.md 和 references/sdk-development.md.

要求

  • 节点 >=20 (包引擎)
  • 或者任何运行时 fetch 可用(浏览器、Bun、Deno等)

创建客户端

import { createCraftyClient } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: "ENVIRONMENT_API_TOKEN",
  timeoutMs: 10_000
});

客户端配置

  • baseUrl (必选):租户基础URL。附加SDK /api/v1 如果丢失自动。
  • apiKey (可选):发送的环境API令牌 X-API-Key.
    • 当提供时,API从这个令牌解析活动站点。
    • pages.* 请求自动使用此站点上下文(您未通过) siteSlug)。
  • siteSlug (可选):用于公共表单提交的网站代码(forms.submit())。
  • fetch (可选):自定义获取实现。
  • headers (可选):应用于所有请求的默认标头。
  • timeoutMs (可选):所有请求的默认超时时间(ms)。仅当值大于时才应用超时 0.
  • preview (可选):视觉编辑器预览令牌作为 X-Crafty-Preview. 在浏览器中忽略它(SDK读取) ?crafty_preview= 把它藏在里面 sessionStorage); 在服务器通道上 previewFromRequest(req);通过 false 永远不发送。
  • onPreviewInvalid (可选):当API应答时调用 X-Crafty-Preview-Status: invalid (过期或外来令牌;响应是公共内容)。
  • locale (可选):为的默认区域设置 entries.* 和 pages.* (如。 "en", "pt-BR"). 看到 本地化内容.

可用的资源

collections

  • crafty.collections.list(options?)
  • crafty.collections.get(slug, options?)

entries

  • crafty.entries.list(collectionSlug, query?, options?)
  • crafty.entries.get(collectionSlug, id, options?)

只返回已发布的条目(每个条目都带有 status 和 published_at; sort_by: "published_at" (按公布日期计算)。草稿只包含在预览模式中。

支持常见的 query 参数:

  • sort_by (任何可排序字段,包括集合字段,如 title)
  • sort_order (asc | desc)
  • q (在集合的 string, text 和 longText 字段;2至100个字符)
  • per_page
  • page
  • 作为键/值对的附加过滤器(传递给API)

pages

  • crafty.pages.list(query?, options?)
  • crafty.pages.get(pageSlug, options?)

pages.* 端点的作用域由解析的环境决定 apiKey.

pages.get() 返回一个包含块帮助器的页面响应:

  • page.data =>的raw CraftyPage
  • page.blocks => all blocks (CraftyBlock[];一个非数组 data.blocks 被强迫 [])
  • page.block(type) =>该类型的第一个块或 null
  • page.blocksOf(type) =>该类型的所有块([] 如果没有一个)
  • page.hasBlock(type) = >布尔
  • page.requireBlock(type) =>第一个块,或抛出一个plain Error (不是 CraftyError)
  • page.byType.<type> =>该类型的块数组
  • page.firstByType.<type> =>该类型或的第一个块 null

本地化内容

当站点在Crafty中配置了区域设置时,可以在其中任何一个区域中请求条目和页面。通过 locale 给客户,或者每个电话 options:

const crafty = createCraftyClient({ baseUrl, apiKey, locale: "en" });

const posts = await crafty.entries.list("posts");                        // English
const home = await crafty.pages.get("home", { locale: "pt-BR" });        // overrides the client locale

每个字段都有回退:任何未翻译的内容都会返回到站点的默认区域设置中,因此部分翻译不会留下空字段。不支持的区域设置会退回到最近的区域设置(en-US → en)或默认值。反应进行 meta.locale (服务地点), meta.default_locale 和 meta.locales; get() 反应增加 meta.translations项中包含有用内容的区域设置 hreflang). 没有区域设置的站点会忽略 locale 不返回locale meta.

forms

  • crafty.forms.list(options?) -列出所有表单(需要API密钥)
  • crafty.forms.get(slug, options?) -获得一个单一的形式(需要API密钥)
  • crafty.forms.submit(formSlug, data, options?) -提交一个公共表单到API密钥的环境

这个 submit() 方法使用不需要身份验证的公共端点。当客户端有 apiKey,它被发送,以便提交到达该键的环境(staging键写入staging);没有一个,它就会投入生产。你必须提供 siteSlug 在提交到工作调用的客户端配置中 submit() 否则会抛出一个同步 CraftyError 使用代码 BAD_REQUEST:

const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: "ENVIRONMENT_API_TOKEN",
  siteSlug: "my-site",
});

// List forms (authenticated)
const forms = await crafty.forms.list();

// Get form details (authenticated)
const contactForm = await crafty.forms.get("contact");

// Submit a form into the API key's environment
const result = await crafty.forms.submit("contact", {
  name: "Jane Doe",
  email: "[email protected]",
  message: "Hello!",
});
// result => { id, form_id, submitted_at }

mediaCollections

  • crafty.mediaCollections.list(query?, options?)

支持 query.include 价值观:

  • "stats"
  • "directories"
  • 支持数组形式,重复数据删除,并序列化为逗号分隔的值;原始字符串将不变地传递

mediaFiles

  • crafty.mediaFiles.list(query?, options?)

支持常见的 query 参数:

  • collection
  • directory_id
  • ttl_minutes
  • per_page
  • page

每个文件都有 url, is_public, expires_at 和 image_transform_base (见 图片)。

bookings

网上预订(需要) siteSlug;网站的“Citas”标签必须打开):

  • crafty.bookings.config(options?) -服务(包括时间和价格)、专业人员、工时、规则、时区、货币和付款方式; enabled: false 当预订取消时。
  • crafty.bookings.availability({ service, professional?, from?, to? }, options?) -本地每日免费时段: { days: [{ date, closed, slots: [{ start, time, professionals }] }] }. 在浏览器中调用它,不要在构建时调用。
  • crafty.checkout.start({ booking: { service, professional?, start } }, { returnUrl?, cancelUrl?, fields? }) -保持插槽并将访问者发送到托管结帐(checkout.create 返回会话而不重定向)。与此同时,一个插槽被占用 SLOT_UNAVAILABLE.
  • crafty.bookings.get(number, token, options?) -客户看到的预订(来自他们链接的令牌)。

导游: skills/crafty-cms/references/online-bookings.md.

图片

crafty.image(media, options?) 返回调整大小、重新编码的图像的URL; crafty.srcset(media, widths, options?) 返回一个 srcset 值,每个宽度一个URL。两者也都导出为 imageUrl 和 imageSrcSet. media 是一个 CraftyMediaFile,一个 <key>_media 对象从块或条目,或URL字符串。

const photo = block.data.image_media;

<img
  src={crafty.image(photo, { width: 800, format: "auto" })}
  srcSet={crafty.srcset(photo, [400, 800, 1200, 1600], { format: "auto" })}
  sizes="(min-width: 768px) 50vw, 100vw"
  width={800}
  alt=""
/>
选项值
width, height整数像素,固定到1-4096
fit"scale-down", "contain", "cover", "crop", "pad"
quality1 - 100
format"auto" (浏览器接受时为AVIF/WebP), "webp", "avif", "jpeg", "png"

未知的选项和值将被删除。url有这样的形式 <image_transform_base>/cdn-cgi/image/width=800,format=auto,onerror=redirect/<source> 并由Cloudflare Image transforms提供服务;Crafty本身从不调整图像的大小。

API返回 image_transform_base 只对 公共 栅格图像,并且仅当Crafty服务器启用图像转换时。否则(带有签名url的私有媒体、svg、pdf、关闭转换) image() 返回原始的 url 不变, srcset() 返回 "",因此浏览器退回到 src. 打电话总是安全的。

llms.txt

狡猾的生成 llms.txt 和 llms-full.txt 从你发布的内容。标题、摘要、语言,以及哪些没有路由的集合被选择加入,这些都来自于admin的“llms.txt”设置屏幕 configure_llms MCP工具;url从可视化编辑器路由中解析。默认情况下,没有路由模式的集合将被排除在外——选择它in,让它的条目出现在索引和in中的“## Optional”下 llms-full.txt.

由Crafty托管的站点将这两个文件注入到站点的根目录 下一个部署;在开始使用此特性之前已经进行的部署不会进行追溯性更新。该网站自己的构建版本已经提供了一个文件 llms.txt / llms-full.txt 已经在部署输出中)总是获胜—Crafty只在文件丢失时添加文件。

从你自己的领域为他们服务:

// Next.js — app/llms.txt/route.ts
export const GET = crafty.llms.handler();

// Next.js — app/llms-full.txt/route.ts
export const GET = crafty.llms.handler({ full: true });

// Astro — src/pages/llms.txt.ts
export const GET = crafty.llms.handler();

// Nuxt — server/routes/llms.txt.ts
export default defineEventHandler(() => crafty.llms.handler()());

对于静态构建,将文件写入 dist/ 自己在构建时,而不是依赖于部署时注入:

// e.g. a postbuild script, run after `vite build` / `astro build`
import { writeFile } from "node:fs/promises";

await writeFile("dist/llms.txt", await crafty.llms.index());
await writeFile("dist/llms-full.txt", await crafty.llms.full());

在网上做广告 <head> 与 crafty.llms.linkTags() (HTML字符串)或 crafty.llms.linkTagAttrs() (对象 useHead / <Head>)。

分析

谷歌标签管理器,GA4,元像素和TikTok像素id在“Analítica”管理屏幕上或与 configure_analytics MCP的工具。不要在站点中硬编码他们的代码片段。

  • 狡猾的托管: 部署将加载器注入到每个HTML页面中。它还替换在构建时呈现的SDK的任何块,因此部署设置总是占上风。不需要其他任何东西。
  • 任何主机上的SSR: 在服务器上渲染现成的HTML,以便爬虫看到它并保留第一个页面视图:
const analytics = await crafty.analytics.get();
// analytics.head  → before </head>
// analytics.body  → right after <body> (<noscript> fallbacks)
  • 任何主机上的客户端渲染站点: 呼叫 load() 在浏览器中。它返回 "already-loaded" 当页面已经带有Crafty analytics (window.__CRAFTY_ANALYTICS__ 是由部署注入的块或之前的加载设置的),所以在Crafty Hosting上,部署获胜:
await crafty.analytics.load(); // "loaded" | "already-loaded" | "not-configured" | "unavailable"

客户端加载错过了不运行JavaScript的爬虫。在管理中所做的更改将显示在下一个请求中 get()/load();在Crafty Hosting上,它们需要重新部署。

结构化数据(JSON-LD)

Crafty将内容转换为schema.org JSON-LD,因此谷歌可以显示丰富的结果(小时数,地址,价格,常见问题)和人工智能搜索引擎获得可靠的事实。不要手写JSON-LD:

  • 网站背后的业务(类型,地址,工作时间,电话,社交资料)是在“Datos estructurados”管理屏幕上设置的 configure_structured_data MCP的工具。主页(pages.get("home")), crafty.site.get() 把它带进去 structured_data.
  • 集合可以映射到 Product, Article, BlogPosting, FAQPage, Event 或者 Service (集合编辑或 structured_data 在 update_collection). 然后,每个发布的条目都携带其JSON-LD structured_data.

crafty.structuredData(...sources) 返回 <script type="application/ld+json"> 标签用于任何混合的页面、条目、列表响应、站点或JSON-LD对象("" 当没有人有时)。FAQPage对象被合并为一个,因为谷歌期望每个页面都有一个FAQPage:

const page = await crafty.pages.get("home");
const faqs = await crafty.entries.list("faq");

head += crafty.structuredData(page, faqs); // LocalBusiness + one FAQPage with every question

对于那些写在纸上的问题, crafty.faqPage(block.data.items) 构建FAQPage { question, answer } 物品。 crafty.jsonLd(...sources) 返回对象而不是HTML useHead / <Head> 组件。

在Crafty Hosting上,部署还将业务JSON-LD添加到主页(index.html),当页面还没有呈现它,所以一个网站得到LocalBusiness,即使没有帮助。

请求选项(每个呼叫)

每个方法都接受 options? 作为最后一个论点:

  • signal: AbortSignal
  • headers:特定于请求的报头(与客户端报头合并)
  • timeoutMs:覆盖该调用的客户端超时
const posts = await crafty.entries.list(
  "posts",
  { per_page: 15, page: 1, sort_by: "updated_at", sort_order: "desc" },
  { timeoutMs: 5_000 }
);

响应(当前形状)

  • collections.list() = > { data: CraftyCollection[] }
  • collections.get() = > CraftyCollection
  • entries.list() = >分页 { data, links, meta }
  • entries.get() = > { data: CraftyEntry }
  • pages.list() = >分页 { data, links, meta }
  • pages.get() = > { data: CraftyPage } 加上上面列出的块帮助器
  • mediaCollections.list() = > { data, meta: { includes } }
  • mediaFiles.list() = > { data, links, meta } (meta 包括 ttl_minutes)
  • forms.list() = > { data: CraftyForm[] }
  • forms.get() = > { data: CraftyForm }
  • forms.submit() = > { id, form_id, submitted_at }

错误

SDK抛出 CraftyError HTTP、超时、中止、网络和配置失败(例如no fetch 可在构建时实现)。

import { CraftyError } from "@craftware/crafty-sdk";

try {
  await crafty.pages.get("home");
} catch (error) {
  if (error instanceof CraftyError) {
    console.error(error.code, error.status, error.message);
    console.error(error.requestId); // if API sent x-request-id
  }
}

当前错误码(及其http状态映射):

  • NETWORK_ERROR -网络故障,或否 fetch 施工时可用
  • TIMEOUT -请求超出 timeoutMs
  • BAD_REQUEST —HTTP 400或422
  • UNAUTHORIZED - HTTP 401
  • FORBIDDEN - HTTP 403
  • NOT_FOUND - HTTP 404
  • RATE_LIMITED - HTTP 429
  • SLOT_UNAVAILABLE -预订结帐时的HTTP 409:插槽同时被占用
  • SERVER_ERROR - HTTP 5xx
  • UNKNOWN_ERROR -任何其他/未映射状态

的端到端示例

import { createCraftyClient } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: process.env.CRAFTY_API_KEY,
  siteSlug: "my-site",
  timeoutMs: 10_000
});

const collections = await crafty.collections.list();
const posts = await crafty.entries.list("posts", { per_page: 15 });
const mediaCollections = await crafty.mediaCollections.list({ include: ["stats", "directories"] });
const mediaFiles = await crafty.mediaFiles.list({ collection: "library", per_page: 24 });
const homePage = await crafty.pages.get("home");
const forms = await crafty.forms.list();
const submission = await crafty.forms.submit("contact", { name: "Jane", email: "[email protected]" });

页面范围(通过API密钥的站点+环境)

pages 请求不再需要 siteSlug 参数。从服务器端推断出该站点 X-API-Key 报头(set from) apiKey 在 createCraftyClient())。

const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: "ENVIRONMENT_API_TOKEN",
});

await crafty.pages.list({ per_page: 12 });

const landing = await crafty.pages.get("landing");
const processBlock = landing.block("process");
const allProcessBlocks = landing.blocksOf("process");
const processFirst = landing.firstByType.process;
const processList = landing.byType.process ?? [];

可视化编辑器

Crafty的可视化编辑器在iframe中加载您的网站,并允许编辑器单击文本或图像来更改它。三个部分使网站可编辑:

1. 标记可编辑元素 与 crafty.attrs(target, field, { label }) (也输出为 attrs)。 target 是来自API的块或条目(或 { kind: "block" | "entry", id }); field 是字段键还是JSON路径 json 字段。

<h1 {...crafty.attrs(block, "title")}>{block.data.title}</h1>
<h2 {...crafty.attrs(entry, `sections[${i}].title`, { label: "Título de la sección" })}>{section.title}</h2>

按钮和链接:通过 hrefField (可选 hrefLabel),因此编辑器将目的地与文本一起编辑。单击链接上的任何位置(填充、图标、键盘激活)将选择其标记的标签,而不是跟随它。真正的 <button>, <summary> 和 <label> 元素保持它们自己的行为(菜单,手风琴),除非点击落在被标记的元素上:

<a href={cta.href} className="btn">
  <span {...crafty.attrs(entry, "hero.cta.label", { label: "Texto del botón", hrefField: "hero.cta.href", hrefLabel: "Enlace del botón" })}>{cta.label}</span>
  <ArrowIcon />
</a>

一个链接,其唯一可编辑的值是它的目标(A) url 场地)被标记在 <a> 本身: <a {...crafty.attrs(entry, "maps_url", { label: "Enlace del mapa" })} href={entry.maps_url}>Ver mapa</a>. 链接目的地永远不会被实时写入页面;他们在保存草稿后显示。

通过 hrefField 只有当内容存储目的地时:如果网站在缺少它时退回到硬编码的href,则离开 hrefField 出(编辑器不能在属性中创建缺失的键) json 字段)。 hrefField 从与标签相同的目标中读取;当标签和URL位于不同的块或项中时,分别标记它们。硬编码链接文本永远不可编辑:标记 <a> 与 url 字段,或者先将文本移动到Crafty。

字符串模板的使用 attrsHtml(target, field, options),返回转义后的 data-crafty-*="…" 对。属性总是被发出,所以静态构建会保留它们。

2. 携带预览令牌。 在浏览器中什么都不需要。使用服务器渲染,每个请求读取它:

import { createCraftyClient, previewFromRequest } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({ baseUrl, apiKey, preview: previewFromRequest(req) });

previewFromRequest 接受URL字符串, URLWeb Request,节点请求(req.url)或特快专递要求(req.query)。

3. 启用网桥 一次,在浏览器中:

import { enableVisualEditing } from "@craftware/crafty-sdk/visual-editing";

enableVisualEditing({ onRefresh: () => reloadContent() });

它不做任何事情,除非页面在Crafty管理iframe中带有预览令牌,所以它在生产中是安全的。即使如此,它仍然保持惰性,直到后端确认令牌(使用的响应) X-Crafty-Preview-Status: active) -通常是网站自己在页面加载期间的第一个Crafty请求。没有浏览器端请求携带令牌的站点(纯SSR,内容只在服务器上获取)必须通过 client 因此,桥梁可以通过一个轻量级请求(pages.list({ per_page: 1 })):

enableVisualEditing({ client: crafty });

如果API回答 invalid (过期或外来令牌),桥将保持惰性,并告诉管理员而不是激活。选项: adminOrigins (默认为 https://*.crafty.do), onRefresh (重新获取而不是完全重新加载), page / entries (服务器渲染页面显示的内容), touchHighlightMs, client (验证令牌,而不是等待站点自己的请求)。

当桥处于活动状态时,在站点内按下的Escape将被转发给管理员(SDK 0.4.1+),因此即使焦点在iframe中,编辑器的底部表单和对话框也会关闭。网站自己处理的Escape(关闭自己的菜单或模式)必须调用 event.preventDefault();然后桥就离开了。

自定义获取层。 如果一个站点用SDK客户端以外的东西获取Crafty(所以既不是站点自己的请求也不是 client 一直贯穿始终 HttpClient),报告 X-Crafty-Preview-Status 响应头你自己,从 @craftware/crafty-sdk/visual-editing:

import { getPreviewStatus, notifyPreviewActive, notifyPreviewInvalid } from "@craftware/crafty-sdk/visual-editing";

const status = response.headers.get("X-Crafty-Preview-Status");
if (status === "active") notifyPreviewActive();
if (status === "invalid") notifyPreviewInvalid();

getPreviewStatus() 读取以这种方式(或SDK自己的客户端)报告的最后状态; PREVIEW_ACTIVE_EVENT / PREVIEW_INVALID_EVENT 挂起的网桥等待的事件名称。

没有捆绑者:

<script src="https://cdn.jsdelivr.net/npm/@craftware/[email protected]/dist/crafty-visual-editing.global.js"></script>
<script>CraftyVisualEditing.enableVisualEditing();</script>

对于生产,固定一个确切的版本(@0.4.1),并加上 integrity="sha384-…" crossorigin="anonymous" 使用jsdeliver为该文件显示的散列。

网站不托管在狡猾的托管必须允许管理员框架他们: Content-Security-Policy: frame-ancestors 'self' https://<tenant>.crafty.do.

笔记

  • SDK标准化 baseUrl 避免重复 /api/v1.
  • Accept: application/json 自动设置,除非被覆盖。
  • src/types/generated.ts 仍然是一个占位符,直到OpenAPI编码被连接。
  • 公共 blockTypes 端点尚未公开;与块相关的类型目前是手工建模的。

版本与发布记录

@craftware/crafty-sdk 共发布 20 个版本,首次发布于 2026/02/24;过去 12 个月发布了 20 个版本。

版本发布日期距今
0.12.0latest2026/11/012 天前
0.11.02026/11/012 天前
0.10.42026/11/012 天前
0.10.22026/10/312 天前
0.10.12026/10/312 天前
0.10.02026/10/312 天前

关于 @craftware/crafty-sdk 的常见问题

@craftware/crafty-sdk 是什么?

官方JavaScript SDK用于Crafty CMS公共API

如何安装 @craftware/crafty-sdk?

在项目目录运行 npm install @craftware/crafty-sdk,或使用 pnpm add / yarn add / bun add。

@craftware/crafty-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@craftware/crafty-sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@craftware/crafty-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@craftware/crafty-sdk 可以商用吗?

@craftware/crafty-sdk 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@craftware/crafty-sdk 还在维护吗?

最新版本 0.12.0 发布于 2026/11/01(2 天前);至今共发布 20 个版本,其中过去 12 个月发布了 20 个;首次发布于 2026/02/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。