From figma-pack
Implements Figma Enterprise OAuth 2.0 flows including authorization, token exchange, refresh, for team/project access management via REST API.
How this skill is triggered — by the user, by Claude, or both
Slash command
/figma-pack:figma-enterprise-rbacThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Figma Enterprise features accessible via the REST API: OAuth 2.0 for user-facing apps, team/project management, and the Variables API (Enterprise-only). This skill covers building OAuth integrations and managing organizational access.
Figma Enterprise features accessible via the REST API: OAuth 2.0 for user-facing apps, team/project management, and the Variables API (Enterprise-only). This skill covers building OAuth integrations and managing organizational access.
// Figma OAuth 2.0 Authorization Code Flow
// 1. Build authorization URL
function getAuthUrl(state: string): string {
const params = new URLSearchParams({
client_id: process.env.FIGMA_CLIENT_ID!,
redirect_uri: process.env.FIGMA_REDIRECT_URI!,
scope: 'file_content:read,file_comments:write,file_variables:read',
state,
response_type: 'code',
});
return `https://www.figma.com/oauth?${params}`;
}
// 2. Exchange authorization code for tokens (within 30 seconds!)
async function exchangeCode(code: string): Promise<{
access_token: string;
refresh_token: string;
expires_in: number;
user_id: string;
}> {
const res = await fetch('https://api.figma.com/v1/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.FIGMA_CLIENT_ID!,
client_secret: process.env.FIGMA_CLIENT_SECRET!,
redirect_uri: process.env.FIGMA_REDIRECT_URI!,
code,
grant_type: 'authorization_code',
}),
});
if (!res.ok) {
const error = await res.text();
throw new Error(`Token exchange failed: ${res.status} ${error}`);
}
return res.json();
}
// 3. Refresh expired tokens
async function refreshAccessToken(refreshToken: string): Promise<{
access_token: string;
expires_in: number;
}> {
const res = await fetch('https://api.figma.com/v1/oauth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: process.env.FIGMA_CLIENT_ID!,
client_secret: process.env.FIGMA_CLIENT_SECRET!,
refresh_token: refreshToken,
}),
});
if (!res.ok) throw new Error(`Token refresh failed: ${res.status}`);
return res.json();
}
// Express callback handler
app.get('/auth/figma/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state matches what we sent (CSRF protection)
if (state !== req.session.oauthState) {
return res.status(403).json({ error: 'Invalid state parameter' });
}
try {
// Exchange code within 30 seconds
const tokens = await exchangeCode(code as string);
// Get user info with the new token
const userRes = await fetch('https://api.figma.com/v1/me', {
headers: { 'X-Figma-Token': tokens.access_token },
});
const user = await userRes.json();
// Store tokens securely (encrypted at rest)
await saveUserTokens(user.id, {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: new Date(Date.now() + tokens.expires_in * 1000),
});
res.redirect('/dashboard?connected=figma');
} catch (error) {
console.error('Figma OAuth error:', error);
res.redirect('/settings?error=figma_auth_failed');
}
});
// GET /v1/teams/:team_id/projects -- list team projects
async function getTeamProjects(teamId: string, token: string) {
const res = await fetch(
`https://api.figma.com/v1/teams/${teamId}/projects`,
{ headers: { 'X-Figma-Token': token } }
);
return res.json(); // { projects: [{ id, name }] }
}
// GET /v1/projects/:project_id/files -- list project files
async function getProjectFiles(projectId: string, token: string) {
const res = await fetch(
`https://api.figma.com/v1/projects/${projectId}/files`,
{ headers: { 'X-Figma-Token': token } }
);
return res.json(); // { files: [{ key, name, thumbnail_url, last_modified }] }
}
// GET /v1/teams/:team_id/components -- published components (Tier 3)
async function getTeamComponents(teamId: string, token: string) {
const res = await fetch(
`https://api.figma.com/v1/teams/${teamId}/components`,
{ headers: { 'X-Figma-Token': token } }
);
return res.json();
// { meta: { components: [{ key, file_key, node_id, name, description }] } }
}
// GET /v1/teams/:team_id/styles -- published styles (Tier 3)
async function getTeamStyles(teamId: string, token: string) {
const res = await fetch(
`https://api.figma.com/v1/teams/${teamId}/styles`,
{ headers: { 'X-Figma-Token': token } }
);
return res.json();
// { meta: { styles: [{ key, file_key, node_id, name, style_type }] } }
}
// GET /v1/files/:key/variables/local -- Tier 2, requires file_variables:read
async function getLocalVariables(fileKey: string, token: string) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/variables/local`,
{ headers: { 'X-Figma-Token': token } }
);
if (res.status === 403) {
throw new Error('Variables API requires Figma Enterprise plan');
}
return res.json();
// { meta: { variables: Record<id, Variable>, variableCollections: Record<id, Collection> } }
}
// GET /v1/files/:key/variables/published -- published variables
async function getPublishedVariables(fileKey: string, token: string) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/variables/published`,
{ headers: { 'X-Figma-Token': token } }
);
return res.json();
// Published variables have a subscribed_id that changes each publish
}
// POST /v1/files/:key/variables -- bulk create/update/delete
async function updateVariables(
fileKey: string,
changes: VariableChanges,
token: string
) {
const res = await fetch(
`https://api.figma.com/v1/files/${fileKey}/variables`,
{
method: 'POST',
headers: {
'X-Figma-Token': token,
'Content-Type': 'application/json',
},
body: JSON.stringify(changes),
}
);
return res.json();
}
// Middleware that checks if user has Figma access to a resource
async function requireFigmaAccess(fileKey: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const userToken = await getUserFigmaToken(req.user.id);
if (!userToken) {
return res.status(403).json({ error: 'Figma account not connected' });
}
// Check if user's token can access this file
const check = await fetch(
`https://api.figma.com/v1/files/${fileKey}?depth=1`,
{ headers: { 'X-Figma-Token': userToken } }
);
if (check.status === 403) {
return res.status(403).json({ error: 'No access to this Figma file' });
}
next();
};
}
| Error | Cause | Solution |
|---|---|---|
| OAuth code expired | Exchange took >30s | Exchange immediately on callback |
| Token refresh failed | Refresh token revoked | Re-authenticate user through OAuth flow |
| 403 on Variables API | Not Enterprise plan | Use styles API instead (available on all plans) |
| Team components empty | No published components | Publish components in Figma first |
For major migrations, see figma-migration-deep-dive.
npx claudepluginhub luxdevnet/claude-plus-lux --plugin figma-pack5plugins reuse this skill
First indexed Jul 10, 2026
Configure Figma Enterprise features: OAuth 2.0, team management, and access control. Use when implementing OAuth flows, managing team/project access via API, or building Enterprise-level Figma integrations. Trigger with phrases like "figma enterprise", "figma OAuth", "figma team management", "figma access control", "figma SCIM".
Automates Figma file inspection, component extraction, design tokens, comments, and image exports via Composio's Figma toolkit and Rube MCP.
Automates Figma tasks via Rube MCP (Composio): files, components, design tokens, comments, exports. Always search tools first for current schemas.