Framer — Printing Press CLI
Prerequisites: Install the CLI
This skill drives the framer-pp-cli binary. You must verify the CLI is installed before invoking any command from this skill. If it is missing, install it first:
- Install via the Printing Press installer. It defaults binaries to
$HOME/.local/binon macOS/Linux and%LOCALAPPDATA%\Programs\PrintingPress\binon Windows:npx -y @mvanhorn/printing-press-library install framer --cli-only - Verify:
framer-pp-cli --version - Ensure the reported install directory is on
$PATHfor the agent/runtime that will invoke this skill.
If the npx install fails (no Node, offline, etc.), fall back to a direct Go install (requires Go 1.26.6 or newer). This installs into $GOPATH/bin (default $HOME/go/bin), so add that directory to $PATH instead:
go install github.com/mvanhorn/printing-press-library/library/developer-tools/framer/cmd/framer-pp-cli@latest
If --version reports "command not found" after install, the runtime cannot see the binary directory on $PATH. Do not proceed with skill commands until verification succeeds.
Framer's Server API is powerful but locked behind a JavaScript SDK with no CLI. framer-pp-cli wraps every API method with offline search, dry-run previews, multi-project management, and migration automation that turns site porting from days into hours.
How It Works (Architecture)
framer-pp-cli is a Go binary that orchestrates a Node.js framer-api subprocess — the bridge (bridge/framer-bridge.mjs) — which speaks Framer's Server API over WebSocket. Any live command (anything that touches the project: publish, changes, pages, nodes, assets, code, redirects, sync, doctor, components, collections, items, project) shells out to this bridge, so Node.js 18+ must be present at runtime. The Go-only install path (go install …) still needs Node at runtime for these live commands — only the local-store commands (snapshot, diff, dashboard, search, cms-sync, cms-validate, cms-schema-diff, styles-import, code-pull, code-push, migrate-scrape, i18n-push, redirects-generate) work fully offline against the local SQLite store.
Run framer-pp-cli doctor to verify Node, the framer-api bridge, and live connectivity in one step.
Environment Variables
Every live / bridge command reads its connection details from the environment. Set these before any command that talks to Framer, or the bridge errors out immediately.
| Variable | Required | Purpose |
|---|---|---|
FRAMER_API_KEY |
Yes | Per-project API key from Framer Site Settings → General. Authenticates the bridge to the Server API. |
FRAMER_PROJECT_URL |
Yes (all live/bridge commands) | Your Framer project URL, copied from the Framer editor's address bar. Without it every bridge command (publish, changes, pages, nodes, sync, doctor, redirects, collections, items, assets, code, components, project) fails with FRAMER_PROJECT_URL not set. |
FRAMER_BRIDGE_TIMEOUT |
No | Go duration string (e.g. 5m; default 120s) capping how long a single bridge subprocess may run before it is killed. |
export FRAMER_API_KEY="$(your-secret-source)"
export FRAMER_PROJECT_URL="https://framer.com/projects/<your-project>" # required for live commands
export FRAMER_BRIDGE_TIMEOUT="5m" # optional
framer-pp-cli doctor # confirms both vars + Node bridge + connectivity
Local-store commands (snapshot, diff, dashboard, search, etc.) do not require FRAMER_PROJECT_URL once data has been synced, but the initial sync that populates the store does.
When to Use This CLI
Use framer-pp-cli when you need to automate Framer operations from scripts, CI/CD pipelines, or AI agents. Especially valuable for site migrations (importing content, uploading assets, generating redirects), multi-project management, and CMS content pipelines. Not for visual design work — use the Framer editor for that.
Unique Capabilities
These capabilities aren't available in any other tool for this API.
Local state that compounds
snapshot— Track your Framer project's evolution over time with full structural snapshots and visual diffsWhen an agent needs to understand what changed in a project between two points in time, this is the only way
framer-pp-cli snapshot --label 'before-redesign' && framer-pp-cli diff latest~1 latest --jsondashboard— Query across all registered Framer projects at once — stale CMS, unpublished changes, collection healthWhen an agent manages multiple client sites and needs a single command to find which projects need attention
framer-pp-cli dashboard --jsoncms-schema-diff— Declare CMS schema in YAML and diff it against the live project — infrastructure-as-code for Framer CMSWhen an agent needs to verify CMS schema matches the expected structure before syncing content
framer-pp-cli cms-schema-diff ./schema.yaml --jsoncms-validate— Find broken collection references, orphan items, and circular refs across your CMSWhen CMS grows past 3-4 collections, broken references become invisible — this surfaces them
framer-pp-cli cms-validate --json
Agent-native plumbing
cms-sync— Import CMS content from CSV/JSON/Sheets with a preview diff before committing changesWhen an agent needs to bulk-update CMS content safely without risking accidental overwrites or deletions
framer-pp-cli cms-sync ./blog-posts.csv --collection Blog --dry-run --jsonnodes set— Set node attributes through the live Framer bridgeWhen an agent needs to update canvas node attributes by ID
framer-pp-cli nodes set abc123 --json --dry-runpublish— Create a preview deployment, then optionally promote it to productionWhen an agent needs a shareable preview URL or an explicit production deploy
framer-pp-cli publish --jsoncode-push— Edit TSX components locally in your editor, then push to Framer — eliminates copy-paste workflowWhen a developer needs to edit Framer components in their preferred editor without the copy-paste dance
framer-pp-cli code-pull HeroSection -o hero.tsx && vim hero.tsx && framer-pp-cli code-push hero.tsx --name HeroSection
Migration automation
migrate-scrape— Planned stub for scraping a site into a Framer migration planDo not use for production migration planning yet; the command reports that this feature is planned
framer-pp-cli migrate-scrape https://old-site.com --depth 3 --dry-run --jsonassets upload— Upload a directory of images and auto-bind them to CMS items by filename-to-slug matchingWhen an agent needs to upload dozens of images and link them to the right CMS records in one operation
framer-pp-cli assets upload --dry-run --jsoni18n-push— Planned stub for syncing translations between standard i18n formats and Framer localizationDo not use for production translation sync yet; the command reports that this feature is planned
framer-pp-cli i18n-push translations.csv --format csv --dry-runredirects-generate— Planned stub for generating redirect maps from an old sitemapDo not use for production redirect generation yet; the command reports that this feature is planned
framer-pp-cli redirects-generate --old-sitemap https://old-site.com/sitemap.xml --jsonstyles-import— Import CSS variables or Tailwind config as Framer color and text styles — no manual recreationWhen porting a site with an existing design system, this eliminates hours of manual style recreation
framer-pp-cli styles-import --from tailwind.config.js --json
HTTP Transport
This CLI uses Chrome-compatible HTTP transport for browser-facing endpoints. It does not require a resident browser process for normal API calls.
Command Reference
The shipped binary exposes a bridge-flattened surface: the primary live operations are single leaf commands (no list/preview/deploy subcommands), while CMS, nodes, and a handful of others keep nested subcommands. Every command below exists in the binary — verify any of them with framer-pp-cli <command> --help.
Live / bridge commands
These talk to the Framer Server API through the Node.js bridge. They require FRAMER_API_KEY and FRAMER_PROJECT_URL.
publish — Create a preview deployment, then optionally promote to production. Single command (no preview/deploy subcommands); pass --yes to auto-deploy.
framer-pp-cli publish— Preview, print the URL + deployment ID, prompt to deployframer-pp-cli publish --yes— Non-interactive: auto-deploy to production
changes — List added, removed, and modified paths since last publish (single command, no subcommands).
framer-pp-cli changes— Show changed paths as a table or JSON
pages — List pages from the live project (single command, no subcommands).
framer-pp-cli pages— List all pages
nodes — Canvas node operations.
framer-pp-cli nodes get <id>— Get a node by ID with all attributesframer-pp-cli nodes children <id>— List children of a nodeframer-pp-cli nodes set <id>— Set attributes on a nodeframer-pp-cli nodes move <id>— Move a node to a new parent / reorder by indexframer-pp-cli nodes clone <id>— Clone a nodeframer-pp-cli nodes create-frame— Create a new frame node on the canvasframer-pp-cli nodes remove <id>— Remove a node from the canvas
assets — Image asset upload.
framer-pp-cli assets upload— Upload images to Framer and get back asset URLs
code — Code file management (live).
framer-pp-cli code list— List all code files in the projectframer-pp-cli code get <id>— Get a code file's content
components — Component operations.
framer-pp-cli components add— Add a code component instance to the canvas by URL or name
redirects — URL redirect management (live).
framer-pp-cli redirects list— List all project redirectsframer-pp-cli redirects add --from /old --to /new— Add a redirect
collections — List CMS collections directly from the Framer API (live).
framer-pp-cli collections— List collections with fields and item counts
items — List CMS items in a collection (live).
framer-pp-cli items— List items in the specified collection
cms-fields — Add, remove, or reorder collection fields.
framer-pp-cli cms-fields --collection-id <id>— Manage a collection's fields
fonts — Font management.
framer-pp-cli fonts— List all available fonts with weights and styles
project — Framer project management.
framer-pp-cli project get— Get project name, ID, and metadataframer-pp-cli project user— Get current authenticated user info
sync — Sync all Framer project data into the local store (single command).
framer-pp-cli sync— Pull collections, items, pages, code, etc. into local SQLite
doctor — Verify the live API connection via the Node.js bridge.
framer-pp-cli doctor— Check Node, theframer-apibridge, and connectivity
Finding the right command
When you know what you want to do but not which command does it, ask the CLI directly:
framer-pp-cli which "<capability in your own words>"
which resolves a natural-language capability query to the best matching command from this CLI's curated feature index. Exit code 0 means at least one match; exit code 2 means no confident match — fall back to --help or use a narrower query.
Hand-written Extensions
These commands are declared by the spec author and require separate hand-written wiring; the generator does not emit Cobra registration for them. They are listed here for discoverability and are intentionally outside ## Command Reference so the verify-skill unknown-command check does not treat them as generator-owned paths.
framer-pp-cli dashboard— Multi-project status dashboard showing stale CMS, unpublished changes, and collection healthframer-pp-cli snapshot— Take a full structural snapshot of the current project into local SQLiteframer-pp-cli diff <snapshot_a> <snapshot_b>— Diff two project snapshots to see structural changes over timeframer-pp-cli migrate-scrape <url>— Scrape an existing website to generate a Framer migration manifest (currently a stub)framer-pp-cli cms-sync <source_file>— Sync CMS content from CSV or JSON with dry-run previewframer-pp-cli cms-schema-diff <schema.yaml>— Compare a local CMS schema definition against live Framer collectionsframer-pp-cli cms-validate— Find broken collection references, orphan items, and circular refs across CMSframer-pp-cli styles-import <file>— Import CSS variables or Tailwind config as Framer color and text styles (local preview/dry-run)framer-pp-cli code-push <file>— Push a local TSX/JS file or preview the change with--dry-runframer-pp-cli code-pull <code_file_id>— Pull a Framer code file from the local store to a local TSX fileframer-pp-cli redirects-generate— Planned stub for redirect map generationframer-pp-cli i18n-push <translations_file>— Planned stub for Framer localization sync
Recipes
Bulk import blog posts from CSV
framer-pp-cli cms-sync ./posts.csv --collection Blog --dry-run && framer-pp-cli cms-sync ./posts.csv --collection Blog
Preview the import diff first, then commit the changes
Port design tokens from Tailwind
framer-pp-cli styles-import --from tailwind.config.js --json --select name,value
Import your entire Tailwind color palette as Framer styles
Edit a component locally
framer-pp-cli code-pull HeroSection --output hero.tsx
Pull a Framer code file to edit locally, then push back with code-push
Preview planned migration redirects
framer-pp-cli redirects-generate --dry-run --json
Show the current planned-stub response for redirect map generation
Create a preview deployment
framer-pp-cli publish --json
Create a preview deployment and get the shareable URL (add --yes to promote to production)
Auth Setup
Framer uses per-project API keys generated in Site Settings → General. Provide the key and project URL via the environment (see Environment Variables): set FRAMER_API_KEY and FRAMER_PROJECT_URL before any live command. Live commands require Node.js 18+ and the framer-api npm package as a runtime bridge.
To reuse the same flag set across invocations (e.g. a scheduled agent), save a named profile and apply it with --profile <name> — see Named Profiles. For status across multiple synced projects at once, use framer-pp-cli dashboard.
Run framer-pp-cli doctor to verify setup (Node + bridge + connectivity + env vars).
Agent Mode
Add --agent to any command. Expands to: --json --compact --no-input --no-color --yes.
Pipeable — JSON on stdout, errors on stderr
Filterable —
--selectkeeps a subset of fields. Dotted paths descend into nested structures; arrays traverse element-wise. Critical for keeping context small on verbose APIs:framer-pp-cli changes --agent --select id,name,statusPreviewable —
--dry-runshows the request without sendingOffline-friendly — sync/search commands can use the local SQLite store when available
Non-interactive — never prompts, every input is a flag
Explicit retries — use
--idempotentonly when an already-existing create should count as success, and--ignore-missingonly when a missing delete target should count as success
Response envelope
Commands that read from the local store or the API wrap output in a provenance envelope:
{
"meta": {"source": "live" | "local", "synced_at": "...", "reason": "..."},
"results": <data>
}
Parse .results for data and .meta.source to know whether it's live or local. A human-readable N results (live) summary is printed to stderr only when stdout is a terminal AND no machine-format flag (--json, --csv, --compact, --quiet, --plain, --select) is set — piped/agent consumers and explicit-format runs get pure JSON on stdout.
Agent Feedback
When you (or the agent) notice something off about this CLI, record it:
framer-pp-cli feedback "the --since flag is inclusive but docs say exclusive"
framer-pp-cli feedback --stdin < notes.txt
framer-pp-cli feedback list --json --limit 10
Entries are stored locally at ~/.framer-pp-cli/feedback.jsonl. They are never POSTed unless FRAMER_FEEDBACK_ENDPOINT is set AND either --send is passed or FRAMER_FEEDBACK_AUTO_SEND=true. Default behavior is local-only.
Write what surprised you, not a bug report. Short, specific, one line: that is the part that compounds.
Output Delivery
Every command accepts --deliver <sink>. The output goes to the named sink in addition to (or instead of) stdout, so agents can route command results without hand-piping. Three sinks are supported:
| Sink | Effect |
|---|---|
stdout |
Default; write to stdout only |
file:<path> |
Atomically write output to <path> (tmp + rename) |
webhook:<url> |
POST the output body to the URL (application/json or application/x-ndjson when --compact) |
Unknown schemes are refused with a structured error naming the supported set. Webhook failures return non-zero and log the URL + HTTP status on stderr.
Named Profiles
A profile is a saved set of flag values, reused across invocations. Use it when a scheduled agent calls the same command every run with the same configuration - HeyGen's "Beacon" pattern.
framer-pp-cli profile save briefing --json
framer-pp-cli --profile briefing changes
framer-pp-cli profile list --json
framer-pp-cli profile show briefing
framer-pp-cli profile delete briefing --yes
Explicit flags always win over profile values; profile values win over defaults. agent-context lists all available profiles under available_profiles so introspecting agents discover them at runtime.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Usage error (wrong arguments) |
| 3 | Resource not found |
| 4 | Authentication required |
| 5 | API error (upstream issue) |
| 7 | Rate limited (wait and retry) |
| 10 | Config error |
Argument Parsing
Parse $ARGUMENTS:
- Empty,
help, or--help→ showframer-pp-cli --helpoutput - Starts with
install→ ends withmcp→ MCP installation; otherwise → see Prerequisites above - Anything else → Direct Use (execute as CLI command with
--agent)
MCP Server Installation
- Install the MCP server:
go install github.com/mvanhorn/printing-press-library/library/developer-tools/framer/cmd/framer-pp-mcp@latest - Register with Claude Code:
claude mcp add framer-pp-mcp -- framer-pp-mcp - Verify:
claude mcp list
Direct Use
- Check if installed:
which framer-pp-cliIf not found, offer to install (see Prerequisites at the top of this skill). - Match the user query to the best command from the Unique Capabilities and Command Reference above.
- Execute with the
--agentflag:framer-pp-cli <command> [subcommand] [args] --agent - If ambiguous, drill into subcommand help:
framer-pp-cli <command> --help.