Claude Code skill pack for Framer (18 skills)
Installation
Open Claude Code and run this command:
/plugin install framer-pack@claude-code-plugins-plus
Use --global to install for all projects, or --project for current project only.
Skills (18)
Configure Framer CI/CD integration with GitHub Actions and testing.
Framer CI Integration
Overview
Set up CI/CD for Framer plugins and Server API integrations. Plugin builds are tested with Vite + vitest. Server API CMS sync can be triggered from CI for automated content publishing.
Instructions
Step 1: GitHub Actions for Plugin Build
name: Framer Plugin CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run build
- run: npm test
cms-sync:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
env:
FRAMER_API_KEY: ${{ secrets.FRAMER_API_KEY }}
FRAMER_SITE_ID: ${{ secrets.FRAMER_SITE_ID }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- name: Sync CMS and publish
run: node scripts/sync-and-publish.js
Step 2: CMS Sync Script for CI
// scripts/sync-and-publish.ts
import { framer } from 'framer-api';
async function main() {
const client = await framer.connect({
apiKey: process.env.FRAMER_API_KEY!,
siteId: process.env.FRAMER_SITE_ID!,
});
// Fetch content from your CMS/API
const posts = await fetch('https://your-api.com/posts').then(r => r.json());
// Sync to Framer CMS
const collections = await client.getCollections();
const blogCollection = collections.find(c => c.name === 'Blog Posts');
if (blogCollection) {
await blogCollection.setItems(posts.map(p => ({ fieldData: { title: p.title, body: p.content, slug: p.slug } })));
console.log(`Synced ${posts.length} posts`);
}
// Publish site
await client.publish();
console.log('Site published');
}
main().catch(e => { console.error(e); process.exit(1); });
Step 3: Configure Secrets
gh secret set FRAMER_API_KEY --body "framer_sk_..."
gh secret set FRAMER_SITE_ID --body "abc123"
Output
- Plugin builds verified on every PR
- Automated CMS sync and publish on main push
- Secrets configured in GitHub
Resources
Next Steps
For deployment, see framer-deploy-integration.
Diagnose and fix Framer common errors and exceptions.
Framer Common Errors
Overview
Diagnostic reference for common Framer plugin, component, and Server API errors with actionable fixes.
Error Reference
Plugin Not Appearing in Editor
Cause: Dev server not running or plugin not registered.
Fix:
npm run dev # Start Vite dev server
# Then in Framer: Plugins > Development > select your plugin
framer is not defined
Cause: Calling framer API outside the Framer editor iframe context.
Fix: The framer global from framer-plugin only works inside the editor. For server-side access, use framer-api package instead.
// Plugin (editor): import { framer } from 'framer-plugin';
// Server (headless): import { framer } from 'framer-api';
Component Renders Blank on Canvas
Cause: Runtime error in component code (swallowed by Framer).
Fix: Open Framer's browser console (right-click > Inspect) and check for errors. Common causes:
- Missing
export defaulton component - Undefined props without defaults
- Fetch errors from blocked CORS requests
addPropertyControls Not Showing
Cause: Called on wrong component or wrong import.
Fix:
// Must import from 'framer', not 'framer-plugin'
import { addPropertyControls, ControlType } from 'framer';
// Must be called AFTER the component definition
export default function MyComponent(props) { /* ... */ }
addPropertyControls(MyComponent, { /* ... */ });
Code Override Not Applying
Cause: Override function not returning correct shape.
Fix: Overrides must return an Override type object (Framer Motion props):
import { Override } from 'framer';
// Correct — returns Override
export function MyOverride(): Override {
return { whileHover: { scale: 1.1 } };
}
// Wrong — missing return type, or returning JSX
Server API WebSocket connection failed
Cause: Invalid API key, wrong site ID, or network blocking WSS.
Fix:
# Verify API key is valid
echo $FRAMER_API_KEY | head -c 20 # Should start with 'framer_sk_'
# Verify site ID
echo $FRAMER_SITE_ID
# Test WebSocket connectivity
curl -s https://api.framer.com/health || echo "Cannot reach Framer API"
CMS Collection field type invalid
Cause: Using unsupported field type string.<
Execute Framer primary workflow: Core Workflow A.
Framer CMS Plugin — Managed Collections
Overview
Build a Framer plugin that syncs external data into CMS Managed Collections. Managed Collections are plugin-controlled — your plugin creates the schema and populates items. This is the primary integration pattern for connecting Framer to external CMSes, databases, or APIs.
Prerequisites
- Completed
framer-install-authsetup - Plugin dev server running
- Understanding of Framer CMS concepts
Instructions
Step 1: Create a Managed Collection
// src/App.tsx — CMS sync plugin
import { framer } from 'framer-plugin';
import { useState } from 'react';
framer.showUI({ width: 340, height: 400, title: 'Content Sync' });
export function App() {
const [status, setStatus] = useState('');
const syncCollection = async () => {
setStatus('Fetching data...');
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await response.json();
setStatus('Creating collection...');
const collection = await framer.createManagedCollection({
name: 'Blog Posts',
fields: [
{ id: 'title', name: 'Title', type: 'string' },
{ id: 'body', name: 'Body', type: 'formattedText' },
{ id: 'author', name: 'Author', type: 'string' },
{ id: 'slug', name: 'Slug', type: 'slug', userEditable: false },
],
});
setStatus(`Syncing ${posts.length} items...`);
const items = posts.slice(0, 20).map((post: any) => ({
fieldData: {
title: post.title,
body: `<p>${post.body}</p>`,
author: `User ${post.userId}`,
slug: post.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50),
},
}));
await collection.setItems(items);
setStatus(`Synced ${items.length} posts`);
framer.notify(`Synced ${items.length} blog posts`);
};
return (
<div style={{ padding: 16 }}>
<h3>Blog Post Sync</h3>
<button onClick={syncCollection} style={{ width: '100%', padding: 8 }}>Sync Now</button>
{status && <p style={{ marginTop: 8, fontSize: 13, color: '#666' }}>{status}</p>}
</div>
);
}
Step 2: Handle CMS Field Types
// Framer CMS field types reference
const fields = [
{ id: 'title', name: 'Title', type: 'string' as const },
{ id: 'content', name: 'Content', type: 'formattedText' as const },
{ id: 'price', name: 'Price', type: 'number' as const },
{ id: 'featured', name: 'Featured', type: 'boolean' as const },
{ id: 'publishDateExecute Framer secondary workflow: Core Workflow B.
Framer Code Components & Overrides
Overview
Build code components with property controls and code overrides for Framer sites. Components are custom React rendered on the canvas. Overrides modify existing layer behavior (animations, interactions) without changing the component.
Prerequisites
- Framer project open in editor
- Understanding of React and Framer Motion
Instructions
Step 1: Code Component with Property Controls
import { addPropertyControls, ControlType } from 'framer';
import { useRef, useEffect, useState } from 'react';
interface Props { target: number; duration: number; suffix: string; fontSize: number; color: string; }
export default function AnimatedCounter({ target = 1000, duration = 2, suffix = '+', fontSize = 48, color = '#000' }: Props) {
const ref = useRef(null);
const [count, setCount] = useState(0);
const [started, setStarted] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([e]) => { if (e.isIntersecting) setStarted(true); }, { threshold: 0.5 });
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
useEffect(() => {
if (!started) return;
const inc = target / (duration * 60);
let val = 0;
const timer = setInterval(() => {
val += inc;
if (val >= target) { setCount(target); clearInterval(timer); }
else setCount(Math.floor(val));
}, 1000 / 60);
return () => clearInterval(timer);
}, [started, target, duration]);
return <div ref={ref} style={{ fontSize, fontWeight: 700, color, textAlign: 'center' }}>{count.toLocaleString()}{suffix}</div>;
}
addPropertyControls(AnimatedCounter, {
target: { type: ControlType.Number, title: 'Target', defaultValue: 1000 },
duration: { type: ControlType.Number, title: 'Duration (s)', defaultValue: 2 },
suffix: { type: ControlType.String, title: 'Suffix', defaultValue: '+' },
fontSize: { type: ControlType.Number, title: 'Font Size', defaultValue: 48 },
color: { type: ControlType.Color, title: 'Color', defaultValue: '#000' },
});
Step 2: Data-Fetching Component
import { addPropertyControls, ControlType } from 'framer';
import { useEffect, useState } from 'react';
export default function DataList({ apiUrl = 'https://jsonplaceholder.typicode.com/posts', limit = 5 }) {
const [items, setItems] = useState<any[]>([]);
useEffect(() => {
fetch(apiUrl).then(r => r.json()).then(d => setItems(d.slice(0, limit))).catch(() => {});
}, [apiUrl, limit]);
return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map((item, i) => <li key={i} style={{ padding: '8px 0', borderBottom: '1px sOptimize Framer costs through tier selection, sampling, and usage monitoring.
Framer Cost Tuning
Overview
Optimize Framer costs across plans and features. The Server API is free during beta. Main costs are Framer site plans, custom domains, and team seats.
Framer Plans
| Plan | Price | CMS Items | Custom Domain | Pages |
|---|---|---|---|---|
| Free | $0 | 100 | No | 2 |
| Mini | $5/mo | 200 | Yes | 150 |
| Basic | $15/mo | 1,000 | Yes | 300 |
| Pro | $30/mo | 10,000 | Yes | Unlimited |
| Enterprise | Custom | Unlimited | Yes | Unlimited |
Cost Optimization Strategies
Step 1: CMS Item Budgeting
// Track CMS item usage to avoid plan overages
async function checkCMSUsage(client: any) {
const collections = await client.getCollections();
let totalItems = 0;
for (const col of collections) {
const items = await col.getItems();
totalItems += items.length;
console.log(`${col.name}: ${items.length} items`);
}
console.log(`Total CMS items: ${totalItems}`);
// Pro plan: 10,000 limit
console.log(`Usage: ${((totalItems / 10000) * 100).toFixed(1)}% of Pro limit`);
}
Step 2: Minimize Publish Frequency
// Batch CMS updates before publishing (each publish rebuilds the site)
async function batchUpdateAndPublish(updates: Array<{ collection: string; items: any[] }>) {
const client = await getClient();
for (const update of updates) {
const col = (await client.getCollections()).find(c => c.name === update.collection);
if (col) await col.setItems(update.items);
}
// Single publish after all updates
await client.publish();
}
Step 3: Development vs Production Sites
Use a free plan site for development and testing. Only pay for the production site.
Resources
Next Steps
For architecture patterns, see framer-reference-architecture.
Collect Framer debug evidence for support tickets and troubleshooting.
Framer Debug Bundle
Overview
Collect diagnostic information for Framer plugin or Server API issues including package versions, API connectivity, and configuration.
Instructions
Step 1: Create Debug Bundle
#!/bin/bash
BUNDLE="framer-debug-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE"
echo "=== Framer Debug Bundle ===" | tee "$BUNDLE/summary.txt"
echo "Date: $(date -u)" >> "$BUNDLE/summary.txt"
# Runtime
echo "--- Runtime ---" >> "$BUNDLE/summary.txt"
node --version >> "$BUNDLE/summary.txt" 2>&1
npm --version >> "$BUNDLE/summary.txt" 2>&1
# Packages
echo "--- Packages ---" >> "$BUNDLE/summary.txt"
npm list framer-plugin framer-api framer 2>/dev/null >> "$BUNDLE/summary.txt"
# Credentials (presence only)
echo "--- Config ---" >> "$BUNDLE/summary.txt"
echo "FRAMER_API_KEY: ${FRAMER_API_KEY:+[SET]}" >> "$BUNDLE/summary.txt"
echo "FRAMER_SITE_ID: ${FRAMER_SITE_ID:+[SET]}" >> "$BUNDLE/summary.txt"
# API connectivity
echo "--- API Test ---" >> "$BUNDLE/summary.txt"
curl -s -o /dev/null -w "Framer API: HTTP %{http_code}\n" https://api.framer.com/health >> "$BUNDLE/summary.txt" 2>&1
# Vite config
cp vite.config.ts "$BUNDLE/" 2>/dev/null
cp tsconfig.json "$BUNDLE/" 2>/dev/null
cp package.json "$BUNDLE/" 2>/dev/null
# Bundle
tar -czf "$BUNDLE.tar.gz" "$BUNDLE"
echo "Bundle: $BUNDLE.tar.gz"
Output
summary.txtwith runtime, packages, connectivity- Configuration files (non-sensitive)
- Compressed archive for support
Resources
Next Steps
For rate limit issues, see framer-rate-limits.
Deploy Framer integrations to Vercel, Fly.
Framer Deploy Integration
Overview
Deploy Framer Server API integrations (CMS sync services, webhook handlers) to cloud platforms. Framer sites themselves are hosted by Framer — this covers deploying your backend services that interact with Framer.
Instructions
Step 1: Vercel Serverless (CMS Sync API)
// api/sync-framer.ts — Vercel serverless function
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { framer } from 'framer-api';
export default async function handler(req: VercelRequest, res: VercelResponse) {
if (req.method !== 'POST') return res.status(405).end();
const client = await framer.connect({
apiKey: process.env.FRAMER_API_KEY!,
siteId: process.env.FRAMER_SITE_ID!,
});
const { items, collectionName } = req.body;
const collections = await client.getCollections();
const col = collections.find(c => c.name === collectionName);
if (!col) return res.status(404).json({ error: 'Collection not found' });
await col.setItems(items);
await client.publish();
res.json({ synced: items.length, published: true });
}
vercel env add FRAMER_API_KEY production
vercel env add FRAMER_SITE_ID production
vercel --prod
Step 2: Fly.io (Long-Running Sync Service)
fly secrets set FRAMER_API_KEY=framer_sk_...
fly secrets set FRAMER_SITE_ID=abc123
fly deploy
Step 3: Webhook Receiver for Content Updates
// api/webhook-handler.ts — receive webhooks from your CMS, sync to Framer
export default async function handler(req, res) {
const { event, data } = req.body;
if (event === 'content.published') {
const client = await framer.connect({ apiKey: process.env.FRAMER_API_KEY!, siteId: process.env.FRAMER_SITE_ID! });
// Sync updated content to Framer CMS
const col = (await client.getCollections()).find(c => c.name === 'Blog Posts');
if (col) await col.setItems([{ fieldData: data }]);
await client.publish();
}
res.json({ ok: true });
}
Output
- Serverless CMS sync endpoint
- Webhook handler for content update automation
- Platform secrets configured
Resources
Next Steps
For webhook patterns, see framer-webhooks-events.
Create a minimal working Framer example.
Framer Hello World
Overview
Build a minimal Framer plugin that inserts a styled text layer onto the canvas, and a code component that renders inside Framer sites. Both use the framer-plugin SDK which provides the framer global for editor interaction.
Prerequisites
- Completed
framer-install-authsetup - Framer editor open with a project
- Plugin dev server running (
npm run dev)
Instructions
Step 1: Hello World Plugin
// src/App.tsx — Plugin UI that runs inside Framer editor
import { framer } from 'framer-plugin';
framer.showUI({ width: 300, height: 200, title: 'Hello World Plugin' });
export function App() {
const insertText = async () => {
await framer.addText('Hello from my plugin!', {
position: { x: 100, y: 100 },
style: { fontSize: 24, color: '#333' },
});
framer.notify('Text inserted on canvas!');
};
const insertImage = async () => {
await framer.addImage({
url: 'https://picsum.photos/400/300',
position: { x: 100, y: 200 },
});
};
return (
<div style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
<h2>Hello World</h2>
<button onClick={insertText}>Insert Text</button>
<button onClick={insertImage}>Insert Image</button>
</div>
);
}
Step 2: Hello World Code Component
// Code component — renders inside Framer sites (not a plugin)
// Create via: Framer editor > Assets > Code > New Component
import { addPropertyControls, ControlType } from 'framer';
interface Props {
text: string;
color: string;
}
export default function HelloComponent({ text, color }: Props) {
return (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
fontSize: 24,
fontWeight: 600,
color,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 12,
padding: 20,
}}>
{text}
</div>
);
}
addPropertyControls(HelloComponent, {
text: { type: ControlType.String, title: 'Text', defaultValue: 'Hello Framer!' },
color: { type: ControlType.Color, title: 'Color', defaultValue: '#ffffff' },
});
Step 3: Hello World Code Override
// Code override — modifies existing layer behavior
// Create via: Framer editor > Assets > Code > New Override
import { Override } from 'framer';
export function FadeInOnScroll(): Override {
return {
initial: { opacity: 0, y: 20 },
whileInView: { opacity: 1, y: 0 },
transition: { duration: 0.6, ease:Install and configure Framer SDK/CLI authentication.
Framer Install & Auth
Overview
Set up the Framer Plugin SDK for building editor plugins, or the framer-api package for Server API access. Framer has two developer surfaces: Plugins (run inside the Framer editor UI) and Server API (run from any Node.js server via WebSocket).
Prerequisites
- Node.js 18+
- Framer account (free or paid)
- For Server API: API key from site settings
Instructions
Step 1: Choose Your Integration Type
| Type | Package | Use Case |
|---|---|---|
| Plugin | framer-plugin |
UI that runs inside Framer editor |
| Server API | framer-api |
Headless CMS sync, CI/CD publishing |
| Code Components | React in Framer | Custom components on the canvas |
| Code Overrides | React in Framer | Modify existing component behavior |
Step 2: Set Up a Framer Plugin
# Scaffold a new plugin project
npx create-framer-plugin@latest my-plugin
cd my-plugin
npm install
npm run dev
This creates a Vite + React project with the framer-plugin package pre-configured. The plugin runs inside Framer's editor iframe.
Step 3: Set Up the Server API (Headless Access)
npm install framer-api
// server.ts — Server API connection
import { framer } from 'framer-api';
const client = await framer.connect({
apiKey: process.env.FRAMER_API_KEY!, // From site settings
siteId: process.env.FRAMER_SITE_ID!,
});
// List all CMS collections
const collections = await client.getCollections();
console.log('Collections:', collections.map(c => c.name));
Step 4: Configure Environment Variables
# .env (NEVER commit)
FRAMER_API_KEY=framer_sk_abc123...
FRAMER_SITE_ID=abc123def456
# .gitignore
.env
.env.local
Step 5: Verify Plugin Connection
Open Framer, go to your project, click Plugins > Development > select your local plugin. The dev server hot-reloads into the editor.
// Plugin verification — runs inside Framer editor
import { framer } from 'framer-plugin';
export function App() {
const handleTest = async () => {
const selection = await framer.getSelection();
console.log('Selected layers:', selection.length);
framer.notify(`Found ${selection.length} selected layers`);
};
return <button onClick={handleTest}>Test Connection</button>;
}
Output
- Plugin project scaffolded with Vite + React
- Server API client connected via WebSocket
- Environment variables configur
Configure Framer local development with hot reload and testing.
Framer Local Dev Loop
Overview
Set up a fast development workflow for Framer plugins and code components with Vite hot-reload, TypeScript, and testing.
Prerequisites
- Completed
framer-install-authsetup - Node.js 18+ with npm
- Framer editor open
Instructions
Step 1: Plugin Dev Environment
npx create-framer-plugin@latest my-plugin
cd my-plugin
npm install
npm run dev # Starts Vite dev server — hot-reloads into Framer editor
Project structure:
my-plugin/
├── src/
│ ├── App.tsx # Plugin UI (React)
│ ├── main.tsx # Entry point
│ └── framer.d.ts # Type definitions
├── package.json
├── vite.config.ts # Vite config with framer-plugin
└── tsconfig.json
Step 2: Connect to Framer Editor
- Open Framer, go to your project
- Click Plugins > Development in the toolbar
- Select your local dev plugin
- Changes in
src/App.tsxhot-reload instantly
Step 3: Testing Plugin Logic
// tests/sync.test.ts — test data transformation outside Framer
import { describe, it, expect } from 'vitest';
// Extract pure functions for testability
function transformPosts(posts: any[]) {
return posts.map(p => ({
fieldData: {
title: p.title,
body: `<p>${p.body}</p>`,
slug: p.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50),
},
}));
}
describe('CMS Sync', () => {
it('should transform posts into CMS items', () => {
const posts = [{ title: 'Hello World', body: 'Content here', userId: 1 }];
const items = transformPosts(posts);
expect(items[0].fieldData.title).toBe('Hello World');
expect(items[0].fieldData.slug).toBe('hello-world');
expect(items[0].fieldData.body).toContain('<p>');
});
it('should handle slugs with special characters', () => {
const posts = [{ title: 'What\'s New in 2025?', body: 'test', userId: 1 }];
const items = transformPosts(posts);
expect(items[0].fieldData.slug).toBe('what-s-new-in-2025-');
});
});
Step 4: Code Component Development
# Code components are edited directly in Framer editor
# For local development of shared component libraries:
mkdir framer-components && cd framer-components
npm init -y
npm install react framer typescript @types/react
// components/Button.tsx — develop locally, paste into Framer
import { addPropertyControls, ControlType } from 'framer';
export default function Button({ label = 'Click me', variant = 'primary' }) {
const styles = {
primary: { backOptimize Framer API performance with caching, batching, and connection pooling.
Framer Performance Tuning
Overview
Optimize Framer plugin, component, and Server API performance. Key areas: CMS sync speed, component render performance, and plugin responsiveness.
Instructions
Step 1: Batch CMS Operations
// Process large collections in batches to avoid timeouts
async function batchSync(collection: any, items: any[], batchSize = 50) {
const total = items.length;
for (let i = 0; i < total; i += batchSize) {
await collection.setItems(items.slice(i, i + batchSize));
const progress = Math.min(i + batchSize, total);
framer.notify(`Synced ${progress}/${total}`);
}
}
Step 2: Optimize Code Component Rendering
import { memo, useMemo } from 'react';
// Memoize expensive components
export default memo(function DataGrid({ data, columns }) {
const processedData = useMemo(() =>
data.map(row => columns.reduce((acc, col) => ({ ...acc, [col.key]: row[col.key] }), {})),
[data, columns]
);
return (
<div style={{ display: 'grid', gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}>
{processedData.map((row, i) => columns.map(col => (
<div key={`${i}-${col.key}`} style={{ padding: 8 }}>{row[col.key]}</div>
)))}
</div>
);
});
Step 3: Persistent WebSocket Connection
// Reuse Server API connection instead of reconnecting each time
let clientInstance: any = null;
async function getClient() {
if (!clientInstance) {
const { framer } = await import('framer-api');
clientInstance = await framer.connect({
apiKey: process.env.FRAMER_API_KEY!,
siteId: process.env.FRAMER_SITE_ID!,
});
}
return clientInstance;
}
Step 4: Image Optimization
// Pre-optimize image URLs before syncing to CMS
function optimizeImageUrl(url: string, width = 800): string {
// Use image CDN if available
if (url.includes('cloudinary.com')) {
return url.replace('/upload/', `/upload/w_${width},q_auto,f_auto/`);
}
if (url.includes('imgix.net')) {
return `${url}?w=${width}&auto=format,compress`;
}
return url;
}
Output
- Batched CMS operations avoiding timeouts
- Memoized components for render performance
- Persistent WebSocket connections
- Image optimization before CMS sync
Resources
Next Steps
For cost optimization, see framer-cost-tuning.
Execute Framer production deployment checklist and rollback procedures.
Framer Production Checklist
Overview
Checklist for deploying Framer plugins, code components, and Server API integrations to production.
Checklist
Plugin Production
- [ ] Plugin tested in Framer editor with real data
- [ ] No
console.logcalls remaining - [ ] Error states handled (network failures, API errors)
- [ ] Loading states shown during async operations
- [ ] Plugin UI responsive at configured width/height
- [ ] All property controls have default values
Code Components
- [ ]
export defaulton all components - [ ]
addPropertyControlson all components - [ ] Responsive at all viewport sizes
- [ ] No hardcoded data (use property controls or CMS)
- [ ] Error boundaries for data-fetching components
- [ ] Performance tested with large datasets
Server API (CMS Sync)
- [ ] API key in secrets vault (not
.env) - [ ] CMS collection schema matches source data
- [ ] Incremental sync (not full replace every run)
- [ ] Error handling with notifications
- [ ] Publish step tested
- [ ] Rate limiting for batch operations
Site Publishing
- [ ] Custom domain configured
- [ ] SEO meta tags on all pages
- [ ] CMS collections populated
- [ ] Code components rendering correctly in preview
- [ ] Code overrides working in published site
Output
- Verified plugin, components, and CMS sync
- Production API key secured
- Site published and accessible
Resources
Next Steps
For version upgrades, see framer-upgrade-migration.
Implement Framer rate limiting, backoff, and idempotency patterns.
Framer Rate Limits
Overview
Handle Framer API rate limits for Server API and plugin operations. The Server API uses WebSocket, so rate limits apply per-connection. CMS operations are limited by collection size and concurrent writes.
Rate Limit Reference
| Operation | Limit | Notes |
|---|---|---|
| Server API connections | 1 per site | WebSocket, persistent |
| CMS setItems | ~100 items/call | Batch larger sets |
| CMS getItems | No hard limit | Returns all items |
| Plugin API calls | Debounced | Framer throttles internally |
| Publish | ~1/minute | Site publishing |
| Image upload | Concurrent limit | Via CMS image fields |
Instructions
Step 1: Batch CMS Writes
async function batchSetItems(collection: any, items: any[], batchSize = 100) {
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
await collection.setItems(batch);
console.log(`Synced ${Math.min(i + batchSize, items.length)}/${items.length}`);
if (i + batchSize < items.length) {
await new Promise(r => setTimeout(r, 1000)); // 1s between batches
}
}
}
Step 2: Debounced Plugin Operations
// Debounce rapid plugin UI interactions
function debounce<T extends (...args: any[]) => any>(fn: T, ms = 300) {
let timer: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
const debouncedSync = debounce(async () => {
await syncCollection();
}, 500);
Step 3: Retry for Server API
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let i = 0; i <= maxRetries; i++) {
try {
return await fn();
} catch (err: any) {
if (i === maxRetries) throw err;
const delay = 1000 * Math.pow(2, i);
console.log(`Retry ${i + 1} in ${delay}ms`);
await new Promise(r => setTimeout(r, delay));
}
}
throw new Error('Unreachable');
}
Error Handling
| Error | Cause | Solution |
|---|---|---|
| WebSocket disconnected | Connection timeout | Reconnect with backoff |
| setItems slow | Large batch | Split into chunks of 100 |
| Publish rate limited | Too frequent | Wait 60s between publishes |
Resources
Implement Framer reference architecture with best-practice project layout.
Framer Reference Architecture
Overview
Production architecture for Framer integrations covering plugins, Server API CMS sync, code components, and automated publishing pipelines.
Architecture Diagram
┌─────────────────────────────────────────────┐
│ Framer Editor │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ │ Plugins │ │ Code │ │ Code │ │
│ │ (iframe) │ │ Components│ │ Overrides│ │
│ └────┬─────┘ └───────────┘ └──────────┘ │
│ │ framer-plugin SDK │
├───────┴──────────────────────────────────────┤
│ Framer CMS │
│ ┌──────────────┐ ┌───────────────────┐ │
│ │ Managed │ │ Unmanaged │ │
│ │ Collections │ │ Collections │ │
│ └──────┬───────┘ └───────────────────┘ │
├─────────┴────────────────────────────────────┤
│ Server API (WebSocket) │
│ framer-api package │
└─────────┬────────────────────────────────────┘
│
┌─────────┴────────────────────────────────────┐
│ Your Backend │
│ ┌────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CMS Sync │ │ Webhook │ │ CI/CD │ │
│ │ Service │ │ Handler │ │ Pipeline │ │
│ └────────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────┘
Project Structure
framer-integration/
├── plugin/ # Framer editor plugin
│ ├── src/
│ │ ├── App.tsx # Plugin UI
│ │ ├── hooks/ # Plugin state hooks
│ │ └── cms/ # CMS sync logic
│ ├── vite.config.ts
│ └── package.json
├── server/ # Server API backend
│ ├── src/
│ │ ├── sync.ts # CMS sync service
│ │ ├── publish.ts # Auto-publish logic
│ │ └── webhooks.ts # External webhook handlers
│ └── package.json
├── components/ # Shared code components
│ ├── AnimatedCounter.tsx
│ ├── DataList.tsx
│ └── index.ts
├── overrides/ # Shared code overrides
│ ├── animations.tsx
│ └── interactions.tsx
└── .env.example
Integration Patterns
| Pattern | When | How |
|---|---|---|
| Plugin CMS Sync | User-initiated sync | Plugin UI → managed collection |
| Server API Sync | Automated/scheduled | Node.js → Server API WebSocket |
| CI/CD Publish | On content change | GitHub Actions → Server API → publish |
| Webhook Bridge | External CMS events | Webhook → your API → Server API |
Decision Matrix
| Need | Solution | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Custom UI in editor | Framer Plugin | ||||||||||||
| Headless CMS sync | Server A
Apply production-ready Framer SDK patterns for TypeScript and Python.
ReadWriteEdit
Framer SDK PatternsOverviewProduction-ready patterns for Framer plugins, Server API, code components, and CMS integrations. Covers plugin architecture, type-safe CMS operations, and reusable component patterns. Prerequisites
InstructionsStep 1: Type-Safe CMS Operations
Step 2: Plugin State Management
Step 3: Reusable Component PatternsApply Framer security best practices for secrets and access control.
ReadWriteGrep
Framer Security BasicsOverviewSecurity best practices for Framer API keys, plugin development, and Server API access. InstructionsStep 1: Credential Management
Step 2: Plugin Security
Step 3: Server API Key Rotation
Step 4: Security Checklist
ResourcesNext StepsFor production deployment, see Analyze, plan, and execute Framer SDK upgrades with breaking change detection.
ReadWriteEditBash(npm:*)Bash(git:*)
Framer Upgrade & MigrationOverviewGuide for upgrading Framer plugin SDK, Server API, and migrating between Framer platform versions. Check the Framer Developer Changelog for breaking changes before upgrading. InstructionsStep 1: Check Current Versions
Step 2: Review ChangelogVisit https://www.framer.com/developers/changelog for breaking changes. Key migrations:
Step 3: Upgrade Plugin SDK
Step 4: Common Migration Patterns
Step 5: Rollback
ResourcesNext StepsFor CI integration, see Implement Framer webhook signature validation and event handling.
ReadWriteEditBash(curl:*)
Framer Webhooks & EventsOverviewFramer's Server API uses a WebSocket channel for real-time communication, not traditional REST webhooks. For event-driven integrations, you subscribe to changes via the WebSocket connection or set up your own webhook endpoints that trigger Framer sync via the Server API. InstructionsStep 1: Subscribe to CMS Changes via Server API
Step 2: External Webhook → Framer Sync
Step 3: Plugin Event Subscriptions
Output
ResourcesNext StepsFor perfor Ready to use framer-pack?Related Pluginsai-ethics-validatorAI ethics and fairness validation ai-experiment-loggerTrack and analyze AI experiments with a web dashboard and MCP tools ai-ml-engineering-packProfessional AI/ML Engineering toolkit: Prompt engineering, LLM integration, RAG systems, AI safety with 12 expert plugins ai-sdk-agentsMulti-agent orchestration with AI SDK v5 - handoffs, routing, and coordination for any AI provider (OpenAI, Anthropic, Google) anomaly-detection-systemDetect anomalies and outliers in data automl-pipeline-builderBuild AutoML pipelines
Tags
framersaassdkintegration
|