# n8n-nodes-framer-images

> n8n community node to upload images to Framer and get permanent CDN URLs

Latest version **0.2.1** (published 2026-09-24) · MIT license · 0 weekly downloads

## Install

```sh
npm install n8n-nodes-framer-images
pnpm add n8n-nodes-framer-images
yarn add n8n-nodes-framer-images
bun add n8n-nodes-framer-images
```

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.2.1 |
| Published | 2026-09-24 |
| First published | 2026-07-13 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 1 |
| Unpacked size | 339.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | wdwell |
| Keywords | n8n-community-node-package, framer, images |

## Links

- npm: https://www.npmjs.com/package/n8n-nodes-framer-images
- Repository: https://github.com/sophyte/framer-node
- Homepage: https://github.com/sophyte/framer-node#readme
- Issues: https://github.com/sophyte/framer-node/issues
- npm.io page: https://npm.io/package/n8n-nodes-framer-images

## Dependencies (1)

- [framer-api](https://npm.io/package/framer-api.md) ^0.1.17

## Alternatives

- [exif-parser](https://npm.io/package/exif-parser.md) — 3.8M weekly downloads
- [vite-plugin-compression](https://npm.io/package/vite-plugin-compression.md) — 569.5K weekly downloads
- [pica](https://npm.io/package/pica.md) — 442.4K weekly downloads
- [@reportportal/client-javascript](https://npm.io/package/@reportportal/client-javascript.md) — 408.8K weekly downloads
- [@tldraw/state](https://npm.io/package/@tldraw/state.md) — 316.0K weekly downloads

## Recent versions

- 0.2.1 (latest) — 2026-09-24
- 0.2.0-strapi.1 (next) — 2026-09-24
- 0.2.0 — 2026-09-24
- 0.1.0 — 2026-07-13

## README

# n8n Framer Custom Node (Docker)

Develop and run a custom n8n community node without a local Node.js install. Everything builds inside Docker.

## Prerequisites

- [Docker Desktop](https://www.docker.com/products/docker-desktop/) (includes Docker Compose)

## Quick start

```powershell
# 1. Build the custom node inside Docker
docker compose --profile build run --rm node-build

# 2. Start n8n
docker compose up -d

# 3. Open n8n
start http://localhost:5678
```

Your **Framer** node supports CMS sync via the Framer Server API:

- **Get Collections** — verify credentials and list CMS collections
- **Get Collection Fields** — discover field IDs for mapping
- **Create or Update Item** — upsert CMS content with draft/published status and optional site publish/deploy
- **Publish Document** — write one item into existing CMS fields. Add a row per field. Formatted text fields receive Markdown or HTML, image fields receive a public URL, and plain fields receive the value as-is. Image URLs are not rewritten. Fields are not created.
- **Publish Strapi Entry** — point a Strapi `entry.publish` or `entry.unpublish` webhook at this operation. Fields are created from the entry. Blocks become Markdown. Unpublish marks the Framer item as a draft. An optional Item ID overrides matching.
- **Sync Table to Collection** — bulk 1:1 sync of all upstream records (e.g. full Airtable table) into a CMS collection

### Credential setup

1. In Framer, open **Site Settings → General** and create an API key
2. Copy your project URL from the browser (e.g. `https://framer.com/projects/Sites--abc123`)
3. In n8n, create a **Framer API** credential with both values
4. Run **Get Collections** in a workflow to verify the connection

### Field mapping (Airtable and other sources)

1. Add **Airtable → List Records** and connect it to **Framer → Create or Update Item**
2. Select your **Collection** and set **Field Input Mode** to **Select Fields to Auto-Map**
3. Choose **Field Sync Scope**:
   - **Selected Fields Only** — add source column names under **Source Fields** (e.g. Title, Body). Only listed columns sync.
   - **All Incoming Fields** — syncs every top-level field from incoming items automatically.
4. Field names are read from incoming `$json.fields` at runtime. Missing Framer fields are **created automatically**.
5. **Slug** defaults to **Auto from Source Field** — reads the **Slug Source Field** column from `$json.fields` (default: `Name`) and slugifies it, e.g. `Test2` → `test2`.

Airtable columns live under `$json.fields`. Column name → Framer field name, cell value → field content.

Airtable `multipleAttachments` image columns sync automatically in **Select Fields to Auto-Map** and **Sync Table** modes (first image wins). Removing all attachments in Airtable clears the Framer image field. Non-image-only attachment cells also clear the image field.

### Sync whole table (Airtable → Framer)

For a full 1:1 mirror of an Airtable table into a Framer CMS collection:

1. Add **Airtable → List Records** and connect it to **Framer → Sync Table to Collection**
2. Select your target **Collection**
3. Set **Slug Source Field** (default: `Name`) — used to generate URL slugs for Framer CMS items
4. Leave **Delete Orphaned Items** enabled to remove Framer items no longer in Airtable (full mirror)
5. Enable **Delete Orphaned Fields** to remove Framer CMS columns that are not in the Airtable table (schema mirror). Slug and system fields are always kept. This deletes stored data on all items for removed fields.
6. For exact Airtable column order (including empty columns), set **Field Source** to **From Airtable Table Schema** and pass one upstream table schema object to **Airtable Table Schema**, e.g. `{{ $('Table Schema').first().json }}`
7. Run the workflow — missing Framer fields are created automatically

Re-runs update existing items by **Airtable record ID** (stored in an auto-created `Airtable ID` field), so duplicate slug errors should not occur when data is unchanged. Rows removed from Airtable are deleted from Framer on the next sync.

**Table schema format:** pass a single table object with a `fields` array. A one-element array `[{ ... }]` is also accepted. A Code node upstream can extract one table from Get Base Schema if needed.

```json
{
  "name": "Articles",
  "fields": [
    { "id": "fld1", "name": "Name", "type": "singleLineText" },
    { "id": "fld2", "name": "Body", "type": "multilineText" }
  ]
}
```

**Safety:** If upstream returns 0 records, nothing is deleted unless you enable **Allow Empty Source to Clear Collection**.

The node returns a single summary: `created`, `updated`, `deleted`, `deletedFields`, `deletedFieldNames`, `skippedSchemaFields`, `fieldOrderApplied`, `syncedSourceFields`, etc.

### JSON field data (advanced)

Use **Get Collection Fields** first to find field IDs, then pass JSON like:

```json
{
  "fieldId123": { "type": "string", "value": "My Title" },
  "fieldId456": { "type": "boolean", "value": true }
}
```

## Development workflow

After changing TypeScript source files:

```powershell
# Rebuild the node
docker compose --profile build run --rm node-build

# Restart n8n to load changes
docker compose restart n8n
```

## Testing

All tests can run **entirely inside Docker** — no local Node.js install needed.

### Docker commands (recommended)

| What | Command |
|---|---|
| Unit + mock integration (142 tests, no credentials) | `docker compose -f docker-compose.test.yml run --rm test-unit` |
| Live Framer + n8n E2E (needs credentials in `.env` or `.env.test`) | `docker compose -f docker-compose.test.yml --env-file .env.test run --rm test-e2e` |
| Full suite: build + unit + E2E + n8n smoke | See **Full Docker test run** below |

Shortcut npm scripts (still use Docker under the hood):

```powershell
npm run test:docker          # unit + mock integration only
npm run test:docker:e2e      # live E2E (skipped if no FRAMER_* vars)
```

### Full Docker test run

```powershell
# 1. Copy and fill credentials (Framer required for live E2E; n8n API key optional for smoke tests)
copy .env.test.example .env.test

# 2. Build the node first (uses existing node-build service)
docker compose --profile build run --rm node-build

# 3a. Unit tests only — always works, no credentials
docker compose -f docker-compose.test.yml run --rm test-unit

# 3b. Live E2E against Framer sandbox
docker compose -f docker-compose.test.yml --env-file .env.test run --rm test-e2e

# 3c. Everything: starts n8n in Docker, rebuilds, runs unit + E2E
docker compose -f docker-compose.test.yml --env-file .env.test --profile test up -d n8n-test
docker compose -f docker-compose.test.yml --env-file .env.test --profile test run --rm test-full
docker compose -f docker-compose.test.yml --profile test down
```

Live E2E tests **auto-skip** when `FRAMER_PROJECT_URL` / `FRAMER_API_KEY` are unset — so `test-unit` always passes cleanly.

### Local commands (only if you have Node.js installed)

| Command | What it runs |
|---|---|
| `npm test` | Unit + mocked integration tests |
| `npm run test:e2e` | Live Framer + n8n smoke tests |
| `npm run test:coverage` | Unit/integration with coverage |

### Live E2E credentials

Copy [`.env.test.example`](.env.test.example) to `.env.test` and set:

- `FRAMER_PROJECT_URL` / `FRAMER_API_KEY` — Framer sandbox project (required for live sync tests)
- `FRAMER_TEST_COLLECTION_ID` — optional dedicated test collection
- `N8N_API_KEY` — optional; enables n8n smoke tests (K9/K10). Create one in n8n **Settings → API**.

For `test-e2e` without the `test` profile, point `N8N_BASE_URL` at your running n8n (`http://host.docker.internal:5678` is the default).

Documented sync behaviors live in [`tests/BEHAVIOR.md`](tests/BEHAVIOR.md).

## Project layout

```
framer-node/
├── credentials/          # n8n credential types
├── nodes/Framer/         # Framer node source
├── dist/                 # Compiled output (generated, mounted into n8n)
├── docker-compose.yml    # n8n + node builder services
└── scripts/              # Build helpers
```

## How it works

- **node-build** — Uses `node:22-alpine` to run `npm install` and `npm run build`. No local Node.js required.
- **n8n** — Official image `docker.n8n.io/n8nio/n8n`. Your compiled `dist/` folder is mounted at `/home/node/.n8n/custom` and loaded via `N8N_CUSTOM_EXTENSIONS`.
- **n8n_data** — Docker volume persists workflows, credentials, and settings across restarts.

## Useful commands

```powershell
# View n8n logs
docker compose logs -f n8n

# Stop everything
docker compose down

# Stop and remove persisted n8n data (fresh start)
docker compose down -v
```

## Configuration

Copy `.env` values to adjust host, timezone, or webhook URL:

| Variable | Default |
|---|---|
| `N8N_HOST` | `localhost` |
| `N8N_PROTOCOL` | `http` |
| `WEBHOOK_URL` | `http://localhost:5678/` |
| `GENERIC_TIMEZONE` | `Europe/Berlin` |

---
_Source: https://npm.io/package/n8n-nodes-framer-images · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
