📦 Framer CRM API — Framer CMS管理
v1.0.1通过 Framer Server API 在本地一键完成 CMS 集合与项目的增删改查、图片上传、预览发布及线上部署,无需打开 Framer 后台。
详细分析 ▾
运行时依赖
版本
**针对 CMS 字段更新的关键修正与说明。** - 更新文档以阐明:使用 `item.setAttributes` 更新 CMS 项目字段时,所有字段值必须包裹在 `fieldData` 对象内;未包裹将导致更新被静默忽略。 - 添加了正确与错误使用 `setAttributes` 的清晰示例。 - 阐明非字段属性(如 slug、draft)必须直接设置,而非放在 fieldData 内。 - 对更新/创建 CMS 项目工作流进行小幅准确性调整。 - 仅涉及文档与使用指导,无代码变更。
安装命令
点击复制技能文档
通过 framer-api npm 包,以编程方式管理 Framer CMS 内容。无需打开 Framer 应用,即可在终端推送文章、上传图片、创建集合、发布/部署。
首次设置(引导)
如果用户首次在项目中使用该技能,请运行 references/onboarding.md 中描述的引导流程。
快速检查: 在用户的 .env 文件或环境中查找 FRAMER_PROJECT_URL 与 FRAMER_API_KEY。若缺失,则进行引导。
工作原理
本技能使用 Framer Server API(framer-api npm 包),通过 API Key 以 WebSocket 方式连接 Framer 项目,提供完整的 CMS CRUD、图片上传、发布与部署功能。
重要: 项目中必须已安装 framer-api。如未安装,请运行:
npm i framer-api
所有操作均采用 ES module 脚本(.mjs 文件),连接模式如下:
import { connect } from "framer-api"
// 重要:API key 作为普通字符串传入(第 2 参数),而非 {apiKey: "..."}
const framer = await connect(process.env.FRAMER_PROJECT_URL, process.env.FRAMER_API_KEY)
try {
// ... 操作 ...
} finally {
await framer.disconnect()
}
可用操作
CMS 集合
| 操作 | 方法 | 说明 |
|---|---|---|
| 列出集合 | framer.getCollections() | 返回所有 CMS 集合 |
| 获取单个集合 | framer.getCollection(id) | 通过集合 ID |
| 创建集合 | framer.createCollection(name) | 创建空集合 |
| 获取字段 | collection.getFields() | 字段定义(名称、类型、ID) |
| 添加字段 | collection.addFields([{type, name}]) | 向集合添加新字段 |
| 删除字段 | collection.removeFields([fieldId]) | 按 ID 删除字段 |
| 排序字段 | collection.setFieldOrder([fieldIds]) | 设置字段显示顺序 |
CMS 条目(文章、记录)
| 操作 | 方法 | 说明 |
|---|---|---|
| 列出条目 | collection.getItems() | 获取所有条目及其字段数据 |
| 创建条目 | collection.addItems([{slug, fieldData}]) | 创建新条目。返回 undefined —— 需再次 getItems() 获取 ID |
| 更新条目字段 | item.setAttributes({ fieldData: { [fieldId]: {type, value} } }) | 必须包裹在 fieldData: 内 —— 否则值会被静默忽略 |
| 更新条目 slug/草稿 | item.setAttributes({ slug: "new", draft: false }) | slug 与 draft 直接设置(不在 fieldData 内) |
| 删除条目 | item.remove() | 单条条目 |
| 批量删除 | collection.removeItems([itemIds]) | 多条条目 |
| 排序条目 | collection.setItemOrder([itemIds]) | 设置显示顺序 |
⚠️ 关键:如何更新 CMS 条目字段
setAttributes 方法有一个不直观的 API 设计——字段值必须包裹在 fieldData 键内:
// ✅ 正确 —— 字段包裹在 fieldData 内 await item.setAttributes({ fieldData: { [titleFieldId]: { type: "string", value: "New Title" } } })// ❌ 错误 —— 会被静默忽略,不抛错 await item.setAttributes({ [titleFieldId]: { type: "string", value: "New Title" } })
// ❌ 错误 —— 同样被静默忽略 await item.setAttributes({ [titleFieldId]: "New Title" })
部分更新有效: 仅指定字段会被修改,其余字段保留。 非字段属性(slug、draft)直接放在对象上,不在 fieldData 内:
await item.setAttributes({
slug: "new-slug",
draft: false
})
字段数据格式
创建/更新条目时,字段数据以字段 ID(而非名称)为键:
const fields = await collection.getFields()
const titleField = fields.find(f => f.name === "Title")
await collection.addItems([{
slug: "my-article",
fieldData: {
[titleField.id]: {
type: "string",
value: "My Article Title"
}
}
}])
支持的字段类型及其值格式:
| 类型 | 值格式 | 示例 |
|---|---|---|
string | string | { type: "string", value: "Hello" } |
number | number | { type: "number", value: 42 } |
boolean | boolean | { type: "boolean", value: true } |
date | string(UTC ISO) | { type: "date", value: "2026-04-06T00:00:00Z" } |
formattedText | string(HTML) | { type: "formattedText", value: " |
link | string(URL) | { type: "link", value: "https://example.com" } |
image | ImageAsset 对象 | 见图片上传章节 |
enum | string(枚举名) | { type: "enum", value: "Published" } |
color | string(hex/rgba) | { type: "color", value: "#FF0000" } |
file | FileAsset 对象 | 与 image 类似 |
collectionReference | string(条目 ID) | { type: "collectionReference", value: "itemId123" } |
multiCollectionReference | string[] | { type: "multiCollectionReference", value: ["id1","id2"] } |
图片
从公开 URL 上传图片,然后将返回的 asset 用于 CMS 条目:
const asset = await framer.uploadImage("https://example.com/photo.jpg")
// asset = { id, url, thumbnailUrl }
await item.setAttributes({
fieldData: {
[thumbnailField.id]: {
type: "image",
value: asset.url
}
}
})
发布与部署
// 创建预览部署 const result = await framer.publish() // result = { deployment: { id }, hostnames: [...] }
// 将预览提升为生产 await framer.deploy(result.deployment.id)
部署到生产前务必询问用户。 发布预览是安全的;部署即上线。
项目信息与变更
await framer.getProjectInfo() // { id, name, apiVersion1Id }
await framer.getCurrentUser() // { id, name, avatar }
await framer.getPublishInfo() // 当前部署状态
await framer.getChangedPaths() // { added, removed, modified }
await framer.getChangeContributors() // 贡献者 UUID 列表
await framer.getDeployments() // 所有部署历史
其他操作
| 操作 | 方法 | 说明 |
|---|---|---|
| 颜色样式 | getColorStyles(), createColorStyle() | 设计令牌 |
| 文本样式 | getTextStyles(), createTextStyle() | 排版令牌 |
| 代码文件 | getCodeFiles(), createCodeFile(name, code) | 自定义代码覆盖 |
| 自定义代码 | getCustomCode() | 头部/主体代码注入 |
| 字体 | getFonts() | 项目字体 |
| 本地化 | getLocales(), getDefaultLocale() | 国际化 |
| 页面 | createWebPage(path), removeNode(id) | 页面管理 |
| 截图 | screenshot(nodeId, options) | 任意节点的 PNG 缓冲 |
| 重定向 | addRedirects([{from, to}]) | 需付费套餐 |
| 节点树 | getNode(id), getChildren(id), getParent(id) | DOM 遍历 |
常见工作流
推送新文章到 CMS
详见 references/cms-operations.md,包含字段解析、图片上传与错误处理完整范例。
批量更新文章
const items = await collection.getItems()
for (const item of items) {
await item.setAttributes({
fieldData: {
[metaField.id]: {
type: "string",
value: generateMeta(item)
}
}
})
}
CMS 变更后发布
const changes = await framer.getChangedPaths()
if (changes.added.length || changes.modified.length || changes.removed.length) {
const result = await framer.publish()
console.log("Preview:", result.hostnames)
// 询问用户后再:
// await framer.deploy(result.deployment.id)
}
重要提示
- API key 作用域: 每个 key 仅绑定一个项目。多个 Framer 站点需存储多个 key。
- WebSocket 连接:
connect()会建立持久 WebSocket。完成后务必disconnect(),或使用using framer = await connect(...)自动清理。 - 使用字段 ID,而非名称: CMS 操作均使用字段 ID。务必先
getFields()并将名称解析为 ID。 - 图片字段: 传入
uploadImage()返回的完整framerusercontent.comURL,而非 asset ID。 - 代理方法: 多数方法(getCollections、publish 等)为代理 —— 不会出现在
Object.keys(framer)中,但可正常使用。 - 速率限制: 无公开限制,但避免猛冲。批量操作(100+ 条目)请加小幅延迟。
formattedText字段: 接受标准 HTML(h1-h6、p、ul、ol、li、a、strong、em、img、blockquote、pre、code、table 等)。- 草稿条目: 条目可设置
draft: true—— 草稿不会被发布。 - Blog Posts 集合: 由
"thisPlugin"管理的集合通过 API 只读。仅"user"管理的集合可修改。