Claude-code-plugins-plus figma-data-handling

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/figma-pack/skills/figma-data-handling" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-figma-data-handling && rm -rf "$T"
manifest: plugins/saas-packs/figma-pack/skills/figma-data-handling/SKILL.md
source content

Figma Data Handling

Overview

Work with Figma's data APIs: comments, version history, and user information. Handle sensitive data correctly with redaction and privacy compliance.

Prerequisites

  • FIGMA_PAT
    with appropriate scopes (
    file_comments:read/write
    ,
    file_versions:read
    )
  • Understanding of GDPR/CCPA basics

Instructions

Step 1: Comments API

const PAT = process.env.FIGMA_PAT!;
const FILE_KEY = process.env.FIGMA_FILE_KEY!;

// GET /v1/files/:key/comments -- requires file_comments:read scope
async function getComments(fileKey: string) {
  const res = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/comments`,
    { headers: { 'X-Figma-Token': PAT } }
  );
  const data = await res.json();

  // data.comments is an array of:
  // { id, message, file_key, parent_id, user, client_meta, resolved_at, created_at, order_id }
  return data.comments;
}

// GET with as_md=true to get rich-text comments as markdown
async function getCommentsAsMarkdown(fileKey: string) {
  const res = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/comments?as_md=true`,
    { headers: { 'X-Figma-Token': PAT } }
  );
  return (await res.json()).comments;
}

// POST /v1/files/:key/comments -- requires file_comments:write scope
async function postComment(fileKey: string, message: string, nodeId?: string) {
  const body: any = { message };
  if (nodeId) {
    body.client_meta = { node_id: nodeId };
  }

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

// POST reactions to a comment -- requires file_comments:write
async function reactToComment(fileKey: string, commentId: string, emoji: string) {
  return fetch(
    `https://api.figma.com/v1/files/${fileKey}/comments/${commentId}/reactions`,
    {
      method: 'POST',
      headers: {
        'X-Figma-Token': PAT,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ emoji }),
    }
  ).then(r => r.json());
}

Step 2: Version History API

// GET /v1/files/:key/versions -- requires file_versions:read scope
async function getVersionHistory(fileKey: string) {
  const res = await fetch(
    `https://api.figma.com/v1/files/${fileKey}/versions`,
    { headers: { 'X-Figma-Token': PAT } }
  );
  const data = await res.json();

  // data.versions: array of { id, created_at, label, description, user }
  // Ordered by created_at (most recent first)
  return data.versions;
}

// Paginate through all versions
async function getAllVersions(fileKey: string) {
  const versions: any[] = [];
  let url: string | null = `https://api.figma.com/v1/files/${fileKey}/versions`;

  while (url) {
    const res = await fetch(url, { headers: { 'X-Figma-Token': PAT } });
    const data = await res.json();
    versions.push(...data.versions);

    // Pagination uses cursor-based pagination
    url = data.pagination?.next_page
      ? `https://api.figma.com/v1/files/${fileKey}/versions?before=${data.pagination.next_page}`
      : null;
  }

  return versions;
}

Step 3: User Data and Privacy

// GET /v1/me -- returns authenticated user
interface FigmaUser {
  id: string;
  handle: string;
  img_url: string;
  email: string;   // PII -- handle carefully
}

// Redact PII before logging or storing
function redactFigmaUser(user: FigmaUser): Omit<FigmaUser, 'email'> & { email: string } {
  return {
    ...user,
    email: '[REDACTED]',
    img_url: '[REDACTED]',
  };
}

// Data classification for Figma responses
interface DataClassification {
  field: string;
  sensitivity: 'public' | 'internal' | 'pii';
  handling: string;
}

const figmaDataClassification: DataClassification[] = [
  { field: 'user.email', sensitivity: 'pii', handling: 'Encrypt at rest, redact in logs' },
  { field: 'user.handle', sensitivity: 'internal', handling: 'Do not expose to unauthorized users' },
  { field: 'user.img_url', sensitivity: 'pii', handling: 'Do not cache without consent' },
  { field: 'file.name', sensitivity: 'internal', handling: 'Standard handling' },
  { field: 'comment.message', sensitivity: 'internal', handling: 'May contain PII -- scan before storing' },
  { field: 'PAT token', sensitivity: 'pii', handling: 'Never log, never store in code' },
];

Step 4: Data Retention

// Figma image export URLs expire after 30 days
// Plan data retention accordingly

interface CachedFigmaData {
  data: any;
  fetchedAt: Date;
  expiresAt: Date;
}

function createCacheEntry(data: any, ttlMs: number): CachedFigmaData {
  const now = new Date();
  return {
    data,
    fetchedAt: now,
    expiresAt: new Date(now.getTime() + ttlMs),
  };
}

// Cleanup expired entries
async function cleanupExpiredData(db: any) {
  const now = new Date();
  const deleted = await db.figmaCache.deleteMany({
    expiresAt: { $lt: now },
  });
  console.log(`Cleaned up ${deleted.count} expired Figma cache entries`);
}

Step 5: Safe Logging

// Never log these fields from Figma responses
const REDACT_FIELDS = ['email', 'img_url', 'access_token', 'refresh_token'];

function safeFigmaLog(label: string, data: any) {
  const safe = JSON.parse(JSON.stringify(data));

  function redact(obj: any) {
    for (const key of Object.keys(obj)) {
      if (REDACT_FIELDS.includes(key)) {
        obj[key] = '[REDACTED]';
      } else if (typeof obj[key] === 'object' && obj[key] !== null) {
        redact(obj[key]);
      }
    }
  }

  redact(safe);
  console.log(`[figma] ${label}:`, JSON.stringify(safe));
}

Output

  • Comments fetched and posted via REST API
  • Version history retrieved with pagination
  • PII redacted before logging and storage
  • Data retention policies applied

Error Handling

ErrorCauseSolution
403 on commentsMissing
file_comments:read
scope
Regenerate PAT with scope
Empty version historyNew file with no saved versionsCreate a named version in Figma first
PII in logsMissing redactionApply
safeFigmaLog
wrapper
Stale image URLsURLs older than 30 daysRe-export images; do not cache URLs long-term

Resources

Next Steps

For enterprise access control, see

figma-enterprise-rbac
.