@figma-vars/hooks 3.0.0-beta.1 โ†’ 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +417 -173
  2. package/dist/api/fetcher.d.ts +32 -1
  3. package/dist/api/fetcher.d.ts.map +1 -1
  4. package/dist/api/index.d.ts +2 -0
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api/mutator.d.ts +36 -9
  7. package/dist/api/mutator.d.ts.map +1 -1
  8. package/dist/constants/index.d.ts +2 -28
  9. package/dist/constants/index.d.ts.map +1 -1
  10. package/dist/contexts/FigmaTokenContext.d.ts.map +1 -1
  11. package/dist/contexts/FigmaVarsProvider.d.ts +3 -1
  12. package/dist/contexts/FigmaVarsProvider.d.ts.map +1 -1
  13. package/dist/contexts/index.d.ts +1 -1
  14. package/dist/contexts/index.d.ts.map +1 -1
  15. package/dist/contexts/useFigmaTokenContext.d.ts.map +1 -1
  16. package/dist/core/index.d.cts +8 -0
  17. package/dist/core/index.d.ts +8 -0
  18. package/dist/core/index.d.ts.map +1 -0
  19. package/dist/core.cjs +1 -0
  20. package/dist/core.d.cts +2 -0
  21. package/dist/core.d.ts +2 -0
  22. package/dist/core.mjs +20 -0
  23. package/dist/hooks/index.d.ts +64 -0
  24. package/dist/hooks/index.d.ts.map +1 -1
  25. package/dist/hooks/useBulkUpdateVariables.d.ts.map +1 -1
  26. package/dist/hooks/useCollectionById.d.ts +31 -0
  27. package/dist/hooks/useCollectionById.d.ts.map +1 -0
  28. package/dist/hooks/useCreateVariable.d.ts.map +1 -1
  29. package/dist/hooks/useDeleteVariable.d.ts.map +1 -1
  30. package/dist/hooks/useFigmaToken.d.ts.map +1 -1
  31. package/dist/hooks/useInvalidateVariables.d.ts +34 -0
  32. package/dist/hooks/useInvalidateVariables.d.ts.map +1 -0
  33. package/dist/hooks/useModesByCollection.d.ts +34 -0
  34. package/dist/hooks/useModesByCollection.d.ts.map +1 -0
  35. package/dist/hooks/usePublishedVariables.d.ts +42 -0
  36. package/dist/hooks/usePublishedVariables.d.ts.map +1 -0
  37. package/dist/hooks/useUpdateVariable.d.ts.map +1 -1
  38. package/dist/hooks/useVariableById.d.ts +31 -0
  39. package/dist/hooks/useVariableById.d.ts.map +1 -0
  40. package/dist/hooks/useVariableCollections.d.ts.map +1 -1
  41. package/dist/hooks/useVariableModes.d.ts.map +1 -1
  42. package/dist/hooks/useVariables.d.ts +3 -1
  43. package/dist/hooks/useVariables.d.ts.map +1 -1
  44. package/dist/index-5ZyKWuYv.cjs +1 -0
  45. package/dist/index-ClHLYVvu.js +142 -0
  46. package/dist/index.cjs +1 -1
  47. package/dist/index.d.cts +9 -4
  48. package/dist/index.d.ts +9 -4
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.mjs +319 -163
  51. package/dist/types/contexts.d.ts +30 -3
  52. package/dist/types/contexts.d.ts.map +1 -1
  53. package/dist/types/figma.d.ts +64 -3
  54. package/dist/types/figma.d.ts.map +1 -1
  55. package/dist/types/hooks.d.ts.map +1 -1
  56. package/dist/types/index.d.ts.map +1 -1
  57. package/dist/types/mutations.d.ts +18 -4
  58. package/dist/types/mutations.d.ts.map +1 -1
  59. package/dist/utils/errorHelpers.d.ts +142 -0
  60. package/dist/utils/errorHelpers.d.ts.map +1 -0
  61. package/dist/utils/filterVariables.d.ts.map +1 -1
  62. package/dist/utils/index.d.ts +3 -1
  63. package/dist/utils/index.d.ts.map +1 -1
  64. package/dist/utils/swrKeys.d.ts +24 -0
  65. package/dist/utils/swrKeys.d.ts.map +1 -0
  66. package/dist/utils/typeGuards.d.ts +50 -0
  67. package/dist/utils/typeGuards.d.ts.map +1 -0
  68. package/package.json +75 -31
  69. package/scripts/export-variables.mjs +101 -0
  70. package/dist/index.d.mts +0 -2
package/README.md CHANGED
@@ -1,278 +1,522 @@
1
+ # FigmaVars/hooks
2
+
1
3
  <p align="left">
2
4
  <img src="assets/figma-vars-tagline-light.png" alt="FigmaVars Logo" width="700px" />
3
5
  </p>
4
6
 
5
- A fast, typed React 19 hooks library for the Figma Variables API: fetch, update, and manage design tokens via the official [Figma REST API](https://www.figma.com/developers/api#variables).
7
+ Built and maintained by Mark Learst.
8
+
9
+ 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](https://www.figma.com/developers/api#variables).
6
10
 
7
- Built for the modern web, this library provides a suite of hooks to fetch, manage, and mutate your design tokens, making it easy to sync them between Figma and your React applications, Storybooks, or design system dashboards.
11
+ 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.
8
12
 
9
13
  ![Status](https://img.shields.io/badge/status-stable-brightgreen)
10
- ![CI](https://github.com/marklearst/figma-vars-hooks/actions/workflows/publish.yml/badge.svg)
14
+ ![CI](https://github.com/marklearst/figma-vars-hooks/actions/workflows/ci.yml/badge.svg)
11
15
  [![codecov](https://codecov.io/gh/marklearst/figma-vars-hooks/branch/main/graph/badge.svg)](https://codecov.io/gh/marklearst/figma-vars-hooks)
12
16
  ![Test Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
13
17
  ![License](https://img.shields.io/github/license/marklearst/figma-vars-hooks)
14
18
  ![GitHub last commit](https://img.shields.io/github/last-commit/marklearst/figma-vars-hooks)
15
19
  ![GitHub code size](https://img.shields.io/github/languages/code-size/marklearst/figma-vars-hooks)
16
- ![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue?logo=typescript)
17
- ![npm](https://img.shields.io/npm/v/@figma-vars/hooks)
18
- ![npm downloads](https://img.shields.io/npm/dm/@figma-vars/hooks)
19
-
20
- ---
21
20
 
22
- ## ๐Ÿš€ Features
21
+ ## ๐Ÿ“Œ Why 3.0
23
22
 
24
- - **โœ… Token Agnostic for the Best DX**: Our library doesn't care _how_ you get your Figma token. Use environment variables, `localStorage`, a state management library, or even a simple input field. This framework-agnostic approach means it works seamlessly with Vite, Next.js, Create React App, and more, without locking you into a specific build tool.
25
- - **โš›๏ธ Modern React Hooks**: A full suite of hooks for fetching and mutating Figma variables, collections, and modes.
26
- - **โœ๏ธ Ergonomic Mutations**: A `useMutation`-style API for creating, updating, and deleting variables, providing clear loading and error states.
27
- - **๐Ÿ”’ TypeScript-first**: Strictly typed for an ergonomic and safe developer experience. Get autocompletion for all API responses.
28
- - **๐Ÿ“– Storybook & Next.js Ready**: Perfect for building live design token dashboards or style guides.
23
+ - โœจ **New DX Features**: SWR configuration support, error handling utilities, cache invalidation helpers
24
+ - ๐Ÿ”ง **React 19.2 Ready**: Optimized hooks with proper cleanup and stable function references
25
+ - ๐Ÿ›ก๏ธ **Better Error Handling**: `FigmaApiError` class with HTTP status codes for better error differentiation
26
+ - โœ… **Type Safety**: Removed unsafe type assertions, improved type definitions throughout
27
+ - ๐Ÿš€ **Performance**: Hardened SWR usage (stable keys, `null` to disable, cleaner fallback handling)
28
+ - ๐Ÿ“ฆ **Modern Tooling**: Node 20+ toolchain, strict TypeScript, and ESM-first packaging with CJS interop
29
+ - ๐Ÿ–ฅ๏ธ **CLI Export Tool**: Automate variable exports with `figma-vars-export` for CI/CD (Enterprise required)
29
30
 
30
- ---
31
-
32
- ## ๐Ÿงฑ Architecture Highlights
31
+ ## ๐Ÿš€ Features at a Glance
33
32
 
34
- - โœ… **100% Test Coverage** - Comprehensive test suite with 78 tests covering all hooks, utilities, and edge cases via Vitest
35
- - โœ… **Consistent Error Handling** - Standardized error propagation with clear messages from the Figma API
36
- - โœ… **Strictly Typed APIs** - Modern TypeScript best practices with full type inference and autocompletion
37
- - โœ… **Predictable Hook Signatures** - Consistent, composable patterns designed for safe React integrations
38
- - โœ… **Developer-First Architecture** - Clean folder structure, path aliases, and logical component separation
39
- - โœ… **React Ecosystem** - Built specifically for React apps, Storybook, Next.js, and design system dashboards
40
- - โœ… **Ergonomic DX** - Intuitive API that's easy to use in both prototype and production environments
41
- - โœ… **Minimal Dependencies** - Leverages SWR for caching with careful dependency selection for optimal bundle size
42
-
43
- ---
33
+ - **Modern React 19.2 hooks** for variables, collections, modes, and published variables
34
+ - **Ergonomic mutation hooks** with consistent loading/error states
35
+ - **SWR configuration support** for customizing caching and revalidation behavior
36
+ - **Error handling utilities** for type-safe error checking and status code access
37
+ - **Cache invalidation helpers** for automatic data refresh after mutations
38
+ - **CLI export tool** (`figma-vars-export`) for automating variable exports to JSON (Enterprise required)
39
+ - **Fallback JSON support** (object or string) for offline/static use - works without Enterprise!
40
+ - **Typed core entrypoint** for non-React consumers (Axios, TanStack Query, server scripts)
41
+ - **100% test coverage** + strict TypeScript + clean exports/attw/publint/size-limit checks
44
42
 
45
43
  ## ๐Ÿ“ฆ Install
46
44
 
47
45
  ```bash
48
46
  npm install @figma-vars/hooks
49
47
  # or
50
- yarn add @figma-vars/hooks
51
- # or
52
48
  pnpm add @figma-vars/hooks
53
49
  ```
54
50
 
55
- > **Peer dependencies:** You'll need `react` and `react-dom`.
51
+ Peer deps: `react` and `react-dom`.
56
52
 
57
- ---
53
+ ## ๐Ÿ–ฅ๏ธ CLI Export Tool
54
+
55
+ 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.
56
+
57
+ > โš ๏ธ **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](#exporting-variables-for-fallback) method instead.
58
+
59
+ ```bash
60
+ # Using npx (no install needed)
61
+ FIGMA_TOKEN=your_token npx figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
62
+
63
+ # After installing
64
+ npm install @figma-vars/hooks
65
+ FIGMA_TOKEN=your_token figma-vars-export --file-key YOUR_FILE_KEY --out ./variables.json
58
66
 
59
- ## ๐Ÿ› ๏ธ Setup & Usage
67
+ # Show help
68
+ figma-vars-export --help
69
+ ```
70
+
71
+ **Options:**
72
+
73
+ - `--file-key` - Figma file key (required, or set `FIGMA_FILE_KEY` env var)
74
+ - `--out` - Output path (default: `data/figma-variables.json`)
75
+ - `--help` - Show help message
76
+
77
+ **Environment Variables:**
78
+
79
+ - `FIGMA_TOKEN` or `FIGMA_PAT` - Figma Personal Access Token (required)
80
+ - `FIGMA_FILE_KEY` - Figma file key (optional)
81
+
82
+ **Example Output:**
83
+
84
+ ```
85
+ Saved variables to ./variables.json
86
+ Variables count: 42
87
+ ```
60
88
 
61
- The library is designed to be as flexible as possible. You provide the Figma token and file key, and the hooks handle the rest.
89
+ **No Enterprise?** See [Exporting variables for fallback](#exporting-variables-for-fallback) for alternative methods that work without Enterprise.
62
90
 
63
- Wrap your application (or the relevant component tree) with the `FigmaVarsProvider`. This makes the Figma token and file key available to all the hooks.
91
+ ## ๐Ÿ› ๏ธ Quick Start (SWR-powered hooks)
64
92
 
65
93
  ```tsx
66
- // src/main.tsx or App.tsx
67
- import React from 'react'
68
- import ReactDOM from 'react-dom/client'
69
- import { FigmaVarsProvider } from '@figma-vars/hooks'
70
- import App from './App'
94
+ import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
71
95
 
72
- // The token can come from anywhere: .env, localStorage, state, etc.
73
96
  const FIGMA_TOKEN = import.meta.env.VITE_FIGMA_TOKEN
74
- const FIGMA_FILE_KEY = 'your-figma-file-key'
97
+ const FIGMA_FILE_KEY = 'your-file-key'
75
98
 
76
- ReactDOM.createRoot(document.getElementById('root')!).render(
77
- <React.StrictMode>
99
+ function App() {
100
+ return (
78
101
  <FigmaVarsProvider
79
102
  token={FIGMA_TOKEN}
80
- fileKey={FIGMA_FILE_KEY}>
81
- <App />
103
+ fileKey={FIGMA_FILE_KEY}
104
+ swrConfig={{
105
+ revalidateOnFocus: false,
106
+ dedupingInterval: 5000,
107
+ }}>
108
+ <Tokens />
82
109
  </FigmaVarsProvider>
83
- </React.StrictMode>
110
+ )
111
+ }
112
+
113
+ function Tokens() {
114
+ const { data, isLoading, error } = useVariables()
115
+ if (isLoading) return <div>Loadingโ€ฆ</div>
116
+ if (error) return <div>Error: {error.message}</div>
117
+ const variables = Object.values(data?.meta.variables ?? {})
118
+ return <pre>{JSON.stringify(variables, null, 2)}</pre>
119
+ }
120
+ ```
121
+
122
+ ## ๐Ÿงฉ Non-SWR Usage (Core entrypoint)
123
+
124
+ Use the `/core` build when you prefer Axios/TanStack/server scripts without React/SWR.
125
+
126
+ **Axios example (GET + bulk PUT)**
127
+
128
+ ```ts
129
+ import axios from 'axios'
130
+ import { FIGMA_FILE_VARIABLES_PATH } from '@figma-vars/hooks/core'
131
+
132
+ const token = process.env.FIGMA_TOKEN!
133
+ const fileKey = process.env.FIGMA_FILE_KEY!
134
+
135
+ // Fetch local variables
136
+ const url = `https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`
137
+ const { data } = await axios.get(url, {
138
+ headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' },
139
+ })
140
+
141
+ // Bulk update
142
+ await axios.put(
143
+ `https://api.figma.com${FIGMA_FILE_VARIABLES_PATH(fileKey)}`,
144
+ { variables: [{ action: 'UPDATE', id: 'VariableId:123', name: 'new-name' }] },
145
+ { headers: { 'X-FIGMA-TOKEN': token, 'Content-Type': 'application/json' } }
84
146
  )
85
147
  ```
86
148
 
87
- ### Fetching Data
149
+ **TanStack Query example**
150
+
151
+ ```ts
152
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
153
+ import { FIGMA_FILE_VARIABLES_PATH, fetcher, mutator } from '@figma-vars/hooks/core'
154
+
155
+ const token = process.env.FIGMA_TOKEN!
156
+ const fileKey = process.env.FIGMA_FILE_KEY!
88
157
 
89
- Now, you can use the query hooks anywhere in your app:
158
+ export function useLocalVariables() {
159
+ return useQuery({
160
+ queryKey: ['figma-local', fileKey],
161
+ queryFn: () => fetcher(`${FIGMA_FILE_VARIABLES_PATH(fileKey)}/local`, token),
162
+ staleTime: 60_000,
163
+ })
164
+ }
165
+
166
+ export function useBulkUpdate() {
167
+ const qc = useQueryClient()
168
+ return useMutation({
169
+ mutationFn: (payload: unknown) =>
170
+ mutator(FIGMA_FILE_VARIABLES_PATH(fileKey), token, 'UPDATE', payload),
171
+ onSuccess: () => qc.invalidateQueries({ queryKey: ['figma-local', fileKey] }),
172
+ })
173
+ }
174
+ ```
175
+
176
+ ## ๐Ÿ›ก๏ธ Fallback JSON (offline/static)
177
+
178
+ Pass `fallbackFile` (object or JSON string) to `FigmaVarsProvider` to bypass live API calls:
90
179
 
91
180
  ```tsx
92
- // src/components/TokenList.tsx
93
- import { useVariables } from '@figma-vars/hooks'
181
+ import exportedVariables from './figma-variables.json'
182
+ ;<FigmaVarsProvider
183
+ token={null}
184
+ fileKey={null}
185
+ fallbackFile={exportedVariables}>
186
+ <App />
187
+ </FigmaVarsProvider>
188
+ ```
94
189
 
95
- export function TokenList() {
96
- const { data, isLoading, error } = useVariables()
190
+ ### Exporting variables for fallback
97
191
 
98
- if (isLoading) return <div>Loading tokens...</div>
99
- if (error) return <div>Error: {error.message}</div>
192
+ There are several ways to get your Figma variables as JSON:
193
+
194
+ 1. **Dev Mode / plugin export (recommended, no Enterprise needed)** โญ
195
+ - Use a Variables exporter plugin in Figma Dev Mode to download the full Variables panel as JSON
196
+ - Save anywhere (e.g., `data/figma-variables.json`) and pass it to `fallbackFile`
197
+ - Works for everyone, no Enterprise account required!
198
+
199
+ 2. **CLI export tool (Enterprise required)** ๐Ÿš€
200
+ - Automatically exports via REST API - perfect for CI/CD and automation
201
+ - See the [CLI Export Tool](#-cli-export-tool) section above for full usage details
202
+ - Also available from cloned repo: `node scripts/export-variables.mjs --file-key KEY --out file.json`
203
+
204
+ - **Desktop MCP (manual/partial)**: Selecting a frame and running `get_variable_defs` returns only that selectionโ€™s variables. Use plugin/REST exports for complete coverage.
205
+
206
+ 4. **Style Dictionary**
207
+ - Once you have the JSON (from any path), feed it into Style Dictionary to emit platform-specific artifacts
208
+ - Or import it directly via `fallbackFile`
209
+
210
+ ## ๐Ÿ”ง Mutation Hooks (verbs fixed)
211
+
212
+ - `useCreateVariable` โ†’ POST via bulk endpoint with `action: 'CREATE'`
213
+ - `useUpdateVariable` โ†’ PUT via bulk endpoint with `action: 'UPDATE'`
214
+ - `useDeleteVariable` โ†’ DELETE via bulk endpoint with `action: 'DELETE'`
215
+ - `useBulkUpdateVariables` โ†’ PUT bulk payload (collections, modes, variables, values)
216
+
217
+ All return `{ mutate, data, error, isLoading, isSuccess, isError }`.
218
+
219
+ ### Example: Creating and updating variables
220
+
221
+ ```tsx
222
+ import { useCreateVariable, useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
223
+
224
+ function VariableEditor() {
225
+ const { mutate: create } = useCreateVariable()
226
+ const { mutate: update } = useUpdateVariable()
227
+ const { invalidate } = useInvalidateVariables()
228
+
229
+ const handleCreate = async () => {
230
+ await create({
231
+ name: 'Primary Color',
232
+ variableCollectionId: 'CollectionId:123',
233
+ resolvedType: 'COLOR',
234
+ })
235
+ invalidate() // Refresh cache after mutation
236
+ }
100
237
 
101
- // The 'data' object contains variables, collections, and modes
102
- const variables = Object.values(data?.variables ?? {})
238
+ const handleUpdate = async (id: string) => {
239
+ await update({
240
+ variableId: id,
241
+ payload: { name: 'Updated Name' },
242
+ })
243
+ invalidate() // Refresh cache after mutation
244
+ }
103
245
 
104
246
  return (
105
- <ul>
106
- {variables.map((variable) => (
107
- <li key={variable.id}>
108
- {variable.name}: {JSON.stringify(variable.valuesByMode)}
109
- </li>
110
- ))}
111
- </ul>
247
+ <>
248
+ <button onClick={handleCreate}>Create Variable</button>
249
+ <button onClick={() => handleUpdate('VariableId:123')}>Update</button>
250
+ </>
112
251
  )
113
252
  }
114
253
  ```
115
254
 
116
- ### Mutating Data
255
+ ## ๐Ÿ›ก๏ธ Error Handling
117
256
 
118
- To create, update, or delete variables, use the provided mutation hooks. They follow a standard pattern, returning a `mutate` function and states for `data`, `isLoading`, and `error`.
257
+ ### Error Boundaries (Recommended)
119
258
 
120
- Here's an example of creating a new variable:
259
+ Wrap your Figma-connected components with an error boundary to gracefully handle errors:
121
260
 
122
261
  ```tsx
123
- // src/components/CreateVariableForm.tsx
124
- import { useCreateVariable } from '@figma-vars/hooks'
125
- import type { CreateVariablePayload } from '@figma-vars/hooks'
126
-
127
- function CreateVariableForm({ collectionId }: { collectionId: string }) {
128
- const { mutate, data, isLoading, error } = useCreateVariable()
129
-
130
- const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
131
- event.preventDefault()
132
- const form = event.currentTarget
133
- const variableName = (
134
- form.elements.namedItem('variableName') as HTMLInputElement
135
- )?.value
136
-
137
- if (!variableName) return
138
-
139
- const payload: CreateVariablePayload = {
140
- name: variableName,
141
- variableCollectionId: collectionId,
142
- resolvedType: 'COLOR', // Example type
143
- }
144
- mutate(payload)
145
- }
262
+ import { ErrorBoundary } from 'react-error-boundary'
263
+ import { FigmaVarsProvider } from '@figma-vars/hooks'
264
+
265
+ function FigmaErrorFallback({ error }: { error: Error }) {
266
+ return (
267
+ <div role='alert'>
268
+ <h2>Failed to load Figma data</h2>
269
+ <pre>{error.message}</pre>
270
+ </div>
271
+ )
272
+ }
146
273
 
274
+ function App() {
147
275
  return (
148
- <form onSubmit={handleSubmit}>
149
- <input
150
- name="variableName"
151
- placeholder="New variable name"
152
- />
153
- <button
154
- type="submit"
155
- disabled={isLoading}>
156
- {isLoading ? 'Creating...' : 'Create Variable'}
157
- </button>
158
- {error && <p>Error: {error.message}</p>}
159
- {data && <p>Created variable with ID: {data.id}</p>}
160
- </form>
276
+ <ErrorBoundary FallbackComponent={FigmaErrorFallback}>
277
+ <FigmaVarsProvider
278
+ token={FIGMA_TOKEN}
279
+ fileKey={FIGMA_FILE_KEY}>
280
+ <YourApp />
281
+ </FigmaVarsProvider>
282
+ </ErrorBoundary>
161
283
  )
162
284
  }
163
285
  ```
164
286
 
165
- ### Figma PAT Security
287
+ > **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 `undefined` data.
288
+
289
+ ### Runtime Validation
166
290
 
167
- When using the Figma API, it's essential to keep your Personal Access Token (PAT) secure. Here are some best practices:
291
+ Use type guards to validate data at runtime:
292
+
293
+ ```tsx
294
+ import { isLocalVariablesResponse, isPublishedVariablesResponse } from '@figma-vars/hooks'
168
295
 
169
- - Never hardcode your PAT in your code.
170
- - Use environment variables or a secure storage mechanism to store your PAT.
171
- - Limit the scope of your PAT to only the necessary permissions.
172
- - Rotate your PAT regularly.
296
+ // Validate before using
297
+ if (isLocalVariablesResponse(data)) {
298
+ // Safe to access data.meta.variables
299
+ }
300
+ ```
173
301
 
174
- ### Advanced Usage
302
+ ### Error Utilities
175
303
 
176
- For advanced use cases, you can use the `useFigmaToken` hook to access the token and file key from the context.
304
+ 3.0.0 introduces powerful error handling utilities for type-safe error checking:
177
305
 
178
306
  ```tsx
179
- // src/components/AdvancedUsage.tsx
180
- import { useFigmaToken } from '@figma-vars/hooks'
181
-
182
- function AdvancedUsage() {
183
- const { token, fileKey } = useFigmaToken()
184
-
185
- // Use the token and file key to make custom API requests
186
- const apiRequest = async () => {
187
- const response = await fetch(
188
- `https://api.figma.com/v1/files/${fileKey}/variables`,
189
- {
190
- headers: {
191
- 'X-Figma-Token': token,
192
- },
193
- }
194
- )
307
+ import { isFigmaApiError, getErrorStatus, getErrorMessage, hasErrorStatus } from '@figma-vars/hooks'
308
+
309
+ function ErrorHandler({ error }: { error: Error | null }) {
310
+ if (!error) return null
195
311
 
196
- const data = await response.json()
197
- console.log(data)
312
+ // Type guard for FigmaApiError
313
+ if (isFigmaApiError(error)) {
314
+ const status = error.statusCode
315
+
316
+ if (status === 401) {
317
+ return <div>Authentication required. Please check your token.</div>
318
+ }
319
+ if (status === 403) {
320
+ return <div>Access forbidden. Check file permissions.</div>
321
+ }
322
+ if (status === 429) {
323
+ return <div>Rate limit exceeded. Please wait before retrying.</div>
324
+ }
325
+ if (status === 404) {
326
+ return <div>File or variable not found.</div>
327
+ }
328
+ }
329
+
330
+ // Helper functions
331
+ const status = getErrorStatus(error) // number | null
332
+ const message = getErrorMessage(error) // string
333
+
334
+ // Convenience check
335
+ if (hasErrorStatus(error, 401)) {
336
+ // Handle unauthorized
198
337
  }
199
338
 
200
- return <button onClick={apiRequest}>Make API Request</button>
339
+ return <div>Error: {message}</div>
201
340
  }
202
341
  ```
203
342
 
204
- ### Error Handling
343
+ **Common HTTP Status Codes:**
205
344
 
206
- All hooks return an `error` state that you can use to handle errors.
345
+ - `401` - Unauthorized (invalid or missing token)
346
+ - `403` - Forbidden (insufficient permissions)
347
+ - `404` - Not Found (file/variable doesn't exist)
348
+ - `429` - Too Many Requests (rate limit exceeded)
349
+
350
+ ## ๐Ÿ”„ Cache Management
351
+
352
+ After mutations, use `useInvalidateVariables` to refresh cached data:
207
353
 
208
354
  ```tsx
209
- // src/components/ErrorHandling.tsx
210
- import { useVariables } from '@figma-vars/hooks'
355
+ import { useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
211
356
 
212
- function ErrorHandling() {
213
- const { data, isLoading, error } = useVariables()
357
+ function UpdateButton({ variableId }: { variableId: string }) {
358
+ const { mutate, isLoading } = useUpdateVariable()
359
+ const { invalidate, revalidate } = useInvalidateVariables()
360
+
361
+ const handleUpdate = async () => {
362
+ await mutate({
363
+ variableId,
364
+ payload: { name: 'New Name' },
365
+ })
214
366
 
215
- if (error) {
216
- return (
217
- <div>
218
- <h2>Error</h2>
219
- <p>{error.message}</p>
220
- </div>
221
- )
367
+ // Option 1: Invalidate (refetch on next access)
368
+ invalidate()
369
+
370
+ // Option 2: Revalidate immediately (refetch now)
371
+ // revalidate()
222
372
  }
223
373
 
224
- // Render data or loading state
374
+ return (
375
+ <button
376
+ onClick={handleUpdate}
377
+ disabled={isLoading}>
378
+ {isLoading ? 'Updating...' : 'Update Variable'}
379
+ </button>
380
+ )
225
381
  }
226
382
  ```
227
383
 
228
- ---
384
+ ## โš™๏ธ SWR Configuration
229
385
 
230
- ## ๐Ÿงฉ API Reference
386
+ Customize SWR behavior globally through the provider:
231
387
 
232
- ### Core Hooks
388
+ ```tsx
389
+ <FigmaVarsProvider
390
+ token={FIGMA_TOKEN}
391
+ fileKey={FIGMA_FILE_KEY}
392
+ swrConfig={{
393
+ revalidateOnFocus: false, // Don't refetch on window focus
394
+ dedupingInterval: 5000, // Dedupe requests within 5s
395
+ errorRetryCount: 3, // Retry failed requests 3 times
396
+ errorRetryInterval: 1000, // Wait 1s between retries
397
+ onError: error => {
398
+ // Global error handler
399
+ if (isFigmaApiError(error) && error.statusCode === 429) {
400
+ console.warn('Rate limited, backing off...')
401
+ }
402
+ },
403
+ }}>
404
+ <App />
405
+ </FigmaVarsProvider>
406
+ ```
407
+
408
+ **Common SWR Options:**
233
409
 
234
- - `useVariables()`: Fetches all local variables for the file key provided to the `FigmaVarsProvider`. Returns a SWR hook state with `data`, `isLoading`, and `error` properties. The actual Figma response is in `data.data`.
235
- - `useVariableCollections()`: A convenience hook that returns just the variable collections from the main `useVariables` data.
236
- - `useVariableModes()`: A convenience hook that returns just the variable modes from the main `useVariables` data.
237
- - `useFigmaToken()`: A simple hook to access the token and file key from the context.
410
+ - `revalidateOnFocus` - Refetch when window regains focus (default: `true`)
411
+ - `dedupingInterval` - Deduplication interval in ms (default: `2000`)
412
+ - `errorRetryCount` - Max retry attempts (default: `5`)
413
+ - `refreshInterval` - Polling interval in ms (default: `0` = disabled)
414
+ - `onError` - Global error callback
238
415
 
239
- ### Mutation Hooks
416
+ ## ๐Ÿ“š API Cheat Sheet
240
417
 
241
- All mutation hooks return an object with the following shape: `{ mutate, data, isLoading, error }`.
418
+ ### Hooks
242
419
 
243
- - `useCreateVariable()`: Creates a new variable. The `mutate` function expects a `CreateVariablePayload` object.
244
- - `useUpdateVariable()`: Updates an existing variable. The `mutate` function expects an object `{ variableId, payload }` where `payload` is an `UpdateVariablePayload`.
245
- - `useDeleteVariable()`: Deletes a variable. The `mutate` function expects the `variableId` (string) of the variable to delete.
246
- - `useBulkUpdateVariables()`: Creates, updates, or deletes multiple entities in a single batch operation. The `mutate` function expects a `BulkUpdatePayload` object.
420
+ - **Queries**: `useVariables` (local), `usePublishedVariables` (library/published), `useVariableCollections`, `useVariableModes`, `useFigmaToken`
421
+ - **Granular Selectors**: `useCollectionById`, `useModesByCollection`, `useVariableById` (optimized selectors for specific entities)
422
+ - **Mutations**: `useCreateVariable`, `useUpdateVariable`, `useDeleteVariable`, `useBulkUpdateVariables`
423
+ - **Cache**: `useInvalidateVariables` (invalidate/revalidate cache)
424
+
425
+ ### Utilities
426
+
427
+ - **Filtering**: `filterVariables` (filter by type, name, etc.)
428
+ - **Error Handling**: `isFigmaApiError`, `getErrorStatus`, `getErrorMessage`, `hasErrorStatus`
429
+ - **Type Guards**: `isLocalVariablesResponse`, `isPublishedVariablesResponse`, `validateFallbackData` (runtime validation)
430
+ - **SWR Keys**: `getVariablesKey`, `getPublishedVariablesKey`, `getInvalidationKeys` (centralized cache key construction)
431
+ - **Core helpers**: `fetcher`, `mutator`, constants for endpoints and headers
247
432
 
248
433
  ### Types
249
434
 
250
- All types are exported from `@figma-vars/hooks`. The core response type from Figma for local variables is `LocalVariablesResponse`.
435
+ - **Responses**: `LocalVariablesResponse`, `PublishedVariablesResponse`
436
+ - **Variables**: `FigmaVariable`, `FigmaCollection`, `VariableMode`
437
+ - **Mutations**: `BulkUpdatePayload`, `CreateVariablePayload`, `UpdateVariablePayload`
438
+ - **Errors**: `FigmaApiError` (extends `Error` with `statusCode`)
251
439
 
252
- ---
440
+ ## ๐Ÿ” Auth & Scope
441
+
442
+ - Header: `X-FIGMA-TOKEN: <PAT>`
443
+ - Scopes: `file_variables:read` for GETs, `file_variables:write` for mutations.
444
+ - Enterprise Full seat required for live API; fallback JSON works without a token.
253
445
 
254
- ## ๐Ÿ“š Storybook & Next.js Integration
446
+ ## โš ๏ธ Enterprise Requirement and Offline Options
255
447
 
256
- The provider model makes integration trivial. Simply wrap your Storybook stories or Next.js pages with the `FigmaVarsProvider`.
448
+ - The Figma Variables REST API requires a Figma Enterprise seat for live requests. Without Enterprise, live calls will fail even with a valid PAT.
449
+ - 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.
450
+ - 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.
451
+
452
+ ## ๐Ÿšซ Do Not Publish Tokens or File Keys
453
+
454
+ - Never commit PATs or file keys to git, Storybook static builds, or client bundles.
455
+ - Use environment variables (`process.env` / `import.meta.env`) and secret managers; keep them server-side where possible.
456
+ - Prefer `fallbackFile` with `token={null}`/`fileKey={null}` for demos and public Storybooks.
457
+ - Avoid logging tokens or keys; scrub them from error messages and analytics.
458
+
459
+ ## ๐Ÿ“ˆ Rate Limits
460
+
461
+ - Figma enforces per-token limits. Rely on SWR/TanStack caching, avoid unnecessary refetches, and prefer fallback JSON for static sites.
462
+ - Use `swrConfig` to customize `dedupingInterval` and `errorRetryCount` to optimize API usage.
463
+ - Handle `429` rate limit errors with `isFigmaApiError` and implement exponential backoff if needed.
464
+
465
+ ## ๐Ÿ“š Storybook & Next.js
466
+
467
+ - **Storybook decorator**: wrap stories once so hooks have context and tokens.
257
468
 
258
469
  ```tsx
259
- // In a Storybook story
260
- import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
470
+ // .storybook/preview.tsx
471
+ import { FigmaVarsProvider } from '@figma-vars/hooks'
472
+ import type { Preview } from '@storybook/react'
473
+
474
+ const FIGMA_TOKEN = process.env.STORYBOOK_FIGMA_TOKEN
475
+ const FIGMA_FILE_KEY = process.env.STORYBOOK_FIGMA_FILE_KEY
476
+
477
+ const preview: Preview = {
478
+ decorators: [
479
+ Story => (
480
+ <FigmaVarsProvider
481
+ token={FIGMA_TOKEN}
482
+ fileKey={FIGMA_FILE_KEY}>
483
+ <Story />
484
+ </FigmaVarsProvider>
485
+ ),
486
+ ],
487
+ }
261
488
 
262
- export const TokensStory = () => (
263
- <FigmaVarsProvider
264
- token="YOUR_TOKEN"
265
- fileKey="YOUR_FILE_KEY">
266
- <TokenList />
267
- </FigmaVarsProvider>
268
- )
489
+ export default preview
490
+ ```
491
+
492
+ - **Next.js App Router**: provide context in a shared provider file.
269
493
 
270
- const TokenList = () => {
271
- const { data } = useVariables()
272
- return <pre>{JSON.stringify(data?.variables, null, 2)}</pre>
494
+ ```tsx
495
+ // app/providers.tsx
496
+ import { FigmaVarsProvider } from '@figma-vars/hooks'
497
+
498
+ export function Providers({ children }: { children: React.ReactNode }) {
499
+ return (
500
+ <FigmaVarsProvider
501
+ token={process.env.NEXT_PUBLIC_FIGMA_TOKEN}
502
+ fileKey={process.env.NEXT_PUBLIC_FIGMA_FILE_KEY}>
503
+ {children}
504
+ </FigmaVarsProvider>
505
+ )
273
506
  }
274
507
  ```
275
508
 
509
+ ## ๐Ÿงช Tooling & Quality Gates
510
+
511
+ - `pnpm run build`, `pnpm test`, `pnpm run test:coverage`
512
+ - `pnpm run check:publint`, `pnpm run check:attw`, `pnpm run check:size`
513
+
514
+ ## ๐Ÿงญ Release Checklist (for 3.0.0)
515
+
516
+ - Run `pnpm run check:release`
517
+ - Tag `v3.0.0` (CI publishes to npm)
518
+ - Update dist-tags on npm if needed (`latest` โ†’ 3.0.0)
519
+
276
520
  ---
277
521
 
278
522
  ## ๐Ÿ“ Contributing
@@ -282,4 +526,4 @@ PRs and issues are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for gu
282
526
  ## ๐Ÿ“ License
283
527
 
284
528
  This project is licensed under the [MIT License](LICENSE).
285
- ยฉ 2024โ€“2025 Mark Learst
529
+ ยฉ 2024โ€“2026 Mark Learst