@figma-vars/hooks 2.0.0-beta.3 โ†’ 3.0.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 (46) hide show
  1. package/README.md +355 -220
  2. package/dist/api/fetcher.d.ts.map +1 -1
  3. package/dist/api/mutator.d.ts +10 -9
  4. package/dist/api/mutator.d.ts.map +1 -1
  5. package/dist/constants/index.d.ts +2 -28
  6. package/dist/constants/index.d.ts.map +1 -1
  7. package/dist/contexts/FigmaVarsProvider.d.ts +1 -1
  8. package/dist/contexts/FigmaVarsProvider.d.ts.map +1 -1
  9. package/dist/core/index.d.cts +8 -0
  10. package/dist/core/index.d.ts +8 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/core.cjs +1 -0
  13. package/dist/core.d.cts +2 -0
  14. package/dist/core.d.ts +2 -0
  15. package/dist/core.mjs +20 -0
  16. package/dist/hooks/index.d.ts +19 -0
  17. package/dist/hooks/index.d.ts.map +1 -1
  18. package/dist/hooks/useBulkUpdateVariables.d.ts.map +1 -1
  19. package/dist/hooks/useCreateVariable.d.ts.map +1 -1
  20. package/dist/hooks/useDeleteVariable.d.ts.map +1 -1
  21. package/dist/hooks/useInvalidateVariables.d.ts +31 -0
  22. package/dist/hooks/useInvalidateVariables.d.ts.map +1 -0
  23. package/dist/hooks/usePublishedVariables.d.ts +42 -0
  24. package/dist/hooks/usePublishedVariables.d.ts.map +1 -0
  25. package/dist/hooks/useUpdateVariable.d.ts.map +1 -1
  26. package/dist/hooks/useVariables.d.ts.map +1 -1
  27. package/dist/index-BIUpDTdr.cjs +1 -0
  28. package/dist/index-Cd4HQQHO.js +94 -0
  29. package/dist/index.cjs +1 -1
  30. package/dist/index.d.cts +9 -4
  31. package/dist/index.d.ts +9 -4
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.mjs +229 -165
  34. package/dist/types/contexts.d.ts +29 -3
  35. package/dist/types/contexts.d.ts.map +1 -1
  36. package/dist/types/figma.d.ts +53 -0
  37. package/dist/types/figma.d.ts.map +1 -1
  38. package/dist/types/mutations.d.ts +1 -1
  39. package/dist/types/mutations.d.ts.map +1 -1
  40. package/dist/utils/errorHelpers.d.ts +96 -0
  41. package/dist/utils/errorHelpers.d.ts.map +1 -0
  42. package/dist/utils/index.d.ts +2 -1
  43. package/dist/utils/index.d.ts.map +1 -1
  44. package/package.json +67 -32
  45. package/scripts/export-variables.mjs +101 -0
  46. package/dist/index.d.mts +0 -2
package/README.md CHANGED
@@ -1,337 +1,472 @@
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.
29
- - **๐Ÿ”„ Local JSON Support**: Use local JSON files exported from Figma Dev Mode plugins when you don't have a Figma Enterprise account, enabling offline development and static deployments.
30
- - **๐Ÿšง Style Dictionary Integration**: Coming soon in future beta releases - seamless integration with Amazon's Style Dictionary for multi-platform design token distribution.
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)
31
30
 
32
- ---
33
-
34
- ## ๐Ÿงฑ Architecture Highlights
31
+ ## ๐Ÿš€ Features at a Glance
35
32
 
36
- - โœ… **100% Test Coverage** - Comprehensive test suite with 78 tests covering all hooks, utilities, and edge cases via Vitest
37
- - โœ… **Consistent Error Handling** - Standardized error propagation with clear messages from the Figma API
38
- - โœ… **Strictly Typed APIs** - Modern TypeScript best practices with full type inference and autocompletion
39
- - โœ… **Predictable Hook Signatures** - Consistent, composable patterns designed for safe React integrations
40
- - โœ… **Developer-First Architecture** - Clean folder structure, path aliases, and logical component separation
41
- - โœ… **React Ecosystem** - Built specifically for React apps, Storybook, Next.js, and design system dashboards
42
- - โœ… **Ergonomic DX** - Intuitive API that's easy to use in both prototype and production environments
43
- - โœ… **Minimal Dependencies** - Leverages SWR for caching with careful dependency selection for optimal bundle size
44
-
45
- ---
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
46
42
 
47
43
  ## ๐Ÿ“ฆ Install
48
44
 
49
45
  ```bash
50
46
  npm install @figma-vars/hooks
51
47
  # or
52
- yarn add @figma-vars/hooks
53
- # or
54
48
  pnpm add @figma-vars/hooks
55
49
  ```
56
50
 
57
- > **Peer dependencies:** You'll need `react` and `react-dom`.
58
-
59
- ---
51
+ Peer deps: `react` and `react-dom`.
60
52
 
61
- ## ๐Ÿ› ๏ธ Setup & Usage
53
+ ## ๐Ÿ–ฅ๏ธ CLI Export Tool
62
54
 
63
- The library is designed to be as flexible as possible. You provide the Figma token and file key, and the hooks handle the rest.
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.
64
56
 
65
- Wrap your application (or the relevant component tree) with the `FigmaVarsProvider`. This makes the Figma token and file key available to all the hooks.
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.
66
58
 
67
- ```tsx
68
- // src/main.tsx or App.tsx
69
- import React from 'react'
70
- import ReactDOM from 'react-dom/client'
71
- import { FigmaVarsProvider } from '@figma-vars/hooks'
72
- import App from './App'
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
73
62
 
74
- // The token can come from anywhere: .env, localStorage, state, etc.
75
- const FIGMA_TOKEN = import.meta.env.VITE_FIGMA_TOKEN
76
- const FIGMA_FILE_KEY = 'your-figma-file-key'
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
77
66
 
78
- ReactDOM.createRoot(document.getElementById('root')!).render(
79
- <React.StrictMode>
80
- <FigmaVarsProvider
81
- token={FIGMA_TOKEN}
82
- fileKey={FIGMA_FILE_KEY}>
83
- <App />
84
- </FigmaVarsProvider>
85
- </React.StrictMode>
86
- )
67
+ # Show help
68
+ figma-vars-export --help
87
69
  ```
88
70
 
89
- ### Fetching Data
71
+ **Options:**
90
72
 
91
- Now, you can use the query hooks anywhere in your app:
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
92
76
 
93
- ```tsx
94
- // src/components/TokenList.tsx
95
- import { useVariables } from '@figma-vars/hooks'
77
+ **Environment Variables:**
96
78
 
97
- export function TokenList() {
98
- const { data, isLoading, error } = useVariables()
79
+ - `FIGMA_TOKEN` or `FIGMA_PAT` - Figma Personal Access Token (required)
80
+ - `FIGMA_FILE_KEY` - Figma file key (optional)
99
81
 
100
- if (isLoading) return <div>Loading tokens...</div>
101
- if (error) return <div>Error: {error.message}</div>
82
+ **Example Output:**
102
83
 
103
- // The 'data' object contains variables, collections, and modes
104
- const variables = Object.values(data?.variables ?? {})
105
-
106
- return (
107
- <ul>
108
- {variables.map(variable => (
109
- <li key={variable.id}>
110
- {variable.name}: {JSON.stringify(variable.valuesByMode)}
111
- </li>
112
- ))}
113
- </ul>
114
- )
115
- }
84
+ ```
85
+ Saved variables to ./variables.json
86
+ Variables count: 42
116
87
  ```
117
88
 
118
- ### Mutating Data
119
-
120
- 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`.
89
+ **No Enterprise?** See [Exporting variables for fallback](#exporting-variables-for-fallback) for alternative methods that work without Enterprise.
121
90
 
122
- Here's an example of creating a new variable:
91
+ ## ๐Ÿ› ๏ธ Quick Start (SWR-powered hooks)
123
92
 
124
93
  ```tsx
125
- // src/components/CreateVariableForm.tsx
126
- import { useCreateVariable } from '@figma-vars/hooks'
127
- import type { CreateVariablePayload } from '@figma-vars/hooks'
128
-
129
- function CreateVariableForm({ collectionId }: { collectionId: string }) {
130
- const { mutate, data, isLoading, error } = useCreateVariable()
131
-
132
- const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
133
- event.preventDefault()
134
- const form = event.currentTarget
135
- const variableName = (form.elements.namedItem('variableName') as HTMLInputElement)?.value
136
-
137
- if (!variableName) return
94
+ import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
138
95
 
139
- const payload: CreateVariablePayload = {
140
- name: variableName,
141
- variableCollectionId: collectionId,
142
- resolvedType: 'COLOR', // Example type
143
- }
144
- mutate(payload)
145
- }
96
+ const FIGMA_TOKEN = import.meta.env.VITE_FIGMA_TOKEN
97
+ const FIGMA_FILE_KEY = 'your-file-key'
146
98
 
99
+ function App() {
147
100
  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>
101
+ <FigmaVarsProvider
102
+ token={FIGMA_TOKEN}
103
+ fileKey={FIGMA_FILE_KEY}
104
+ swrConfig={{
105
+ revalidateOnFocus: false,
106
+ dedupingInterval: 5000,
107
+ }}>
108
+ <Tokens />
109
+ </FigmaVarsProvider>
161
110
  )
162
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
+ }
163
120
  ```
164
121
 
165
- ### Using Local JSON Files (No Enterprise Account Required)
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)**
166
127
 
167
- If you don't have a Figma Enterprise account (required for the Variables API), you can still use this library with local JSON files exported from Figma Dev Mode plugins. This is perfect for:
128
+ ```ts
129
+ import axios from 'axios'
130
+ import { FIGMA_FILE_VARIABLES_PATH } from '@figma-vars/hooks/core'
168
131
 
169
- - **Offline Development**: Work on your design system without an internet connection
170
- - **Static Deployments**: Deploy design token dashboards to static hosting
171
- - **CI/CD Pipelines**: Use exported JSON files in automated workflows
172
- - **Team Collaboration**: Share design tokens without API access
132
+ const token = process.env.FIGMA_TOKEN!
133
+ const fileKey = process.env.FIGMA_FILE_KEY!
173
134
 
174
- #### Getting Your JSON File
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
+ })
175
140
 
176
- We recommend using the [Variables Exporter for Dev Mode](https://www.figma.com/community/plugin/1491572182178544621) plugin:
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' } }
146
+ )
147
+ ```
177
148
 
178
- 1. Install the plugin in Figma
179
- 2. Open your Figma file in Dev Mode
180
- 3. Run the plugin and export your variables as JSON
181
- 4. Save the JSON file to your project (e.g., `src/assets/figma-variables.json`)
149
+ **TanStack Query example**
182
150
 
183
- This plugin exports the exact same format that the Figma Variables API returns, ensuring perfect compatibility with this library.
151
+ ```ts
152
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
153
+ import { FIGMA_FILE_VARIABLES_PATH, fetcher, mutator } from '@figma-vars/hooks/core'
184
154
 
185
- #### Using the Fallback File
155
+ const token = process.env.FIGMA_TOKEN!
156
+ const fileKey = process.env.FIGMA_FILE_KEY!
186
157
 
187
- ```tsx
188
- // src/main.tsx or App.tsx
189
- import React from 'react'
190
- import ReactDOM from 'react-dom/client'
191
- import { FigmaVarsProvider } from '@figma-vars/hooks'
192
- import App from './App'
193
- import variablesData from './assets/figma-variables.json'
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
+ }
194
165
 
195
- ReactDOM.createRoot(document.getElementById('root')!).render(
196
- <React.StrictMode>
197
- <FigmaVarsProvider
198
- token={null} // No token needed when using fallbackFile
199
- fileKey={null} // No fileKey needed when using fallbackFile
200
- fallbackFile={variablesData}>
201
- <App />
202
- </FigmaVarsProvider>
203
- </React.StrictMode>
204
- )
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
+ }
205
174
  ```
206
175
 
207
- You can also pass the JSON as a string if you prefer:
176
+ ## ๐Ÿ›ก๏ธ Fallback JSON (offline/static)
177
+
178
+ Pass `fallbackFile` (object or JSON string) to `FigmaVarsProvider` to bypass live API calls:
208
179
 
209
180
  ```tsx
210
- import variablesJson from './assets/figma-variables.json?raw'
181
+ import exportedVariables from './figma-variables.json'
211
182
  ;<FigmaVarsProvider
212
183
  token={null}
213
184
  fileKey={null}
214
- fallbackFile={variablesJson}>
185
+ fallbackFile={exportedVariables}>
215
186
  <App />
216
187
  </FigmaVarsProvider>
217
188
  ```
218
189
 
219
- ### Figma PAT Security
190
+ ### Exporting variables for fallback
191
+
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`
220
209
 
221
- When using the Figma API, it's essential to keep your Personal Access Token (PAT) secure. Here are some best practices:
210
+ ## ๐Ÿ”ง Mutation Hooks (verbs fixed)
222
211
 
223
- - Never hardcode your PAT in your code.
224
- - Use environment variables or a secure storage mechanism to store your PAT.
225
- - Limit the scope of your PAT to only the necessary permissions.
226
- - Rotate your PAT regularly.
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)
227
216
 
228
- ### Advanced Usage
217
+ All return `{ mutate, data, error, isLoading, isSuccess, isError }`.
229
218
 
230
- For advanced use cases, you can use the `useFigmaToken` hook to access the token and file key from the context.
219
+ ### Example: Creating and updating variables
231
220
 
232
221
  ```tsx
233
- // src/components/AdvancedUsage.tsx
234
- import { useFigmaToken } from '@figma-vars/hooks'
235
-
236
- function AdvancedUsage() {
237
- const { token, fileKey } = useFigmaToken()
238
-
239
- // Use the token and file key to make custom API requests
240
- const apiRequest = async () => {
241
- const response = await fetch(`https://api.figma.com/v1/files/${fileKey}/variables`, {
242
- headers: {
243
- 'X-Figma-Token': token,
244
- },
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',
245
234
  })
235
+ invalidate() // Refresh cache after mutation
236
+ }
246
237
 
247
- const data = await response.json()
248
- console.log(data)
238
+ const handleUpdate = async (id: string) => {
239
+ await update({
240
+ variableId: id,
241
+ payload: { name: 'Updated Name' },
242
+ })
243
+ invalidate() // Refresh cache after mutation
249
244
  }
250
245
 
251
- return <button onClick={apiRequest}>Make API Request</button>
246
+ return (
247
+ <>
248
+ <button onClick={handleCreate}>Create Variable</button>
249
+ <button onClick={() => handleUpdate('VariableId:123')}>Update</button>
250
+ </>
251
+ )
252
252
  }
253
253
  ```
254
254
 
255
- ### Error Handling
255
+ ## ๐Ÿ›ก๏ธ Error Handling
256
256
 
257
- All hooks return an `error` state that you can use to handle errors.
257
+ 3.0.0 introduces powerful error handling utilities for type-safe error checking:
258
258
 
259
259
  ```tsx
260
- // src/components/ErrorHandling.tsx
261
- import { useVariables } from '@figma-vars/hooks'
260
+ import { isFigmaApiError, getErrorStatus, getErrorMessage, hasErrorStatus } from '@figma-vars/hooks'
262
261
 
263
- function ErrorHandling() {
264
- const { data, isLoading, error } = useVariables()
262
+ function ErrorHandler({ error }: { error: Error | null }) {
263
+ if (!error) return null
264
+
265
+ // Type guard for FigmaApiError
266
+ if (isFigmaApiError(error)) {
267
+ const status = error.statusCode
265
268
 
266
- if (error) {
267
- return (
268
- <div>
269
- <h2>Error</h2>
270
- <p>{error.message}</p>
271
- </div>
272
- )
269
+ if (status === 401) {
270
+ return <div>Authentication required. Please check your token.</div>
271
+ }
272
+ if (status === 403) {
273
+ return <div>Access forbidden. Check file permissions.</div>
274
+ }
275
+ if (status === 429) {
276
+ return <div>Rate limit exceeded. Please wait before retrying.</div>
277
+ }
278
+ if (status === 404) {
279
+ return <div>File or variable not found.</div>
280
+ }
273
281
  }
274
282
 
275
- // Render data or loading state
283
+ // Helper functions
284
+ const status = getErrorStatus(error) // number | null
285
+ const message = getErrorMessage(error) // string
286
+
287
+ // Convenience check
288
+ if (hasErrorStatus(error, 401)) {
289
+ // Handle unauthorized
290
+ }
291
+
292
+ return <div>Error: {message}</div>
276
293
  }
277
294
  ```
278
295
 
279
- ---
296
+ **Common HTTP Status Codes:**
297
+
298
+ - `401` - Unauthorized (invalid or missing token)
299
+ - `403` - Forbidden (insufficient permissions)
300
+ - `404` - Not Found (file/variable doesn't exist)
301
+ - `429` - Too Many Requests (rate limit exceeded)
302
+
303
+ ## ๐Ÿ”„ Cache Management
304
+
305
+ After mutations, use `useInvalidateVariables` to refresh cached data:
306
+
307
+ ```tsx
308
+ import { useUpdateVariable, useInvalidateVariables } from '@figma-vars/hooks'
309
+
310
+ function UpdateButton({ variableId }: { variableId: string }) {
311
+ const { mutate, isLoading } = useUpdateVariable()
312
+ const { invalidate, revalidate } = useInvalidateVariables()
313
+
314
+ const handleUpdate = async () => {
315
+ await mutate({
316
+ variableId,
317
+ payload: { name: 'New Name' },
318
+ })
319
+
320
+ // Option 1: Invalidate (refetch on next access)
321
+ invalidate()
322
+
323
+ // Option 2: Revalidate immediately (refetch now)
324
+ // revalidate()
325
+ }
326
+
327
+ return (
328
+ <button
329
+ onClick={handleUpdate}
330
+ disabled={isLoading}>
331
+ {isLoading ? 'Updating...' : 'Update Variable'}
332
+ </button>
333
+ )
334
+ }
335
+ ```
280
336
 
281
- ## ๐Ÿงฉ API Reference
337
+ ## โš™๏ธ SWR Configuration
282
338
 
283
- ### Core Hooks
339
+ Customize SWR behavior globally through the provider:
284
340
 
285
- - `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`. When `fallbackFile` is provided, it uses the local JSON data instead of making an API request.
286
- - `useVariableCollections()`: A convenience hook that returns just the variable collections from the main `useVariables` data.
287
- - `useVariableModes()`: A convenience hook that returns just the variable modes from the main `useVariables` data.
288
- - `useFigmaToken()`: A simple hook to access the token and file key from the context.
341
+ ```tsx
342
+ <FigmaVarsProvider
343
+ token={FIGMA_TOKEN}
344
+ fileKey={FIGMA_FILE_KEY}
345
+ swrConfig={{
346
+ revalidateOnFocus: false, // Don't refetch on window focus
347
+ dedupingInterval: 5000, // Dedupe requests within 5s
348
+ errorRetryCount: 3, // Retry failed requests 3 times
349
+ errorRetryInterval: 1000, // Wait 1s between retries
350
+ onError: error => {
351
+ // Global error handler
352
+ if (isFigmaApiError(error) && error.statusCode === 429) {
353
+ console.warn('Rate limited, backing off...')
354
+ }
355
+ },
356
+ }}>
357
+ <App />
358
+ </FigmaVarsProvider>
359
+ ```
289
360
 
290
- ### Provider Props
361
+ **Common SWR Options:**
291
362
 
292
- The `FigmaVarsProvider` accepts the following props:
363
+ - `revalidateOnFocus` - Refetch when window regains focus (default: `true`)
364
+ - `dedupingInterval` - Deduplication interval in ms (default: `2000`)
365
+ - `errorRetryCount` - Max retry attempts (default: `5`)
366
+ - `refreshInterval` - Polling interval in ms (default: `0` = disabled)
367
+ - `onError` - Global error callback
293
368
 
294
- - `token`: Figma Personal Access Token (PAT) for API authentication. Can be `null` when using `fallbackFile`.
295
- - `fileKey`: Figma file key for the target file. Required for API requests but can be `null` when using `fallbackFile`.
296
- - `fallbackFile`: Optional local JSON file (as object or string) to use instead of API requests. Perfect for users without Figma Enterprise accounts.
369
+ ## ๐Ÿ“š API Cheat Sheet
297
370
 
298
- ### Mutation Hooks
371
+ ### Hooks
299
372
 
300
- All mutation hooks return an object with the following shape: `{ mutate, data, isLoading, error }`.
373
+ - **Queries**: `useVariables` (local), `usePublishedVariables` (library/published), `useVariableCollections`, `useVariableModes`, `useFigmaToken`
374
+ - **Mutations**: `useCreateVariable`, `useUpdateVariable`, `useDeleteVariable`, `useBulkUpdateVariables`
375
+ - **Cache**: `useInvalidateVariables` (invalidate/revalidate cache)
301
376
 
302
- - `useCreateVariable()`: Creates a new variable. The `mutate` function expects a `CreateVariablePayload` object.
303
- - `useUpdateVariable()`: Updates an existing variable. The `mutate` function expects an object `{ variableId, payload }` where `payload` is an `UpdateVariablePayload`.
304
- - `useDeleteVariable()`: Deletes a variable. The `mutate` function expects the `variableId` (string) of the variable to delete.
305
- - `useBulkUpdateVariables()`: Creates, updates, or deletes multiple entities in a single batch operation. The `mutate` function expects a `BulkUpdatePayload` object.
377
+ ### Utilities
378
+
379
+ - **Filtering**: `filterVariables` (filter by type, name, etc.)
380
+ - **Error Handling**: `isFigmaApiError`, `getErrorStatus`, `getErrorMessage`, `hasErrorStatus`
381
+ - **Core helpers**: `fetcher`, `mutator`, constants for endpoints and headers
306
382
 
307
383
  ### Types
308
384
 
309
- All types are exported from `@figma-vars/hooks`. The core response type from Figma for local variables is `LocalVariablesResponse`.
385
+ - **Responses**: `LocalVariablesResponse`, `PublishedVariablesResponse`
386
+ - **Variables**: `FigmaVariable`, `FigmaCollection`, `VariableMode`
387
+ - **Mutations**: `BulkUpdatePayload`, `CreateVariablePayload`, `UpdateVariablePayload`
388
+ - **Errors**: `FigmaApiError` (extends `Error` with `statusCode`)
310
389
 
311
- ---
390
+ ## ๐Ÿ” Auth & Scope
391
+
392
+ - Header: `X-FIGMA-TOKEN: <PAT>`
393
+ - Scopes: `file_variables:read` for GETs, `file_variables:write` for mutations.
394
+ - Enterprise Full seat required for live API; fallback JSON works without a token.
395
+
396
+ ## โš ๏ธ Enterprise Requirement and Offline Options
312
397
 
313
- ## ๐Ÿ“š Storybook & Next.js Integration
398
+ - The Figma Variables REST API requires a Figma Enterprise seat for live requests. Without Enterprise, live calls will fail even with a valid PAT.
399
+ - 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.
400
+ - 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.
314
401
 
315
- The provider model makes integration trivial. Simply wrap your Storybook stories or Next.js pages with the `FigmaVarsProvider`.
402
+ ## ๐Ÿšซ Do Not Publish Tokens or File Keys
403
+
404
+ - Never commit PATs or file keys to git, Storybook static builds, or client bundles.
405
+ - Use environment variables (`process.env` / `import.meta.env`) and secret managers; keep them server-side where possible.
406
+ - Prefer `fallbackFile` with `token={null}`/`fileKey={null}` for demos and public Storybooks.
407
+ - Avoid logging tokens or keys; scrub them from error messages and analytics.
408
+
409
+ ## ๐Ÿ“ˆ Rate Limits
410
+
411
+ - Figma enforces per-token limits. Rely on SWR/TanStack caching, avoid unnecessary refetches, and prefer fallback JSON for static sites.
412
+ - Use `swrConfig` to customize `dedupingInterval` and `errorRetryCount` to optimize API usage.
413
+ - Handle `429` rate limit errors with `isFigmaApiError` and implement exponential backoff if needed.
414
+
415
+ ## ๐Ÿ“š Storybook & Next.js
416
+
417
+ - **Storybook decorator**: wrap stories once so hooks have context and tokens.
316
418
 
317
419
  ```tsx
318
- // In a Storybook story
319
- import { FigmaVarsProvider, useVariables } from '@figma-vars/hooks'
420
+ // .storybook/preview.tsx
421
+ import { FigmaVarsProvider } from '@figma-vars/hooks'
422
+ import type { Preview } from '@storybook/react'
423
+
424
+ const FIGMA_TOKEN = process.env.STORYBOOK_FIGMA_TOKEN
425
+ const FIGMA_FILE_KEY = process.env.STORYBOOK_FIGMA_FILE_KEY
426
+
427
+ const preview: Preview = {
428
+ decorators: [
429
+ Story => (
430
+ <FigmaVarsProvider
431
+ token={FIGMA_TOKEN}
432
+ fileKey={FIGMA_FILE_KEY}>
433
+ <Story />
434
+ </FigmaVarsProvider>
435
+ ),
436
+ ],
437
+ }
320
438
 
321
- export const TokensStory = () => (
322
- <FigmaVarsProvider
323
- token='YOUR_TOKEN'
324
- fileKey='YOUR_FILE_KEY'>
325
- <TokenList />
326
- </FigmaVarsProvider>
327
- )
439
+ export default preview
440
+ ```
441
+
442
+ - **Next.js App Router**: provide context in a shared provider file.
328
443
 
329
- const TokenList = () => {
330
- const { data } = useVariables()
331
- return <pre>{JSON.stringify(data?.variables, null, 2)}</pre>
444
+ ```tsx
445
+ // app/providers.tsx
446
+ import { FigmaVarsProvider } from '@figma-vars/hooks'
447
+
448
+ export function Providers({ children }: { children: React.ReactNode }) {
449
+ return (
450
+ <FigmaVarsProvider
451
+ token={process.env.NEXT_PUBLIC_FIGMA_TOKEN}
452
+ fileKey={process.env.NEXT_PUBLIC_FIGMA_FILE_KEY}>
453
+ {children}
454
+ </FigmaVarsProvider>
455
+ )
332
456
  }
333
457
  ```
334
458
 
459
+ ## ๐Ÿงช Tooling & Quality Gates
460
+
461
+ - `pnpm run build`, `pnpm test`, `pnpm run test:coverage`
462
+ - `pnpm run check:publint`, `pnpm run check:attw`, `pnpm run check:size`
463
+
464
+ ## ๐Ÿงญ Release Checklist (for 3.0.0)
465
+
466
+ - Run `pnpm run check:release`
467
+ - Tag `v3.0.0` (CI publishes to npm)
468
+ - Update dist-tags on npm if needed (`latest` โ†’ 3.0.0)
469
+
335
470
  ---
336
471
 
337
472
  ## ๐Ÿ“ Contributing
@@ -341,4 +476,4 @@ PRs and issues are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for gu
341
476
  ## ๐Ÿ“ License
342
477
 
343
478
  This project is licensed under the [MIT License](LICENSE).
344
- ยฉ 2024โ€“2025 Mark Learst
479
+ ยฉ 2024โ€“2026 Mark Learst