/install framer-crm-api
Framer CMS — Server API Skill
Manage Framer CMS content programmatically via the framer-api npm package. Push articles, upload images, create collections, and publish/deploy — all from the terminal, no Framer app needed.
First-time setup (onboarding)
If this is the first time the user uses this skill in a project, run the onboarding flow described in references/onboarding.md.
Quick check: Look for FRAMER_PROJECT_URL and FRAMER_API_KEY in the user's .env file or environment. If missing, onboard.
How it works
This skill uses the Framer Server API (framer-api npm package) which connects to Framer projects via WebSocket using an API key. It provides full CMS CRUD, image uploads, publishing, and deployment.
Important: The framer-api package must be installed in the project. If not present, run:
npm i framer-api
All operations use ES module scripts (.mjs files) with this connection pattern:
import { connect } from "framer-api"
// IMPORTANT: API key is passed as a plain string (2nd argument), NOT as {apiKey: "..."}
const framer = await connect(process.env.FRAMER_PROJECT_URL, process.env.FRAMER_API_KEY)
try {
// ... operations ...
} finally {
await framer.disconnect()
}
Available operations
CMS Collections
| Operation | Method | Notes |
|---|---|---|
| List collections | framer.getCollections() |
Returns all CMS collections |
| Get one collection | framer.getCollection(id) |
By collection ID |
| Create collection | framer.createCollection(name) |
Creates empty collection |
| Get fields | collection.getFields() |
Field definitions (name, type, id) |
| Add fields | collection.addFields([{type, name}]) |
Add new fields to collection |
| Remove fields | collection.removeFields([fieldId]) |
Delete fields by ID |
| Reorder fields | collection.setFieldOrder([fieldIds]) |
Set field display order |
CMS Items (articles, entries)
| Operation | Method | Notes |
|---|---|---|
| List items | collection.getItems() |
All items with field data |
| Create items | collection.addItems([{slug, fieldData}]) |
Create new items. Returns undefined — re-fetch with getItems() to get IDs |
| Update item fields | item.setAttributes({ fieldData: { [fieldId]: {type, value} } }) |
MUST wrap in fieldData: — without it, values are silently ignored |
| Update item slug/draft | item.setAttributes({ slug: "new", draft: false }) |
Slug and draft are set directly (NOT inside fieldData) |
| Delete item | item.remove() |
Single item |
| Bulk delete | collection.removeItems([itemIds]) |
Multiple items |
| Reorder items | collection.setItemOrder([itemIds]) |
Set display order |
⚠️ Critical: How to update CMS item fields
The setAttributes method has a non-obvious API design — field values MUST be wrapped in a fieldData key:
// ✅ CORRECT — fields wrapped in fieldData
await item.setAttributes({
fieldData: {
[titleFieldId]: { type: "string", value: "New Title" }
}
})
// ❌ WRONG — silently ignored, no error thrown
await item.setAttributes({
[titleFieldId]: { type: "string", value: "New Title" }
})
// ❌ WRONG — also silently ignored
await item.setAttributes({
[titleFieldId]: "New Title"
})
Partial updates work: Only specified fields are changed. Other fields are preserved.
Non-field attributes (slug, draft) go directly on the object, NOT inside fieldData:
await item.setAttributes({ slug: "new-slug", draft: false })
Field data format
When creating/updating items, field data is keyed by field ID (not name):
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" },
}
}])
Supported field types and their value format:
| Type | Value format | Example |
|---|---|---|
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: "\x3Ch2>Title\x3C/h2>\x3Cp>Text\x3C/p>" } |
link |
string (URL) |
{ type: "link", value: "https://example.com" } |
image |
ImageAsset object |
See image upload section |
enum |
string (case name) |
{ type: "enum", value: "Published" } |
color |
string (hex/rgba) |
{ type: "color", value: "#FF0000" } |
file |
FileAsset object |
Similar to image |
collectionReference |
string (item ID) |
{ type: "collectionReference", value: "itemId123" } |
multiCollectionReference |
string[] |
{ type: "multiCollectionReference", value: ["id1","id2"] } |
Images
Upload images from public URLs, then use the returned asset in CMS items:
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 }
}
})
Publishing & deployment
// Create a preview deployment
const result = await framer.publish()
// result = { deployment: { id }, hostnames: [...] }
// Promote preview to production
await framer.deploy(result.deployment.id)
Always ask the user before deploying to production. Publishing a preview is safe; deploying is live.
Project info & changes
await framer.getProjectInfo() // { id, name, apiVersion1Id }
await framer.getCurrentUser() // { id, name, avatar }
await framer.getPublishInfo() // Current deployment status
await framer.getChangedPaths() // { added, removed, modified }
await framer.getChangeContributors() // Contributor UUIDs
await framer.getDeployments() // All deployment history
Other operations
| Operation | Method | Notes |
|---|---|---|
| Color styles | getColorStyles(), createColorStyle() |
Design tokens |
| Text styles | getTextStyles(), createTextStyle() |
Typography tokens |
| Code files | getCodeFiles(), createCodeFile(name, code) |
Custom code overrides |
| Custom code | getCustomCode() |
Head/body code injection |
| Fonts | getFonts() |
Project fonts |
| Locales | getLocales(), getDefaultLocale() |
i18n |
| Pages | createWebPage(path), removeNode(id) |
Page management |
| Screenshots | screenshot(nodeId, options) |
PNG buffer of any node |
| Redirects | addRedirects([{from, to}]) |
Requires paid plan |
| Node tree | getNode(id), getChildren(id), getParent(id) |
DOM traversal |
Common workflows
Push a new article to CMS
See references/cms-operations.md for the full pattern including field resolution, image upload, and error handling.
Bulk update articles
const items = await collection.getItems()
for (const item of items) {
await item.setAttributes({
fieldData: {
[metaField.id]: { type: "string", value: generateMeta(item) }
}
})
}
Publish after CMS changes
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)
// Ask user before: await framer.deploy(result.deployment.id)
}
Important notes
- API key scope: Each key is bound to one project. For multiple Framer sites, store multiple keys.
- WebSocket connection: The
connect()call opens a persistent WebSocket. Always calldisconnect()when done, or useusing framer = await connect(...)for auto-cleanup. - Field IDs, not names: CMS operations use field IDs. Always call
getFields()first and resolve names to IDs. - Image fields: Pass the full
framerusercontent.comURL fromuploadImage(), not the asset ID. - Proxy methods: Most methods (getCollections, publish, etc.) are proxied — they don't appear in
Object.keys(framer)but work correctly. - Rate limits: No documented rate limits, but avoid hammering. Add small delays for bulk operations (100+ items).
formattedTextfields: Accept standard HTML (h1-h6, p, ul, ol, li, a, strong, em, img, blockquote, pre, code, table, etc.).- Draft items: Items can have
draft: true— drafts are excluded from publishing. - Blog Posts collection: Collections managed by
"thisPlugin"are read-only via the API. Only"user"managed collections can be modified.
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install framer-crm-api - 安装完成后,直接呼叫该 Skill 的名称或使用
/framer-crm-api触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
Framer CRM API 是什么?
Framer CMS management via the Server API — list, create, read, update, and delete CMS collections and items, upload images, publish previews, deploy to produ... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 124 次。
如何安装 Framer CRM API?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install framer-crm-api」即可一键安装,无需额外配置。
Framer CRM API 是免费的吗?
是的,Framer CRM API 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
Framer CRM API 支持哪些平台?
Framer CRM API 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 Framer CRM API?
由 berthelol(@berthelol)开发并维护,当前版本 v1.0.1。