/**
* Parse Figma URLs into structured components.
*
* Supports /design/, /file/, /proto/, /board/ prefixes.
* File key is the 22-char alphanumeric segment after the prefix.
* Node ID extracted from ?node-id= query param (hyphens converted to colons).
*/
export interface ParsedFigmaUrl {
fileKey: string;
nodeId?: string;
}
const VALID_PREFIXES = new Set(['design', 'file', 'proto', 'board']);
const FILE_KEY_PATTERN = /^[a-zA-Z0-9]{22}$/;
export function parseFigmaUrl(url: string): ParsedFigmaUrl {
let parsed: URL;
try {
parsed = new URL(url);
} catch {
throw new Error(`Invalid URL: ${url}`);
}
if (parsed.hostname !== 'www.figma.com' && parsed.hostname !== 'figma.com') {
throw new Error(`Not a Figma URL: expected figma.com host, got "${parsed.hostname}"`);
}
// Path segments: ['', 'design', '<fileKey>', '<title>', ...]
const segments = parsed.pathname.split('/').filter(Boolean);
if (segments.length < 2) {
throw new Error(`Invalid Figma URL: missing file type or file key in path "${parsed.pathname}"`);
}
const prefix = segments[0];
if (!VALID_PREFIXES.has(prefix)) {
throw new Error(
`Invalid Figma URL: unsupported path prefix "${prefix}". Expected one of: ${[...VALID_PREFIXES].join(', ')}`,
);
}
const fileKey = segments[1];
if (!FILE_KEY_PATTERN.test(fileKey)) {
throw new Error(
`Invalid Figma URL: file key "${fileKey}" does not match expected 22-character alphanumeric format`,
);
}
const result: ParsedFigmaUrl = {fileKey};
const nodeIdParam = parsed.searchParams.get('node-id');
if (nodeIdParam) {
// Figma uses hyphens in URL params but colons in API calls
result.nodeId = nodeIdParam.replace(/-/g, ':');
}
return result;
}