@figma-vars/hooks
v4.0.0
Published
Typed React hooks for managing Figma Variables, modes, tokens, and bindings via API.
Downloads
541
Maintainers
Readme
FigmaVars/hooks
Built and maintained by Mark Learst.
A fast, typed React 19.2.3 hooks library for the Figma Variables API: fetch, update, and manage design tokens via the official Figma REST API.
Built for the modern web, this library provides a suite of hooks to fetch, manage, and mutate your design tokens/variables, making it easy to sync them between Figma and your React applications, Storybooks, or design system dashboards.
| Package | Quality | Activity |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
📌 Why 4.0.0
- ✨ New Utilities:
withRetry()for automatic retry with exponential backoff,redactToken()for safe logging - 🔧 Flexible API:
baseUrloption for fetcher/mutator,caseInsensitiveoption for filterVariables - 🛡️ Better Error Handling: Improved parsing for non-JSON API responses (HTML, plain text)
- 🐛 Critical Bug Fix: SWR cache keys now correctly separate fallback and live data
- 📚 Improved Docs: Comprehensive mutation return type documentation with examples
- 📦 Modern Tooling: Node 20+ toolchain, strict TypeScript, and ESM-first packaging with CJS interop
- 🖥️ CLI Export Tool: Automate variable exports with
figma-vars-exportfor CI/CD (Enterprise required)
⚠️ Breaking Change:
useFigmaTokenis now a named export. See Migration Guide.
🚀 Features at a Glance
- Modern React 19.2 hooks for variables, collections, modes, and published variables
- Ergonomic mutation hooks with consistent loading/error states
- SWR configuration support for customizing caching and revalidation behavior
- Error handling utilities for type-safe error checking and status code access
- Cache invalidation helpers for automatic data refresh after mutations
- CLI export tool (
figma-vars-export) for automating variable exports to JSON (Enterprise required) - Fallback JSON support (object or string) for offline/static use - works without Enterprise!
- Typed core entrypoint for non-React consumers (Axios, TanStack Query, server scripts)
- 100% test coverage + strict TypeScript + clean exports/attw/publint/size-limit checks
📦 Install
npm install @figma-vars/hooks
# or
pnpm add @figma-vars/hooksPeer deps: react and react-dom.
🖥️ CLI Export Tool
The package includes a CLI tool (figma-vars-export) for automatically exporting Figma variables to JSON via the REST API. Perfect for CI/CD pipelines, build scripts, or one-off exports.
⚠️ Enterprise Required: The CLI tool uses the Figma Variables REST API, which requires a Figma Enterprise account. Without Enterprise, use the Dev Mode plugin export method instead.
# Using npx (no install needed)
FIGMA_TOKEN=your_token npx figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
# After installing
npm install @figma-vars/hooks
FIGMA_TOKEN=your_token figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
# Show help
figma-vars-export --helpOptions:
--file-key- Figma file key (required, or setFIGMA_FILE_KEYenv var)--out- Output path (default:data/figma-variables.json)--help- Show help message
Environment Variables:
FIGMA_TOKENorFIGMA_PAT- Figma Personal Access Token (required)FIGMA_FILE_KEY- Figma file key (optional)
Example Output:
Saved variables to ./variables.json
Variables count: 42No Enterprise? See Exporting variables for fallback for alternative methods that work without Enterprise.
🛠️ Quick Start (SWR-powered hooks)
import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
const FIGMA_TOKEN = import.meta.env.VITE_FIGMA_TOKEN
const FIGMA_FILE_KEY = 'your-file-key'
function App() {
return (
<FigmaVarsProvider
token={FIGMA_TOKEN}
fileKey={FIGMA_FILE_KEY}
swrConfig={{
revalidateOnFocus: false,
dedupingInterval: 5000,
}}>
<Tokens />
</FigmaVarsProvider>
)
}
function Tokens() {
const { data, isLoading, error } = useVariables()
if (isLoading) return <div>Loading…</div>
if (error) return <div>Error: {error.message}</div>
const variables = Object.values(data?.meta.variables ?? {})
return <pre>{JSON.stringify(variables, null, 2)}</pre>
}🧩 Non-SWR Usage (Core entrypoint)
Use the /core build when you prefer Axios/TanStack/server scripts without React/SWR.
Axios example (GET + bulk PUT)
import axios from 'axios'
import { FIGMA_FILE_VARIABLES_PATH } from '@figma-vars/hooks/core'
const token = process.env.FIGMA_TOKEN!
const fileKey = process.env.FIGMA_FILE_KEY!
// Fetch local variables
const url = `https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`
const { data } = await axios.get(url, {
headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' },
})
// Bulk update
await axios.put(
`https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}`,
{ variables: [{ action: 'UPDATE', id: 'VariableId:123', name: 'new-name' }] },
{ headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' } }
)TanStack Query example
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { FIGMA_FILE_VARIABLES_PATH, fetcher, mutator } from '@figma-vars/hooks/core'
const token = process.env.FIGMA_TOKEN!
const fileKey = process.env.FIGMA_FILE_KEY!
export function useLocalVariables() {
return useQuery({
queryKey: ['figma-local', fileKey],
queryFn: () => fetcher(`${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`, token),
staleTime: 60_000,
})
}
export function useBulkUpdate() {
const qc = useQueryClient()
return useMutation({
mutationFn: (payload: unknown) =>
mutator(FIGMA_FILE_VARIABLES_PATH(fileKey), token, 'UPDATE', payload),
onSuccess: () => qc.invalidateQueries({ queryKey: ['figma-local', fileKey] }),
})
}🛡️ Fallback JSON (offline/static)
Pass fallbackFile (object or JSON string) to FigmaVarsProvider to bypass live API calls:
import exportedVariables from './figma-variables.json'
;<FigmaVarsProvider
token={null}
fileKey={null}
fallbackFile={exportedVariables}>
<App />
</FigmaVarsProvider>Exporting variables for fallback
There are several ways to get your Figma variables as JSON:
Dev Mode / plugin export (recommended, no Enterprise needed) ⭐
- Use a Variables exporter plugin in Figma Dev Mode to download the full Variables panel as JSON
- Save anywhere (e.g.,
data/figma-variables.json) and pass it tofallbackFile - Works for everyone, no Enterprise account required!
CLI export tool (Enterprise required) 🚀
- Automatically exports via REST API - perfect for CI/CD and automation
- See the CLI Export Tool section above for full usage details
- Also available from cloned repo:
node scripts/export-variables.mjs --file-key KEY --out file.json
- Desktop MCP (manual/partial): Selecting a frame and running
get_variable_defsreturns only that selection’s variables. Use plugin/REST exports for complete coverage.
- Style Dictionary
- Once you have the JSON (from any path), feed it into Style Dictionary to emit platform-specific artifacts
- Or import it directly via
fallbackFile
🔧 Mutation Hooks (verbs fixed)
useCreateVariable→ POST via bulk endpoint withaction: 'CREATE'useUpdateVariable→ PUT via bulk endpoint withaction: 'UPDATE'useDeleteVariable→ DELETE via bulk endpoint withaction: 'DELETE'useBulkUpdateVariables→ PUT bulk payload (collections, modes, variables, values)
All return { mutate, data, error, isLoading, isSuccess, isError }.
Example: Creating and updating variables
import { useCreateVariable, useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
function VariableEditor() {
const { mutate: create } = useCreateVariable()
const { mutate: update } = useUpdateVariable()
const { invalidate } = useInvalidateVariables()
const handleCreate = async () => {
await create({
name: 'Primary Color',
variableCollectionId: 'CollectionId:123',
resolvedType: 'COLOR',
})
invalidate() // Refresh cache after mutation
}
const handleUpdate = async (id: string) => {
await update({
variableId: id,
payload: { name: 'Updated Name' },
})
invalidate() // Refresh cache after mutation
}
return (
<>
<button onClick={handleCreate}>Create Variable</button>
<button onClick={() => handleUpdate('VariableId:123')}>Update</button>
</>
)
}🛡️ Error Handling
Error Boundaries (Recommended)
Wrap your Figma-connected components with an error boundary to gracefully handle errors:
import { ErrorBoundary } from 'react-error-boundary'
import { FigmaVarsProvider } from '@figma-vars/hooks'
function FigmaErrorFallback({ error }: { error: Error }) {
return (
<div role='alert'>
<h2>Failed to load Figma data</h2>
<pre>{error.message}</pre>
</div>
)
}
function App() {
return (
<ErrorBoundary FallbackComponent={FigmaErrorFallback}>
<FigmaVarsProvider
token={FIGMA_TOKEN}
fileKey={FIGMA_FILE_KEY}>
<YourApp />
</FigmaVarsProvider>
</ErrorBoundary>
)
}Note: The provider validates fallback file structure at runtime and logs warnings in development. Invalid fallback data won't crash the app but will result in
undefineddata.
Runtime Validation
Use type guards to validate data at runtime:
import { isLocalVariablesResponse, isPublishedVariablesResponse } from '@figma-vars/hooks'
// Validate before using
if (isLocalVariablesResponse(data)) {
// Safe to access data.meta.variables
}Error Utilities
v3 introduces powerful error handling utilities for type-safe error checking:
import { isFigmaApiError, getErrorStatus, getErrorMessage, hasErrorStatus } from '@figma-vars/hooks'
function ErrorHandler({ error }: { error: Error | null }) {
if (!error) return null
// Type guard for FigmaApiError
if (isFigmaApiError(error)) {
const status = error.statusCode
if (status === 401) {
return <div>Authentication required. Please check your token.</div>
}
if (status === 403) {
return <div>Access forbidden. Check file permissions.</div>
}
if (status === 429) {
return <div>Rate limit exceeded. Please wait before retrying.</div>
}
if (status === 404) {
return <div>File or variable not found.</div>
}
}
// Helper functions
const status = getErrorStatus(error) // number | null
const message = getErrorMessage(error) // string
// Convenience check
if (hasErrorStatus(error, 401)) {
// Handle unauthorized
}
return <div>Error: {message}</div>
}Common HTTP Status Codes:
401- Unauthorized (invalid or missing token)403- Forbidden (insufficient permissions)404- Not Found (file/variable doesn't exist)429- Too Many Requests (rate limit exceeded)
🔄 Cache Management
After mutations, use useInvalidateVariables to refresh cached data:
import { useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
function UpdateButton({ variableId }: { variableId: string }) {
const { mutate, isLoading } = useUpdateVariable()
const { invalidate, revalidate } = useInvalidateVariables()
const handleUpdate = async () => {
await mutate({
variableId,
payload: { name: 'New Name' },
})
// Option 1: Invalidate (refetch on next access)
invalidate()
// Option 2: Revalidate immediately (refetch now)
// revalidate()
}
return (
<button
onClick={handleUpdate}
disabled={isLoading}>
{isLoading ? 'Updating...' : 'Update Variable'}
</button>
)
}⚙️ SWR Configuration
Customize SWR behavior globally through the provider:
<FigmaVarsProvider
token={FIGMA_TOKEN}
fileKey={FIGMA_FILE_KEY}
swrConfig={{
revalidateOnFocus: false, // Don't refetch on window focus
dedupingInterval: 5000, // Dedupe requests within 5s
errorRetryCount: 3, // Retry failed requests 3 times
errorRetryInterval: 1000, // Wait 1s between retries
onError: error => {
// Global error handler
if (isFigmaApiError(error) && error.statusCode === 429) {
console.warn('Rate limited, backing off...')
}
},
}}>
<App />
</FigmaVarsProvider>Common SWR Options:
revalidateOnFocus- Refetch when window regains focus (default:true)dedupingInterval- Deduplication interval in ms (default:2000)errorRetryCount- Max retry attempts (default:5)refreshInterval- Polling interval in ms (default:0= disabled)onError- Global error callback
📚 API Cheat Sheet
Hooks
- Queries:
useVariables(local),usePublishedVariables(library/published),useVariableCollections,useVariableModes,useFigmaToken - Granular Selectors:
useCollectionById,useModesByCollection,useVariableById(optimized selectors for specific entities) - Mutations:
useCreateVariable,useUpdateVariable,useDeleteVariable,useBulkUpdateVariables - Cache:
useInvalidateVariables(invalidate/revalidate cache)
Utilities
- Filtering:
filterVariables(filter by type, name, with optionalcaseInsensitivematching) - Retry:
withRetry(automatic retry with exponential backoff for rate limits) - Security:
redactToken(safely redact tokens for logging/display) - Error Handling:
isFigmaApiError,getErrorStatus,getErrorMessage,hasErrorStatus,isRateLimited,getRetryAfter - Type Guards:
isLocalVariablesResponse,isPublishedVariablesResponse,validateFallbackData(runtime validation) - SWR Keys:
getVariablesKey,getPublishedVariablesKey,getInvalidationKeys(centralized cache key construction) - Core helpers:
fetcher,mutator(withbaseUrloption), constants for endpoints and headers
Types
- Responses:
LocalVariablesResponse,PublishedVariablesResponse - Variables:
FigmaVariable,FigmaCollection,VariableMode - Mutations:
BulkUpdatePayload,CreateVariablePayload,UpdateVariablePayload - Errors:
FigmaApiError(extendsErrorwithstatusCode)
🔐 Auth & Scope
- Header:
X-FIGMA-TOKEN: <PAT> - Scopes:
file_variables:readfor GETs,file_variables:writefor mutations. - Enterprise Full seat required for live API; fallback JSON works without a token.
⚠️ Enterprise Requirement and Offline Options
- The Figma Variables REST API requires a Figma Enterprise seat for live requests. Without Enterprise, live calls will fail even with a valid PAT.
- The library remains useful without Enterprise: supply
fallbackFile(object or JSON string) exported from Figma (Dev Mode plugin, CLI, or Figma MCP server output) and all read hooks work offline/for static deployments. - MCP/other exporters: as long as they emit the same JSON shape as the Variables API, you can feed that JSON into
fallbackFile; mutations still require Enterprise access.
🚫 Do Not Publish Tokens or File Keys
- Never commit PATs or file keys to git, Storybook static builds, or client bundles.
- Use environment variables (
process.env/import.meta.env) and secret managers; keep them server-side where possible. - Prefer
fallbackFilewithtoken={null}/fileKey={null}for demos and public Storybooks. - Use
redactToken()when logging tokens for debugging:
import { redactToken } from '@figma-vars/hooks'
// Safe logging
console.log('Using token:', redactToken(token))
// Output: "Using token: figd_***...***cret"📈 Rate Limits
- Figma enforces per-token limits. Rely on SWR/TanStack caching, avoid unnecessary refetches, and prefer fallback JSON for static sites.
- Use
swrConfigto customizededupingIntervalanderrorRetryCountto optimize API usage. - Use
withRetry()utility for automatic retry with exponential backoff on 429 errors:
import { withRetry, fetcher } from '@figma-vars/hooks'
const fetchWithRetry = withRetry(() => fetcher('/v1/files/KEY/variables/local', token), {
maxRetries: 3,
onRetry: (attempt, delay) => console.log(`Retry ${attempt}...`),
})📚 Storybook & Next.js
- Storybook decorator: wrap stories once so hooks have context and tokens.
// .storybook/preview.tsx
import { FigmaVarsProvider } from '@figma-vars/hooks'
import type { Preview } from '@storybook/react'
const FIGMA_TOKEN = process.env.STORYBOOK_FIGMA_TOKEN
const FIGMA_FILE_KEY = process.env.STORYBOOK_FIGMA_FILE_KEY
const preview: Preview = {
decorators: [
Story => (
<FigmaVarsProvider
token={FIGMA_TOKEN}
fileKey={FIGMA_FILE_KEY}>
<Story />
</FigmaVarsProvider>
),
],
}
export default preview- Next.js App Router: provide context in a shared provider file.
// app/providers.tsx
import { FigmaVarsProvider } from '@figma-vars/hooks'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<FigmaVarsProvider
token={process.env.NEXT_PUBLIC_FIGMA_TOKEN}
fileKey={process.env.NEXT_PUBLIC_FIGMA_FILE_KEY}>
{children}
</FigmaVarsProvider>
)
}🧪 Tooling & Quality Gates
pnpm run build,pnpm test,pnpm run test:coveragepnpm run check:publint,pnpm run check:attw,pnpm run check:size
🧭 Release Checklist (for 4.0.0)
- Run
pnpm run check:release - Run
pnpm version major(createsv4.0.0tag) - CI publishes to npm automatically
- Update dist-tags on npm if needed (
latest→ 4.0.0)
🔄 Migration Guide (3.x → 4.0)
Breaking Change: useFigmaToken Export
// Before (3.x) - NO LONGER WORKS
import useFigmaToken from '@figma-vars/hooks'
// After (4.0) - USE THIS
import { useFigmaToken } from '@figma-vars/hooks'New Utilities (opt-in)
import { withRetry, redactToken, filterVariables } from '@figma-vars/hooks'
// Automatic retry with exponential backoff
const fetchWithRetry = withRetry(() => myApiCall(), { maxRetries: 3 })
// Safe token logging
console.log('Token:', redactToken(token)) // "figd_***...***cret"
// Case-insensitive filtering
filterVariables(vars, { name: 'primary', caseInsensitive: true })Custom API Base URL
import { fetcher, mutator } from '@figma-vars/hooks/core'
// Use mock server for testing
await fetcher('/v1/files/KEY/variables/local', token, {
baseUrl: 'http://localhost:3000',
})📝 Contributing
PRs and issues are welcome! Please see CONTRIBUTING.md for guidelines.
📝 License
This project is licensed under the MIT License. © 2024–2026 Mark Learst
