This document provides technical documentation for developers contributing to the Figma to WordPress theme.json Exporter plugin.
- Architecture Overview
- Directory Structure
- Plugin Entry Point
- Message Protocol
- Data Flow
- Collection Processor System
- Key Modules
- Utility Functions
- Adding New Features
- Testing
The plugin follows a dual-thread architecture required by Figma plugins:
┌─────────────────────────────────────────────────────────────┐
│ UI Thread │
│ ┌─────────────────┐ ┌─────────────────────────────────┐│
│ │ export.html │ │ css-vars.html ││
│ │ - File upload │ │ - Apply CSS var syntax ││
│ │ - Options UI │ │ - Status display ││
│ │ - Preview │ │ ││
│ │ - Download │ │ ││
│ └────────┬────────┘ └────────────────┬────────────────┘│
│ │ │ │
│ └──────────────┬───────────────┘ │
│ │ postMessage │
└──────────────────────────┼──────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────────────┐
│ │ │
│ ┌───────────────────────▼────────────────────────────────┐│
│ │ code.ts ││
│ │ (Message Router) ││
│ └───────────────────────┬────────────────────────────────┘│
│ │ │
│ ┌───────────────────────▼────────────────────────────────┐│
│ │ export/index.ts ││
│ │ (Export Orchestrator) ││
│ └───────────────────────┬────────────────────────────────┘│
│ │ │
│ ┌───────────────────────▼────────────────────────────────┐│
│ │ collections/registry.ts ││
│ │ (Processor Dispatch) ││
│ └───────────────────────┬────────────────────────────────┘│
│ │ │
│ ┌───────────────────────▼────────────────────────────────┐│
│ │ collections/processors/* ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ primitives │ │ wp-settings │ │ wp-elements │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────┘ ││
│ └────────────────────────────────────────────────────────┘│
│ Code Thread │
└─────────────────────────────────────────────────────────────┘
src/
├── code.ts # Plugin entry point, message handler
├── types.ts # TypeScript interfaces and types
│
├── export/
│ └── index.ts # Main export orchestration (exportToJSON)
│
├── collection/
│ └── index.ts # processCollectionData, processCollectionModeData
│
├── collections/
│ ├── types.ts # CollectionProcessor interface
│ ├── registry.ts # Processor registration and dispatch
│ └── processors/
│ ├── primitives.ts # Primitives collection processor
│ ├── wp-settings.ts # wp.settings.* processor
│ ├── wp-settings-colors.ts# wp.settings.color processor
│ ├── wp-settings-subset.ts# wp.settings.{layout,spacing,typography,shadow}
│ ├── wp-settings-extended.ts # wp.settings.{background,border,dimensions,position}
│ ├── wp-styles-global.ts # wp.styles processor for global styles
│ ├── wp-elements.ts # wp.elements.* processor
│ ├── wp-blocks.ts # wp.blocks.{namespace/block} processor
│ └── fallback-selected.ts # Fallback for selected non-wp collections
│
├── types/
│ └── theme-json.ts # WordPress theme.json v3 type definitions
│
├── color/
│ └── index.ts # Color preset generation
│
├── typography/
│ └── index.ts # Typography preset generation
│
├── spacing/
│ └── index.ts # Spacing preset generation
│
├── button/
│ └── index.ts # Button variant style generation
│
└── utils/
├── index.ts # Core utilities (merge, format, convert)
├── css.ts # CSS custom property builders
├── color.ts # RGB to Hex conversion
├── figma-variables.ts # CSS variable syntax application
└── validation.ts # theme.json validation utilities
The main entry point is src/code.ts. It handles:
- UI Initialization - Shows the appropriate UI based on the command
- Message Routing - Dispatches incoming messages to handlers
- Response Coordination - Sends results back to the UI
| Command | UI File | Description |
|---|---|---|
export |
export.html | Main export interface |
css-vars |
css-vars.html | CSS variable syntax utility |
figma.ui.onmessage = async (msg) => {
switch (msg.type) {
case 'EXPORT': // Trigger exportToJSON()
case 'GET_COLOR_PRESETS': // Return color presets for UI
case 'GET_COLLECTIONS': // Return available collections
case 'APPLY_CSS_VAR_SYNTAX': // Apply CSS var syntax
case 'RESIZE': // Handle window resize
}
};// Export request
{ type: 'EXPORT', options: ExportOptions }
// Get color presets for customization modal
{ type: 'GET_COLOR_PRESETS', selectedCollectionIds?: string[] }
// Get available collections
{ type: 'GET_COLLECTIONS' }
// Apply CSS variable syntax
{ type: 'APPLY_CSS_VAR_SYNTAX', overwriteExisting: boolean }
// Resize plugin window
{ type: 'RESIZE', width: number, height: number }// Export result
{
type: 'EXPORT_RESULT',
files: Array<{ fileName: string; body: object }>,
error?: string
}
// Color presets for customization
{
type: 'COLOR_PRESETS_RESULT',
colorPresets: ColorPresetData[]
}
// Available collections
{
type: 'COLLECTIONS_RESULT',
collections: Array<{ id, name, modeCount, variableCount }>
}exportToJSON(options)
│
├─1. Fetch local variable collections from Figma
│
├─2. Filter collections (by selectedCollections if provided)
│
├─3. Identify and process Primitives collection first
│
├─4. Route remaining collections through processor registry
│ ├── primitivesProcessor
│ ├── wpSettingsColorsProcessor
│ ├── wpSettingsSubsetProcessor
│ ├── wpSettingsProcessor
│ ├── wpElementsProcessor
│ └── fallbackSelectedProcessor
│
├─5. Process each mode's variables
│ ├── Convert colors to hex
│ ├── Resolve variable aliases to CSS var references
│ ├── Handle fluid variables (Desktop/Mobile modes)
│ └── Apply unit formatting (px, rem)
│
├─6. Generate specialized presets
│ ├── Color presets → settings.color.palette
│ ├── Typography presets → settings.typography
│ └── Spacing presets → settings.spacing.spacingSizes
│
├─7. Generate button variant style files
│
├─8. Construct final theme.json structure
│
└─9. Return files array
For each variable in a collection:
- Fetch Details - Get name, resolvedType, valuesByMode
- Extract Value - Get value for target mode (Desktop for fluid)
- Resolve Aliases - Follow VARIABLE_ALIAS chains (max 10 hops)
- Convert Values
- Colors: RGB → Hex/RGBA
- Numbers: Add units (px/rem)
- Aliases: Convert to
var(--wp--custom--...)
- Build Nested Structure - Split name by "/" to create object hierarchy
- Merge into Theme - Route to appropriate location
The processor system uses a chain-of-responsibility pattern. Each processor:
- Has a
matches(name)function to determine if it handles a collection - Has a
process(collection, ctx, options)function to process the collection
// src/collections/types.ts
interface CollectionProcessor {
matches: (collectionName: string) => boolean;
process: (
collection: VariableCollection,
ctx: ProcessorContext,
options: ExportOptions
) => Promise<void>;
}
interface ProcessorContext {
themeJson: object; // Main theme.json being built
allFiles: FileOutput[]; // Additional output files
processedVariants: Set<string>; // Track processed button variants
}Processors are evaluated in order. The first matching processor handles the collection:
| Order | Processor | Matches | Destination |
|---|---|---|---|
| 1 | primitivesProcessor | primitives (case-insensitive) |
settings.custom |
| 2 | wpSettingsColorsProcessor | wp.settings.color or wp.settings.colors |
Button styles only |
| 3 | wpSettingsSubsetProcessor | wp.settings.{layout|spacing|typography|shadow} |
settings.{subset} |
| 4 | wpSettingsExtendedProcessor | wp.settings.{background|border|dimensions|position} |
settings.{subset} |
| 5 | wpSettingsProcessor | wp.settings.* |
settings or settings.custom |
| 6 | wpStylesGlobalProcessor | wp.styles or wp.styles.* |
styles (root) |
| 7 | wpElementsProcessor | wp.elements.* |
styles.elements |
| 8 | wpBlocksProcessor | wp.blocks.{namespace/block} |
styles.blocks |
| 9 | fallbackSelectedProcessor | Everything else | settings.custom |
- Create a new file in
src/collections/processors/:
// src/collections/processors/my-processor.ts
import { CollectionProcessor } from '../types';
import { processCollectionData } from '../../collection';
export const myProcessor: CollectionProcessor = {
matches: (name: string) => {
return name.toLowerCase().startsWith('my-prefix.');
},
process: async (collection, ctx, options) => {
const data = await processCollectionData(collection, options);
// Process and merge data into ctx.themeJson
// Or add files to ctx.allFiles
}
};- Register in
src/collections/registry.ts:
import { myProcessor } from './processors/my-processor';
export const collectionProcessors: CollectionProcessor[] = [
primitivesProcessor,
myProcessor, // Add in appropriate order
// ... other processors
fallbackSelectedProcessor, // Always keep last
];Main function: exportToJSON(options: ExportOptions)
Coordinates the entire export process. Creates the base theme.json structure, routes collections through processors, and generates presets.
Functions:
processCollectionData(collection, options)- Process entire collectionprocessCollectionModeData(collection, mode, options)- Process single mode
Handles the core logic of converting Figma variables to theme.json structure.
Functions:
getColorPresets(selectedColorIds?)- Get presets with CSS var referencesgetColorPresetsWithValues(selectedColorIds?)- Get presets with actual hex valuesgetAllColorPresets(selectedCollectionIds?)- Get all presets for UI display
Functions:
getWordPressTypographyPresets(options?)- Modern WordPress typography structuregetTypographyPresets(options?)- Legacy preset structure
Converts Figma text styles to WordPress typography presets.
Function: processButtonStyles(buttonData, allFiles)
Generates separate theme.json style files for button variants (secondary, tertiary, outline, etc.).
| Function | Purpose |
|---|---|
isVariableAlias(value) |
Check if value is VARIABLE_ALIAS type |
isWordPressSettingsCollection(name) |
Detect wp.settings.* collections |
isWordPressElementsCollection(name) |
Detect wp.elements.* collections |
isWordPressBlocksCollection(name) |
Detect wp.blocks.* collections |
isWordPressStylesCollection(name) |
Detect wp.styles collections |
extractWordPressBlockName(name) |
Extract block name from collection |
isCoreBlock(blockName) |
Check if block is WordPress core |
getBlockBadge(blockName) |
Get Core/Custom badge info for UI |
isValidElement(elementName) |
Validate element against schema |
isValidPseudoSelector(selector) |
Validate pseudo-selector |
mergeCollectionData(target, path, data) |
Deep merge at path |
deepMerge(target, source) |
Recursive object merge |
shouldAddPxUnit(nameParts, value) |
Determine if px units needed |
formatValueWithUnits(nameParts, value) |
Add appropriate units |
toCamelCase(input) |
Convert to camelCase |
convertPxToRem(pxValue) |
Convert pixels to rem |
| Function | Purpose |
|---|---|
validatePseudoSelector(selector) |
Validate a pseudo-selector string |
isPseudoSelectorKey(key) |
Check if key starts with ":" |
extractPseudoSelectors(obj) |
Separate pseudo-selectors from other keys |
validatePseudoSelectorsDeep(obj, path) |
Recursively validate all pseudo-selectors |
getAllowedPseudoSelectorsForElement(element) |
Get allowed pseudo-selectors for element |
| Function | Purpose |
|---|---|
buildWpCustomPropertyPath(nameParts) |
Build --wp--custom--... path |
buildCssVarReference(nameParts) |
Build var(--wp--custom--...) |
sanitizeCollectionName(name) |
Convert to valid CSS property |
camelToKebabCase(value) |
Convert camelCase to kebab-case |
| Function | Purpose |
|---|---|
rgbToHex(color) |
Convert RGB object to hex string |
- Add the option to
ExportOptionsinsrc/types.ts:
interface ExportOptions {
// ... existing options
myNewOption?: boolean;
}- Update the export.html UI to include the option
- Handle the option in
src/export/index.ts
- Create a new module in
src/{preset-type}/index.ts - Implement functions to extract and format the preset data
- Call your function from
exportToJSON()insrc/export/index.ts - Merge the output into the appropriate theme.json location
- Update
processCollectionModeData()insrc/collection/index.ts - Add conversion logic for the new type
- Update unit handling in
src/utils/index.tsif needed
The project uses Vitest for testing.
npm run test # Run tests in watch mode
npm run test:run # Run tests once
npm run test:coverage # Generate coverage reportTests are co-located with source files using the .test.ts extension:
src/
├── utils/
│ ├── index.ts
│ └── index.test.ts
├── color/
│ ├── index.ts
│ └── index.test.ts
import { describe, it, expect } from 'vitest';
import { myFunction } from './index';
describe('myFunction', () => {
it('should handle basic case', () => {
const result = myFunction('input');
expect(result).toBe('expected');
});
});| Command | Description |
|---|---|
npm run build |
Build for production |
npm run watch |
Watch mode for development |
npm run lint |
Run ESLint |
npm run lint:fix |
Auto-fix lint issues |
npm run lint:types |
TypeScript type checking |
npm run test |
Run tests in watch mode |
npm run test:coverage |
Generate coverage report |
Variables with paths create nested structures:
// Variable: "color/button/primary/background"
// Becomes:
{
color: {
button: {
primary: {
background: value
}
}
}
}Collections with Desktop/Mobile modes produce fluid objects:
// Desktop: 32px, Mobile: 16px
{
"fluid": "true",
"min": "16px",
"max": "32px"
}Aliases are converted to CSS variable references:
// Figma: References "color/primary"
// Output: "var(--wp--custom--color--primary)"For wp.elements colors, aliases can export as preset references:
// elementsColorExportMode: "preset"
// Output: "var:preset|color|primary"