Claude
Skills
Sign in
Back

framer-plugins

Included with Lifetime
$97 forever

Framer Plugin SDK expert. Use when building, debugging, or modifying Framer plugins. Covers ManagedCollection API, CMS sync, plugin modes, UI patterns, permissions, data storage, and common pitfalls.

Design

What this skill does


# Framer Plugin Development Guide

You are an expert on the Framer Plugin SDK. Use this reference when building, debugging, or modifying Framer plugins. Always check the project's CLAUDE.md for project-specific overrides.

## Quick Reference

- **SDK package**: `framer-plugin` (v3.6+)
- **Scaffolding**: `npm create framer-plugin@latest`
- **Build**: Vite + `vite-plugin-framer`
- **Base styles**: `import "framer-plugin/framer.css"`
- **Core import**: `import { framer } from "framer-plugin"`
- **Dev workflow**: `npm run dev` → Framer → Developer Tools → Development Plugin

## framer.json

Every plugin needs a `framer.json` at the project root:

```json
{
  "id": "6bbb4f",
  "name": "My Plugin",
  "modes": ["configureManagedCollection", "syncManagedCollection"],
  "icon": "/icon.svg"
}
```

- `id` — unique hex identifier (auto-generated by scaffolding)
- `modes` — array of supported modes (see below)
- `icon` — 30×30 SVG/PNG in `public/`. SVGs need careful centering.

## Plugin Modes

| Mode | Purpose | `framer.mode` value |
|------|---------|---------------------|
| `canvas` | General-purpose canvas access | `"canvas"` |
| `configureManagedCollection` | CMS: first-time setup / field config | `"configureManagedCollection"` |
| `syncManagedCollection` | CMS: re-sync existing collection | `"syncManagedCollection"` |
| `image` | User picks an image | `"image"` |
| `editImage` | Edit existing image | `"editImage"` |
| `collection` | Access user-editable collections | `"collection"` |

CMS plugins use both `configureManagedCollection` + `syncManagedCollection`.

## Core framer API

### UI Management

```typescript
framer.showUI({ position?, width, height, minWidth?, minHeight?, maxWidth?, resizable? })
framer.hideUI()
framer.closePlugin(message?, { variant: "success" | "error" | "info" })  // returns never
framer.notify(message, { variant?, durationMs?, button?: { text, onClick } })
framer.setCloseWarning(message | false)  // warn before closing during sync
framer.setBackgroundMessage(message)     // status while plugin runs hidden
framer.setMenu([{ label, onAction, visible? }, { type: "separator" }])
```

- `closePlugin` throws `FramerPluginClosedError` internally — always ignore in catch blocks
- `showUI` should be called in `useLayoutEffect` to avoid flicker

### Properties

- `framer.mode` — current mode string

### Collection Access

```typescript
framer.getActiveManagedCollection()    // → Promise<ManagedCollection>
framer.getActiveCollection()           // → Promise<Collection> (unmanaged)
framer.getManagedCollections()          // → Promise<ManagedCollection[]>
framer.getCollections()                // → Promise<Collection[]>
framer.createManagedCollection()       // → Promise<ManagedCollection>
```

### Canvas Methods (canvas mode)

```typescript
framer.addImage({ image, name, altText })
framer.setImage({ image, name, altText })
framer.getImage()
framer.addText(text)
framer.addFrame()
framer.addSVG(svg, name)              // max 10kB
framer.addComponentInstance({ url, attributes? })
framer.getSelection()
framer.subscribeToSelection(callback)
```

## ManagedCollection API

```typescript
interface ManagedCollection {
    id: string
    getItemIds(): Promise<string[]>
    setItemOrder(ids: string[]): Promise<void>
    getFields(): Promise<ManagedCollectionField[]>
    setFields(fields: ManagedCollectionFieldInput[]): Promise<void>
    addItems(items: ManagedCollectionItemInput[]): Promise<void>   // upsert!
    removeItems(ids: string[]): Promise<void>
    setPluginData(key: string, value: string | null): Promise<void>
    getPluginData(key: string): Promise<string | null>
}
```

**Critical**: `addItems()` is an **upsert** — it adds new items and updates existing ones matched by `id`.

### Field Types

```
"boolean" | "color" | "number" | "string" | "formattedText" |
"image" | "file" | "link" | "date" | "enum" |
"collectionReference" | "multiCollectionReference" | "array"
```

### Field Definition

```typescript
interface ManagedCollectionFieldInput {
    id: string
    name: string
    type: CollectionFieldType
    userEditable?: boolean        // default false for managed
    cases?: { id, name }[]       // for "enum"
    collectionId?: string         // for collection references
    fields?: ManagedCollectionFieldInput[]  // for "array" (gallery)
}
```

### Item Structure

```typescript
interface ManagedCollectionItemInput {
    id: string
    slug: string       // Must be unique, max 64 characters
    draft: boolean
    fieldData: Record<string, FieldDataEntryInput>
}
```

### Field Data Values — MUST specify type explicitly

```typescript
{ type: "string", value: "hello" }
{ type: "number", value: 42 }
{ type: "boolean", value: true }
{ type: "date", value: "2024-01-01T00:00:00Z" }   // ISO 8601
{ type: "link", value: "https://example.com" }
{ type: "image", value: "https://img.url" | null }
{ type: "file", value: "https://file.url" | null }
{ type: "color", value: "#FF0000" | null }
{ type: "formattedText", value: "<p>hello</p>", contentType: "html" }
{ type: "enum", value: "case-id" }
{ type: "collectionReference", value: "item-id" }
{ type: "multiCollectionReference", value: ["id1", "id2"] }
{ type: "array", value: [{ id: "1", fieldData: { ... } }] }
```

## Permissions

```typescript
import { framer, useIsAllowedTo, type ProtectedMethod } from "framer-plugin"

// Imperative check
framer.isAllowedTo("ManagedCollection.addItems", "ManagedCollection.removeItems")

// React hook (reactive)
const canSync = useIsAllowedTo("ManagedCollection.addItems", "ManagedCollection.removeItems")

// Standard CMS sync permissions
const SYNC_METHODS = [
    "ManagedCollection.setFields",
    "ManagedCollection.addItems",
    "ManagedCollection.removeItems",
    "ManagedCollection.setPluginData",
] as const satisfies ProtectedMethod[]
```

## Data Storage Decision Tree

| Need | Use | Why |
|------|-----|-----|
| API keys, auth tokens | `localStorage` | Per-user, no size warnings, not shared |
| User preferences | `localStorage` | Per-user, synchronous |
| Data source ID, last sync time | `collection.setPluginData()` | Shared across collaborators, tied to collection |
| Project-level config | `framer.setPluginData()` | Shared, but 4kB total limit |

- `pluginData`: 2kB per entry, 4kB total. Strings only. Pass `null` to delete.
- `localStorage`: Sandboxed per-plugin origin. No size warnings.
- `setPluginData()` triggers "Invoking protected message type" toast (SDK bug).

## Key Exports from "framer-plugin"

```typescript
import { framer, useIsAllowedTo, FramerPluginClosedError } from "framer-plugin"
import type {
    ManagedCollection, ManagedCollectionField, ManagedCollectionFieldInput,
    ManagedCollectionItemInput, FieldDataInput, FieldDataEntryInput,
    ProtectedMethod, Collection, CollectionItem
} from "framer-plugin"
import "framer-plugin/framer.css"
```

## Supporting References

For deeper information, see the companion files in this skill directory:

- **[api-reference.md](references/api-reference.md)** — Complete API signatures and type definitions
- **[patterns.md](references/patterns.md)** — Common plugin patterns extracted from 32 official examples
- **[pitfalls.md](references/pitfalls.md)** — Known gotchas, workarounds, and debugging tips
- **[marketplace.md](references/marketplace.md)** — Marketplace submission workflow, listing requirements, review process, plugin policies, and post-publication obligations

## Key Rules

1. Always check the project's `CLAUDE.md` for project-specific overrides and decisions
16. **Before building any new feature**, check [marketplace.md](references/marketplace.md) — the plugin must comply with Framer's policies (English UI, light+dark mode, no ads, USD-only pricing, IP ownership, etc.) or it will be rejected during the ~3-week review process
2. CMS plugins should attempt silent sync in `syncManagedCollection` mode before showing UI
3. `addItems()` is upsert — no need to check for existing items before adding
4. F
Files: 5
Size: 46.9 KB
Complexity: 51/100
Category: Design

Related in Design