Figstract bridges the process for maintaining design systems (in Figma) between frontend engineers and designers and helps automate mundane upkeep tasks.
This tool extracts the following design system tokens produced by designers in Figma:
- Assets (raster and vector assets like images and icons)
- Design system variables (booleans, numbers, strings, and colors)
And imports them in native formats for frontend engineers to use on:
- Web
- Android (natively, using Android SDKs)
- iOS (natively, using Apple / iOS SDKs)
Figstract comes with an out-of-the-box CLI, which includes options for filtering and extraction, but has been designed to be modular so engineers can compose their own CLIs or other tools based on Figstract's foundation.
Note
While Figstract is pre-release, some CLI options may change
Figstract uses the Figma REST API and has the following features:
- Concurrent and Parallel: Uses concurrency and multithreading to process Figma files at speed
- Multi-file: Operations can handle multiple Figma files in parallel
- Flexible filters: Manage included and excluded tokens
- Theme-aware: Supports light/dark theme variant mapping for variables, generating separate light and dark values in a single pass
Figstract operations use a processing pipeline:
- Reading: Accessing Figma files
- Exporting: Extracting tokens
- Importing: Converting tokens
To create a runnable fat JAR, just run the following Gradle command:
./gradlew :cli:shadowJarThe compiled JAR will be located in cli/build/libs/cli-<version>-all.jar (make sure you use the .jar with the name ending in -all).
Run the following command to run Figstract and list the subcommands and options:
java -jar /path/to/cli.jar --helpThe CLI can be configured with CLI args, or by supplying a [subcommandName].properties file with the same keys as the arguments in the working directory (e.g. assets.properties).
Some corporates may use of custom CA certificates for internal monitoring, causing the JVM to fail certificate verification and prevent access to the Figma API or where assets are stored. You can create a custom truststore that includes your organisation's CA certificates:
- Copy default JVM cacerts:
cp $JAVA_HOME/lib/security/cacerts ~/.figstract-cacerts
- Export your organisation's CA certs as a
.pemor.cerfile. This really depends on how CA certs are managed within your organisation. On MacOS, these may be located in Keychain access under the System or System Roots keychains. CA certs may also be provided for use for these purposes. - Import each CA cert into the truststore using (include the appropriate
<ca-cert>and/path/to/ca/cert.pemvalues per cert):keytool -importcert \ -alias <cert-alias> \ -file /path/to/ca/cert.pem \ -keystore ~/.figstract-cacerts \ -storepass changeit \ -noprompt
- Configure truststore when running Figstract by providing the java truststore flags (
javax.net.ssl.trustStoreandjavax.net.ssl.trustStorePassword):java -Djavax.net.ssl.trustStore=~/.figstract-cacerts \ -Djavax.net.ssl.trustStorePassword=changeit \ -jar /path/to/cli.jar [options]
If your network requires a proxy to reach the Figma API or asset download URLs, configure it using:
--proxyType:HTTPorSOCKS(default: none)--proxyHost: proxy hostname--proxyPort: proxy port
Figstract supports the following authentication mechanisms to accessing Figma:
- Personal Access Tokens (PATs). Refer to the Figma documentation and API reference for more info.
When generating credentials, ensure that the following scopes are configured.
| Operation | Required scope |
|---|---|
| assets | File content |
| variables | File content, Variables |
Caution
Ensure scopes are set to Read only and tokens are refreshed regularly
Either one auth credential can be generated with all the scopes above, or specific auth credentials can be created for each subcommand.
Figstract uses kotlin-logging and Logback for logging, and logs errors to the console by default.
When using the CLI, the log level can be configured using the --logLevel option (e.g. --logLevel DEBUG), or by configuring logback using environment variables (refer to https://logback.qos.ch/manual/configuration.html#configFileProperty).
Figstract extracts two types of assets from Figma files: artwork (raster images) and icons (vector graphics). Both are configured independently and can target multiple platforms in a single run.
By default, Figstract locates assets by traversing the Figma node tree:
- Artwork are parent nodes containing a child with an image fill. Cropped and uncropped variants are produced by exporting either the parent node or the image fill child.
- Icons are
Componentnodes containingVectorchildren, placed as direct children of a canvas (page)
If your Figma file uses a non-standard layout, use JsonPath to locate nodes instead.
Each asset handler targets a single Figma file using its file key.
The file can be targeted at a specific version or branch.
Figma file branches have their own distinct file key, but can alternatively be accessed using the parent file's key combined with --[artwork | icon]FigmaFileBranchName.
The following args provide file targeting options:
--artworkFigmaFile/--iconsFigmaFile: the Figma file key--artworkFigmaFileBranchName/--iconsFigmaFileBranchName: file branch name--artworkFigmaFileVersion/--iconsFigmaFileVersion: file version
Asset importing uses a composable pipeline, allowing you to chain transformers and handlers together. This makes it possible to apply format conversion, renaming, and other processing steps in a flexible way.
Assets can be filtered by canvas (page), node, and parent node name using regex patterns. Include and exclude filters are mutually exclusive. Filters can be repeated to supply multiple patterns.
- Canvas filters:
--artworkFilterIncludedCanvas/--artworkFilterExcludedCanvas(andiconsequivalents) - Node filters:
--artworkFilterIncludedNode/--artworkFilterExcludedNode - Parent node filters:
--artworkFilterIncludedParentNode/--artworkFilterExcludedParentNode
JsonPath expressions (Stefan Goessner's implementation) can be used to locate nodes. Expressions are relative to each canvas and should locate the required node (refer to the Figma API Node reference for node types). Canvas and node filters will be applied, but parent node filters aren't supported when using JsonPath.
Use --artworkJsonPath or --iconsJsonPath to supply the expression.
Output file names can be customised using a format string.
The following tokens are supported, wrapped in {}:
canvas.id— the canvas (page) IDcanvas.name— the canvas (page) namenode.id— the node IDnode.name— the node namenode.name.split "<sep>" first— splits the node name on<sep>(a regex) and takes the first segmentnode.name.split "<sep>" last— splits the node name on<sep>(a regex) and takes the last segmentnode.name.split "<sep>" <N>— splits the node name on<sep>(a regex) and takes the segment at indexN
If the separator is not found in the node name, the full name is used.
Names are automatically cased to match platform conventions (snake_case for Android and Web, UpperCamelCase for iOS).
Override the format using --artworkAndroidFormat, --artworkIosFormat, --artworkWebFormat (and icons equivalents).
Processing records prevent re-processing Figma files that haven't changed since the last run, based on the file's last-modified timestamp.
The record is stored as processing_record.json in the output directory.
--processingRecordEnabled(defaulttrue): enable or disable processing records--processingRecordName: a unique name suffix for the record file, useful when running multiple configurations against the same Figma file (e.g.processing_record_icons.json)
At least one platform must be enabled (--platformAndroid, --platformIos, --platformWeb).
Output is written to android/, ios/, and web/ subdirectories within the output directory.
Artwork supports two crop modes, configured per run:
--artworkCreateUncropped(defaulttrue): exports the full frame using the parent--artworkCreateCropped(defaultfalse): exports the image fill only, appending a_croppedsuffix
- Artwork: PNG
- Icons: SVG
- Artwork: PNG scaled into density buckets (
mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi) from axxxhdpisource - Icons: SVG converted to Android Vector Drawable (AVD), with colors replaced by a magenta placeholder for tinting
- Artwork: PNG stored in an Asset Catalog with
@1x-@3xscales from a@3xsource. Optionally, HEIC format can be used instead of PNG via--artworkIosOutputHeic(see HEIC output for setup requirements). - Icons: SVG stored in an Asset Catalog at
@1xscale
HEIC can be enabled for iOS artwork via --artworkIosOutputHeic. This requires ImageMagick.
macOS
brew install imagemagickLinux
apt-get install -y imagemagickWindows: Not supported as ImageMagick does not support writing HEIC on Windows due to licensing restrictions. Consider running this on macOS or Linux (including WSL2).
Figstract can extract local variables from Figma files. All variable types (booleans, numbers, strings and colors) are supported.
All variable types will be outputted by default, but can be configured to be omitted completely.
Variables can be filtered by collection name, mode name, and type. Include and exclude filters are mutually exclusive for a given dimension and can be repeated to supply multiple patterns.
- Collection filters:
--filterIncludedVariableCollection/--filterExcludedVariableCollection - Mode filters:
--filterIncludedMode/--filterExcludedMode - Type filters:
--includeTypeBoolean,--includeTypeNumber,--includeTypeString,--includeTypeColor(all defaulttrue)
Figstract supports resolving variables to themes.
When a Figma variable collection uses modes to represent light and dark themes, Figstract can map these to a LightAndDark resolved mapping with separate light and dark values.
This is particularly useful for Android Compose, where light and dark companion object properties are generated on the output type, ready to be used with isSystemInDarkTheme().
When no light/dark mapping is detected, all modes are output individually as separate nested objects.
Note
Coming soon
All output formats use the variable collection's name as the file name. Variables will be first grouped by mode then by variable type within each variable collection's file. Colors can be configured to be outputted as either Hex or RGBA values (RGBA values are floats between 0 and 1 inclusive).
Note: Variable collection, mode and variable names will be sanitised to conform with platform / language conventions.
For web (or any platform), Figstract generates JSON files with the following format:
My Variable Collection.json
{
"My Mode": {
"booleans": {
"bool var 1": true,
"bool var 2": false
},
"numbers": {
"number var 1": 123.45,
"number var 2": -543.21
},
"strings": {
"string var 1": "Hello",
"string var 2": "World"
},
"colors": {
"color rgba var 1": {
"r": 0.1,
"g": 0.2,
"b": 0.3,
"a": 1.0
},
"color rgba var 2": {
"r": 1.0,
"g": 1.0,
"b": 1.0,
"a": 1.0
},
"color hex var 1": "0xFF19334C",
"color hex var 2": "0xFFFFFFFF"
}
}
}Refer to JsonVariableDataWriter for the implementation.
Figstract generates an R-file-like Kotlin object with constants with the following format:
MyVariableCollection.kt
package your.pkg.here
import androidx.compose.ui.graphics.Color
import kotlin.Double
public object MyVariableCollection {
public object MyMode {
public object Booleans {
public val boolVar1: Boolean = true
public val boolVar2: Boolean = false
}
public object Numbers {
public val numberVar1: Double = 123.45
public val numberVar2: Double = -543.21
}
public object Strings {
public val stringVar1: String = "Hello"
public val stringVar2: String = "World"
}
public object Colors {
public val colorRgbaVar1: Color = Color(
red = 0.1f,
green = 0.2f,
blue = 0.3f,
alpha = 1.0f,
)
public val colorRgbaVar2: Color = Color(
red = 1.0f,
green = 1.0f,
blue = 1.0f,
alpha = 1.0f,
)
public val colorHexVar1: Color = Color(0xFF19334C)
public val colorHexVar2: Color = Color(0xFFFFFFFF)
}
}
}When light/dark theme variant mapping is active (see Theme variant mapping), the output uses a data class with light and dark companion properties instead of nested objects:
public object MyVariableCollection {
public data class Colors(
val primaryColor: Color,
val backgroundColor: Color,
) {
public companion object {
public val light: Colors = Colors(
primaryColor = Color(0xFF0057FF),
backgroundColor = Color(0xFFFFFFFF),
)
public val dark: Colors = Colors(
primaryColor = Color(0xFF82AAFF),
backgroundColor = Color(0xFF121212),
)
}
}
}The package must be specified when configuring this output.
Refer to AndroidComposeVariableDataWriter for the implementation.
Note
Coming soon
Note
Coming soon
Figstract is structured as a multi-module Gradle project. The modules are layered so that platform-specific modules depend on core, and the CLI depends on all of them.
| Module | Artifact | Description |
|---|---|---|
library-core |
com.anifichadia.figstract:library-core |
Core pipeline abstractions, Figma REST API client, JSON variable writer, JsonPath-based asset extraction |
library-android |
com.anifichadia.figstract:library-android |
Android-specific importers: WEBP, AVD conversion, density scaling, etc. |
library-ios |
com.anifichadia.figstract:library-ios |
iOS-specific importers: iOS asset scaling, asset catalog management |
cli-core |
com.anifichadia.figstract:cli-core |
Reusable CLI building blocks (option groups, base commands) for composing custom CLIs on top of Figstract |
cli |
com.anifichadia.figstract:cli |
Out-of-the-box CLI built on Clikt, bundles all modules into a fat JAR via Shadow |
If you want to build your own tooling on top of Figstract rather than using the CLI out of the box, depend on only the library modules you need and use the cli-core and cli modules as a template.
// Core only (custom tooling, web/JSON output)
implementation("com.anifichadia.figstract:library-core:<version>")
// Android-specific output
implementation("com.anifichadia.figstract:library-android:<version>")
// iOS-specific output
implementation("com.anifichadia.figstract:library-ios:<version>")
// CLI core
implementation("com.anifichadia.figstract:cli-core:<version>")Snapshots are available from Maven Central's snapshot repository:
// settings.gradle.kts
repositories {
maven {
name = "Central Portal Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
mavenContent { snapshotsOnly() }
}
mavenCentral()
}- Shell wrapper
- GitHub action support
- Better error handling
- Android XML variable output
- iOS variable output