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.
- β¨ New DX Features: SWR configuration support, error handling utilities, cache invalidation helpers
- π§ React 19.2 Ready: Optimized hooks with proper cleanup and stable function references
- π‘οΈ Better Error Handling:
FigmaApiErrorclass with HTTP status codes for better error differentiation - β Type Safety: Removed unsafe type assertions, improved type definitions throughout
- π Performance: Hardened SWR usage (stable keys,
nullto disable, cleaner fallback handling) - π¦ 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)
- 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
npm install @figma-vars/hooks
# or
pnpm add @figma-vars/hooksPeer deps: react and react-dom.
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: 42
No Enterprise? See Exporting variables for fallback for alternative methods that work without Enterprise.
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>
}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] }),
})
}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>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
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 }.
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>
</>
)
}3.0.0 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)
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>
)
}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
- Queries:
useVariables(local),usePublishedVariables(library/published),useVariableCollections,useVariableModes,useFigmaToken - Mutations:
useCreateVariable,useUpdateVariable,useDeleteVariable,useBulkUpdateVariables - Cache:
useInvalidateVariables(invalidate/revalidate cache)
- Filtering:
filterVariables(filter by type, name, etc.) - Error Handling:
isFigmaApiError,getErrorStatus,getErrorMessage,hasErrorStatus - Core helpers:
fetcher,mutator, constants for endpoints and headers
- Responses:
LocalVariablesResponse,PublishedVariablesResponse - Variables:
FigmaVariable,FigmaCollection,VariableMode - Mutations:
BulkUpdatePayload,CreateVariablePayload,UpdateVariablePayload - Errors:
FigmaApiError(extendsErrorwithstatusCode)
- 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.
- 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.
- 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. - Avoid logging tokens or keys; scrub them from error messages and analytics.
- 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. - Handle
429rate limit errors withisFigmaApiErrorand implement exponential backoff if needed.
- 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>
)
}pnpm run build,pnpm test,pnpm run test:coveragepnpm run check:publint,pnpm run check:attw,pnpm run check:size
- Run
pnpm run check:release - Tag
v3.0.0(CI publishes to npm) - Update dist-tags on npm if needed (
latestβ 3.0.0)
PRs and issues are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License. Β© 2024β2026 Mark Learst
