Skip to content
Skillv1.0.0

figma-migration-deep-dive

Migrate design systems between Figma files, or from other tools to Figma via API. Use when migrating design tokens between files, syncing variables across libraries, or building automated migration pi

by jeremylongshore(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from jeremylongshore/tons-of-skills-marketplace (skills/.curated/figma-migration-deep-dive/SKILL.md). Install upstream with npx skills add jeremylongshore/tons-of-skills-marketplace --skill figma-migration-deep-dive. Copyright stays with the author (MIT).

Figma Migration Deep Dive

Overview

Automate migration of design data between Figma files, from other tools to Figma, or from Figma styles to the Variables API. Covers inventory, extraction, transformation, and validation.

Prerequisites

  • Source and destination Figma file keys
  • FIGMA_PAT with file_content:read and file_variables:write (Enterprise) scopes
  • Understanding of source file structure

Instructions

Step 1: Inventory Source File

const PAT = process.env.FIGMA_PAT!;

async function inventoryFile(fileKey: string) {
  const res = await fetch(
    `https://api.figma.com/v1/files/${fileKey}`,
    { headers: { 'X-Figma-Token': PAT } }
  );
  const file = await res.json();

  const inventory = {
    name: file.name,
    pages: file.document.children.map((p: any) => p.name),
    componentCount: Object.keys(file.components).length,
    styleCount: Object.keys(file.styles).length,
    styles: {
      fills: Object.values(file.styles).filter((s: any) => s.style_type === 'FILL').length,
      text: Object.values(file.styles).filter((s: any) => s.style_type === 'TEXT').length,
      effects: Object.values(file.styles).filter((s: any) => s.style_type === 'EFFECT').length,
      grids: Object.values(file.styles).filter((s: any) => s.style_type === 'GRID').length,
    },
  };

  // Count total nodes
  let nodeCount = 0;
  function countNodes(node: any) {
    nodeCount++;
    if (node.children) node.children.forEach(countNodes);
  }
  countNodes(file.document);
  (inventory as any).totalNodes = nodeCount;

  return inventory;
}

// Usage
const inv = await inventoryFile(process.env.FIGMA_FILE_KEY!);
console.log(`File: ${inv.name}`);
console.log(`Pages: ${inv.pages.join(', ')}`);
console.log(`Components: ${inv.componentCount}, Styles: ${inv.styleCount}`);
console.log(`Total nodes: ${(inv as any).totalNodes}`);

Step 2: Extract Styles from Source

async function extractAllStyles(fileKey: string) {
  const file = await fetch(
    `https://api.figma.com/v1/files/${fileKey}`,
    { headers: { 'X-Figma-Token': PAT } }
  ).then(r => r.json());

  const styleNodeIds = Object.keys(file.styles);
  const nodesRes = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${styleNodeIds.join(',')}`,
    { headers: { 'X-Figma-Token': PAT } }
  ).then(r => r.json());

  const extracted = [];
  for (const [nodeId, styleMeta] of Object.entries(file.styles) as any[]) {
    const node = nodesRes.nodes[nodeId]?.document;
    if (!node) continue;

    extracted.push({
      name: styleMeta.name,
      type: styleMeta.style_type,
      nodeId,
      data: {
        fills: node.fills,
        strokes: node.strokes,
        effects: node.effects,
        style: node.style,       // typography
        characters: node.characters,
      },
    });
  }

  return extracted;
}

Step 3: Transform and Map to Target

// Map extracted styles to design tokens JSON
interface MigrationToken {
  name: string;
  category: 'color' | 'typography' | 'effect';
  source: { file: string; nodeId: string };
  value: any;
}

function transformStyles(styles: any[], sourceFileKey: string): MigrationToken[] {
  return styles.map(style => {
    switch (style.type) {
      case 'FILL':
        const fill = style.data.fills?.[0];
        return {
          name: style.name,
          category: 'color' as const,
          source: { file: sourceFileKey, nodeId: style.nodeId },
          value: fill?.color
            ? {
                r: Math.round(fill.color.r * 255),
                g: Math.round(fill.color.g * 255),
                b: Math.round(fill.color.b * 255),
                a: fill.color.a ?? 1,
              }
            : null,
        };
      case 'TEXT':
        return {
          name: style.name,
          category: 'typography' as const,
          source: { file: sourceFileKey, nodeId: style.nodeId },
          value: style.data.style
            ? {
                fontFamily: style.data.style.fontFamily,
                fontSize: style.data.style.fontSize,
                fontWeight: style.data.style.fontWeight,
                lineHeight: style.data.style.lineHeightPx,
              }
            : null,
        };
      default:
        return {
          name: style.name,
          category: 'effect' as const,
          source: { file: sourceFileKey, nodeId: style.nodeId },
          value: style.data.effects,
        };
    }
  }).filter(t => t.value !== null);
}

Step 4: Write to Target (Variables API)

// Enterprise only: create variables in the target file
async function migrateToVariables(
  targetFileKey: string,
  tokens: MigrationToken[]
) {
  const colorTokens = tokens.filter(t => t.category === 'color');

  // Create variable collection and variables
  const payload = {
    variableCollections: [{
      action: 'CREATE' as const,
      id: 'temp_collection_1',
      name: 'Migrated Colors',
    }],
    variables: colorTokens.map((token, i) => ({
      action: 'CREATE' as const,
      id: `temp_var_${i}`,
      name: token.name.replace(/\//g, '/'), // preserve Figma group paths
      variableCollectionId: 'temp_collection_1',
      resolvedType: 'COLOR' as const,
      codeSyntax: { WEB: `--${token.name.toLowerCase().replace(/[\s/]+/g, '-')}` },
    })),
    variableModeValues: colorTokens.map((token, i) => ({
      variableId: `temp_var_${i}`,
      modeId: '', // Will use default mode
      value: {
        r: token.value.r / 255,
        g: token.value.g / 255,
        b: token.value.b / 255,
        a: token.value.a,
      },
    })),
  };

  const res = await fetch(
    `https://api.figma.com/v1/files/${targetFileKey}/variables`,
    {
      method: 'POST',
      headers: {
        'X-Figma-Token': PAT,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(payload),
    }
  );

  if (!res.ok) throw new Error(`Variable creation failed: ${res.status} ${await res.text()}`);
  return res.json();
}

Step 5: Validation

async function validateMigration(
  sourceFileKey: string,
  targetFileKey: string
): Promise<{ passed: boolean; issues: string[] }> {
  const sourceStyles = await extractAllStyles(sourceFileKey);
  const targetVars = await fetch(
    `https://api.figma.com/v1/files/${targetFileKey}/variables/local`,
    { headers: { 'X-Figma-Token': PAT } }
  ).then(r => r.json());

  const issues: string[] = [];
  const targetNames = new Set(
    Object.values(targetVars.meta.variables).map((v: any) => v.name)
  );

  for (const style of sourceStyles) {
    if (style.type === 'FILL' && !targetNames.has(style.name)) {
      issues.push(`Missing in target: ${style.name}`);
    }
  }

  return { passed: issues.length === 0, issues };
}

Output

  • Source file inventoried (components, styles, nodes)
  • Styles extracted and transformed to tokens
  • Tokens written to target file via Variables API
  • Migration validated with comparison report

Error Handling

Error Cause Solution
403 on Variables POST Not Enterprise Use JSON export instead of Variables API
Duplicate variable names Name collision in target Add prefix/suffix to migrated names
Missing node data Node deleted between fetch and read Re-fetch with error handling
Large file timeout File >100MB Use /nodes endpoint for specific pages

Examples

Dry-run a styles→variables migration and review the mapping before writing (Steps 2-3):

node migrate.js --source ${SOURCE_FILE_KEY} --target ${TARGET_FILE_KEY} --dry-run
48 styles found in source (32 FILL, 12 TEXT, 4 EFFECT)
32 FILL styles → color variables in collection "Primitives"
  Color/Brand/Primary  → color/brand/primary  #4F46E5
  Color/Neutral/100    → color/neutral/100    #F5F5F5
2 styles skipped: gradient fills (no variable equivalent) — kept as styles
DRY RUN — no POST to /v1/files/{key}/variables performed

Then re-run without --dry-run to write via the Variables API and validate with Step 5 (GET /v1/files/{key}/variables/local count check). Transform rules: references/transform-and-map-to-target.md.

Resources

Next Steps

For advanced troubleshooting, see figma-advanced-troubleshooting.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/jeremylongshore-tons-of-skills-marketplace-figma-migrati-0fd534/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

jeremylongshore-tons-of-skills-marketplace-figma-migrati-0fd534.ocm.jsonjson
{
  "ocm": "1",
  "id": "jeremylongshore-tons-of-skills-marketplace-figma-migrati-0fd534",
  "kind": "skill",
  "name": "figma-migration-deep-dive",
  "description": "Migrate design systems between Figma files, or from other tools to Figma via API. Use when migrating design tokens between files, syncing variables across libraries, or building automated migration pipelines for Figma. Trigger with phrases like \"migrate figma\", \"figma migration\", \"move figma library\", \"figma file migration\", \"sync figma files\".",
  "publisher": "jeremylongshore",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "coding"
    ],
    "tags": [
      "skill-md",
      "saas",
      "figma",
      "skills-sh"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Migrate design systems between Figma files, or from other tools to Figma via API. Use when migrating design tokens between files, syncing variables across libraries, or building automated migration pipelines for Figma. Trigger with phrases like \"migrate figma\", \"figma migration\", \"move figma library\", \"figma file migration\", \"sync figma files\"."
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "skills.sh",
      "repository": "https://github.com/jeremylongshore/tons-of-skills-marketplace",
      "path": "skills/.curated/figma-migration-deep-dive/SKILL.md",
      "ref": "HEAD",
      "url": "https://github.com/jeremylongshore/tons-of-skills-marketplace/blob/HEAD/skills/.curated/figma-migration-deep-dive/SKILL.md",
      "key": "jeremylongshore/tons-of-skills-marketplace/skills/.curated/figma-migration-deep-dive/SKILL.md"
    },
    "compatibility": "Designed for Claude Code",
    "allowed_tools": [
      "Read,",
      "Write,",
      "Edit,",
      "Bash(npm:*),",
      "Bash(node:*)"
    ],
    "license": "MIT"
  },
  "instructions": "# Figma Migration Deep Dive\n\n## Overview\n\nAutomate migration of design data between Figma files, from other tools to Figma, or from Figma styles to the Variables API. Covers inventory, extraction, transformation, and validation.\n\n## Prerequisites\n\n- Source and destination Figma file keys\n- `FIGMA_PAT` with `file_content:read` and `file_variables:write` (Enterprise) scopes\n- Understanding of source file structure\n\n## Instructions\n\n### Step 1: Inventory Source File\n\n```typescript\nconst PAT = process.env.FIGMA_PAT!;\n\nasync function inventoryFile(fileKey: string) {\n  const res = await fetch(\n    `",
  "cost": {
    "context_tokens": 2140
  }
}

Fetch it by URL: GET /api/v1/registry/jeremylongshore-tons-of-skills-marketplace-figma-migrati-0fd534/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.