agentsclimarketplace

Figma data handling

Skill jeremylongshore/claude-code-plugins-plus-skills/skills/.curated/figma-data-handling

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill figma-data-handling

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Handle Figma API data correctly: comments, versions, user data, and privacy compliance.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.8 KB, as published. Nobody here has run it

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 scopeRegenerate 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

Examples

Pull the latest comments as Markdown and post a reaction (Step 1 Comments API):

curl -s -H "X-Figma-Token: ${FIGMA_PAT}" \
  "https://api.figma.com/v1/files/${FIGMA_FILE_KEY}/comments?as_md=true" \
  | jq -r '.comments[0] | "\(.user.handle): \(.message)"'
# mia.designer: Updated the button radius to 8px — ready for review

Walk version history with pagination (Step 2):

curl -s -H "X-Figma-Token: ${FIGMA_PAT}" \
  "https://api.figma.com/v1/files/${FIGMA_FILE_KEY}/versions" \
  | jq '{versions: [.versions[] | {id, created_at, label}], next: .pagination.next_page}'

PII rules for what you may persist from these payloads (user handles, avatars, emails): references/user-data-and-privacy.md and references/safe-logging.md.

Resources

Next Steps

For enterprise access control, see figma-enterprise-rbac.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.