npm.io
0.2.1 • Published 15h ago

n8n-nodes-framer-images

Licence
MIT
Version
0.2.1
Deps
1
Size
339 kB
Vulns
0
Weekly
0

n8n Framer Custom Node (Docker)

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

Prerequisites

Quick start

# 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. Test2test2.

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.

{
  "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:

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

Development workflow

After changing TypeScript source files:

# 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.

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):

npm run test:docker          # unit + mock integration only
npm run test:docker:e2e      # live E2E (skipped if no FRAMER_* vars)
Full Docker test run
# 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 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.

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

# 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

Keywords