v1.0.0 · Unity 2021.3+

FigSync

The Figma layout bridge for Unity. Paste a Figma URL. Press Sync. Your design lands in the scene as a real UGUI Canvas — with the right anchors, the right fonts, the right gradients, and the right rounded corners. Keep designing in Figma, keep editing in Unity. Re-sync any time.

Unity 2021.3+ UGUI / Canvas URP / Built-in Editor Tool Figma & FigJam
ⓘ
Two paths to a sync. Either authenticate against Figma's REST API directly (Personal Access Token or OAuth), or install the bundled FigSyncExporter Figma plugin and push from Figma to Unity over a local bridge. The local bridge supports FigJam files and offline sync; the REST path is one-click after activation.
💡
Edits survive re-sync. When you re-sync a file, FigSync hashes every node and reuses scene GameObjects whose hash matches. Your wired references, added components, and child edits stay intact — only changed nodes are redrawn.

This document covers the full FigSync workflow. Start with Installation and Quick Start; go deep on How It Works for the pipeline detail; reach Troubleshooting when something doesn't import the way you expect.

⤓ Installation

Import once. The Hub window lights up from the menu.

From the Unity Asset Store

Open Package Manager
In Unity 2021.3 or later, open Window → Package Manager and switch the dropdown to My Assets.
Find FigSync, click Download then Import
Accept the import dialog with everything checked. The package lands at Assets/FigSync/.
Wait for compile
Unity recompiles editor scripts. First-time compile takes ~30 s; later imports are faster.
Open the Hub
Tools → FigSync. Everything you need — URL, sync, settings, status — is in one window.

Prerequisites

FigSync uses Unity packages that ship with a default Unity 2021.3+ install:

If TMP Essential Resources isn't imported yet, the first sync will surface a one-time warning. Run Window → TextMeshPro → Import TMP Essential Resources and re-sync.

What gets installed

Assets/FigSync/
  Editor/                     // Pipeline, drawers, settings, hub window
    Bridge/                   // Local HTTP bridge for the Figma plugin
    Drawers/                  // One drawer per Unity UGUI component
    Hub/                      // Tools → FigSync window + settings store
    Pipeline/                 // Numbered Step01..Step26 deterministic flow
    Settings/                 // Per-tab settings UI (UI Toolkit)
    Web/                      // Figma REST + Google Fonts clients
  Runtime/                    // Shaders, helper components, ScriptableObjects
    Components/               // FigSyncSyncHelper, dropshadow, blur, etc.
    Shaders/                  // SDF rounded-corner + gradient
  Generated/                  // Per-sync output: sprites, fonts, snapshot, report
  Documentation/              // You are hereProject
ⓘ
The Generated/ folder is yours. FigSync writes downloaded sprites, fonts, and the diff snapshot here. It's safe to re-sync anytime; the folder is rebuilt deterministically.

🔌 Figma Plugin (Optional)

The fastest path for FigJam files, complex frames, or offline sync.

FigSync ships with a Figma plugin called FigSyncExporter. Installed in Figma, it captures the full plugin-API node tree (which is richer than REST), bundles every fill image and pre-rendered vector raster, and pushes the result to Unity over a local HTTP bridge. The Unity Hub picks up the push automatically — no copy/paste of node ids.

Why use the plugin path

☀️ FigJam support

Stickies, connectors, shape-with-text only appear in the plugin's node tree — FigJam files won't import via REST.

⚡ One-click push

Hit Send to Unity in Figma; Unity starts the sync. No personal-access token to create and no URL to paste — you push straight from the file you're designing.

📁 Offline iteration

Each push saves a .figsync.zip bundle locally; you can re-sync from disk later without Figma being open.

📊 Richer geometry

Plugin API exposes fillGeometry path data on every vector node; REST is occasionally sparser.

Install the plugin

In Figma, open the Plugins menu
Menu → Plugins → Development → Import plugin from manifest...
Point at the bundled manifest
In Unity, run Tools → FigSync → Open Figma Plugin Folder (or browse to Assets/FigSync/FigmaPlugin/), then select manifest.json. Tip: Tools → FigSync → Set Up Figma Bridge… walks you through this with live connection status.
Verify the bridge is running
In Unity, open Tools → FigSync → Bridge. The status should read Bridge: listening on 127.0.0.1 (ports 8473–8475) — the Bridge badge at the top of the Hub mirrors it.
Push from Figma
Open the FigSyncExporter plugin from your Plugins menu, choose scope (Current page / All pages), click Send to Unity. Unity receives the bundle and starts a sync automatically.
⚠
The bridge is loopback-only. The HTTP listener binds to 127.0.0.1 and accepts only CORS requests originating from Figma's domains. No external network traffic.

⚡ Quick Start

Zero to a synced Canvas in about a minute.

Open the Hub
Menu: Tools → FigSync. The window has tabs for Sync, Settings, Bridge, Tools, and Help.
Pick an authentication method
Either paste a Personal Access Token in Settings → Auth, or install the Figma plugin for one-click push. (You can do both; the plugin path wins when both are configured.)
Paste a Figma URL
Copy any frame URL from Figma (right-click frame → Copy link). Paste into the URL field in the Hub's Sync tab.
Click Sync
The pipeline runs 27 deterministic steps. Status updates per step. First sync takes ~10-90 s depending on sprite count.
Inspect the scene
A FigSync Canvas GameObject appears in the active scene. Frames live under FigSync Canvas / FigSync Import / Page 1 / <Frame>. Game view shows the design rendered.
Edit, re-sync, repeat
Tweak Figma, click Sync again. FigSync diffs against the previous run; only changed nodes redraw. Your wired references and added components are preserved.
💡
The Diff Window shows what changed before any scene mutation. If a re-sync would touch a frame you're mid-editing, untick that frame in the diff window to defer it. The deferred frame stays untouched; the rest of the page syncs as usual.

⚙ How It Works

A 27-step deterministic pipeline, runnable resumable cancellable.

Source → Flatten → Tag & Classify → Hash & Diff → Draw → Sprites & Fonts → Finalise

Each sync runs as a sequence of named steps. Steps log their start and finish to the Console (look for the [FigSync] prefix); the Hub window mirrors the same status as a per-step progress strip. A failed step stops the pipeline early; the scene either keeps the prior state intact (if the failure happened pre-Draw) or surfaces the partial result with a HelpBox explaining where it broke.

Steps, in order

StepWhat it does
DownloadNodesLoads the Figma node tree from REST, the plugin bundle, or local cache.
WrapInPageEnsures the document root is wrapped in a CANVAS / PAGE node for uniform traversal.
ClearCachesResets per-sync sidecars (UnchangedNodeIds, DeferredNodeIds, external-children rescue map).
FlattenWalks the tree depth-first; produces a FlatList of NodeAux entries with parent/depth indices.
TagTags each node by FigmaType + manual sidecar markers (Frame, Text, Image, Vector, Mask, Shadow, Page, etc.).
ClassifyImagesDecides which nodes route through the PNG raster path vs the procedural draw path.
PropagateInsideDownloadableMarks descendants of a Downloadable parent so they aren't drawn separately.
HashComputes a 128-bit content hash per node (visual properties + children) using xxHash3.
NameFolders / NameFiles / NameStylesheetClassesResolves Figma names through the sanitiser into Unity-safe folder, file, and class names.
PickRootFramesIdentifies top-level frames worth surfacing in the diff window.
ShowDiffWindowIf a prior snapshot exists, compute the diff and prompt the user to confirm or defer roots.
LoadPrefabMatchesWires Figma node ids back to existing scene GameObjects with matching FigSyncSyncHelper stamps.
DrawHierarchyThe main draw pass. One GameObject per Figma node; one drawer per tag.
RePickRootFrames / CountTags / SpritePathsPost-Draw planning for sprite IO.
DownloadSprites / FlushSpritesPulls /v1/images PNGs (or reads them from the plugin bundle); writes them to Generated/.
GenerateSprites / SliceSprites / ColorizeSpritesProcedural sprite synthesis for nodes that don't need raster (white quads, solid fills).
DedupeSpritesHash-merges visually identical sprite outputs so the project doesn't double-store them.
DownloadFontsResolves font families through Google Fonts (Webfonts API or fontsource fallback), bakes TMP _SDF assets.
FrameworkFinalisePer-backend post-pass: rounded corners, shadows, layout drivers, text binding, FigJam canvas dots.
DiagnosticReportWrites _figsync_report.json (telemetry) and _figsync_snapshot.json (hash snapshot for next-run diff).

What happens at Draw time

The Draw step iterates the FlatList. Each entry is either reused, deferred, or freshly drawn:

What happens at Finalise time

After every node has a GameObject, a per-backend Finalise pass runs second-pass work that needs the full hierarchy in place:

🔒 Authentication

Three options. Pick one. Or two.

Option 1 — Personal Access Token

Simplest path. Generate a token at figma.com/settings (Personal access tokens section), paste it into Tools → FigSync → Settings → Auth → Personal Access Token. The token stays in EditorPrefs (base64-obfuscated, never written to assets) and is sent only over HTTPS to Figma's REST API.

Option 2 — OAuth

Click Sign in with Figma in the Auth tab. A browser tab opens Figma's consent screen; after approval Figma redirects to a one-shot local listener on 127.0.0.1 with the auth code. FigSync exchanges that for an access + refresh token pair and stores both in EditorPrefs (same obfuscation as the PAT). Refresh tokens rotate automatically; expired tokens trigger a transparent refresh on next call.

Option 3 — The Figma plugin

No tokens needed. Install the plugin in Figma and push directly. The plugin reads node data using its own Figma plugin sandbox permissions; FigSync receives an HTTPS-style ZIP bundle over a loopback port. No credentials leave your machine.

 TokenOAuthPlugin
Setup time30 s1 min (browser hop)2 min (plugin install)
Re-auth neededNever (until you revoke)Refresh auto-rotatesNever
FigJam filesNo (REST limitation)No (REST limitation)Yes
Offline syncNoNoYes (saved ZIPs)
Rate limitedYes (Figma policy)Yes (Figma policy)No
What's storedOne token in EditorPrefsAccess + refresh tokens in EditorPrefsNothing
ⓘ
You can combine paths. Keep a token configured as a fallback while using the plugin as your daily driver. FigSync prefers the plugin bundle when one is freshly pushed, falls back to REST when a URL is pasted directly.

🌐 The Hub Window

One window, every control. Tools → FigSync.

Sync tab

The everyday surface. Paste a Figma URL, choose the import mode (Live URL, Offline ZIP, Bridge push), and click Sync. A status strip across the top of the window mirrors what's happening behind the scenes — current step, elapsed time, last warning. A right-side panel shows the last sync's diagnostic summary (node count, sprite count, fonts bound, missing fonts, anything that went sideways).

ⓘ
Which import mode? Choose in the Sync tab's mode dropdown:
• Live URL — paste a Figma URL + token; best for quick one-off pulls.
• Bridge — push live from the Figma plugin (Send to Unity); best for daily iteration, and the only path that imports FigJam.
• Offline ZIP — import a saved .figsync.zip; best when Figma and Unity are on different machines, or with no internet.

Settings tabs

Each tab edits one slice of the FigSyncProjectSettings ScriptableObject (lives at Assets/FigSync/FigSyncProjectSettings.asset; commit it). Changes apply on next sync.

🔍 Diff & Re-sync

Re-sync without losing the work you did between syncs.

After every successful sync FigSync writes a hash snapshot of the node tree to Generated/_figsync_snapshot.json. On the next sync, the pipeline diffs the incoming nodes against that snapshot before any scene mutation. The result is a per-root summary of what would change.

The diff window

When there's at least one change, the diff window opens before DrawHierarchy:

What "preserve" means at the GameObject level

Three cases survive a re-sync identically:

📝 Inspector edits

Material overrides, animation triggers, references wired in the inspector — persisted on the unchanged GameObjects.

📚 Added components

Anything you've added — controllers, behaviours, audio sources — stays attached to the same GameObject across syncs.

📋 Added children

Non-FigSync children dropped under a synced GameObject are rescued and re-parented to the freshly-drawn replacement if the parent's hash changed.

First sync, no snapshot

The diff window is skipped on first sync — nothing to compare against. The pipeline writes a snapshot at the end. Every sync from the second onwards benefits from the diff path.

📐 Auto-Layout

Figma's auto-layout maps to UGUI LayoutGroup + ContentSizeFitter end-to-end.

A Figma frame with auto-layout becomes a Unity GameObject with the matching HorizontalLayoutGroup, VerticalLayoutGroup, or GridLayoutGroup, plus a ContentSizeFitter when the sizing mode is "Hug contents". Children flow in the same direction, with the same padding, gap, and primary/counter alignment.

What's wired

What's not wired

See the dedicated Known Limitations for the specifics:

🔗 Constraints (Anchors)

Figma per-axis constraints drive UGUI anchors so layouts respond to Canvas resize.

Every Figma child carries a per-axis constraint (Min / Max / Center / Stretch / Scale). FigSync maps them onto the child's anchorMin / anchorMax + offsetMin / offsetMax so the child reacts the right way when its parent rect changes size.

Figma constraintHorizontal effectVertical effectUnity anchors
MinPinned to leftPinned to top(0,1) — (0,1)
MaxPinned to rightPinned to bottom(1,0) — (1,0)
CenterCentered horizontallyCentered vertically(0.5,0.5) — (0.5,0.5)
StretchResizes with parent (L/R)Resizes with parent (T/B)(0,*) — (1,*)
ScaleScales proportionallyScales proportionally(0,0) — (1,1)

Re-sizing the root Canvas at runtime should re-flow the design exactly the way it would in Figma's preview.

🌈 Gradients

Four gradient kinds, rendered procedurally in one SDF shader.

FigSync renders Figma gradients on the GPU instead of baking them to a texture. Four kinds are supported: GRADIENT_LINEAR, GRADIENT_RADIAL, GRADIENT_ANGULAR, and GRADIENT_DIAMOND — the full set Figma can author. Multi-stop ramps up to 4 stops render natively; 5+ stop ramps collapse to first + last + two middle stops (see Known Limitations).

How gradients render

The same FigSync/RoundedCorner SDF shader that handles rounded corners also handles gradients. The two are composed at the fragment level: gradient sampled first, alpha multiplied by the corner SDF, output to screen. One Image + one Material per node, no extra draw call.

What's preserved

◯ Rounded Corners

SDF rounded corners with per-corner radii, stroke band, drop shadow, inner shadow.

Nodes with a non-zero corner radius receive a FigSyncRoundedCorner runtime component (built on Unity's BaseMeshEffect). The component encodes the corner radii, stroke width, drop-shadow offset, and inner-shadow offset into the mesh's UV channels — one shader draws every effect against the SDF distance field, no extra GameObjects or quads.

What's encoded

Why an SDF shader instead of PNG raster

SDF stays crisp at any scale — rounded buttons look right at 0.5x and 4x zoom alike. PNG raster would force a fixed-resolution bake that aliases under scale. The SDF path also unifies stroke + corner + shadow into a single draw call, so a screen with 50 rounded panels is 50 draw calls, not 200.

☀ Shadows & BackgroundBlur

Drop shadow, inner shadow, layer blur, and Figma's signature glass effect.

Drop & inner shadow

Up to four stacked drop shadows and four stacked inner shadows per node, all rendered against the same SDF. No extra GameObjects, no overlay quads — just additional uniforms on the shared material. Stacking is preserved: Figma's first shadow is the topmost layer.

Layer blur

A Figma LayerBlur effect on a leaf node renders procedurally by widening the SDF edge. On a node with children, the procedural path can't blur the descendants — FigSync routes the whole subtree to the PNG raster path where Figma bakes the blur server-side, then displays the result as a Unity sprite.

Background blur (the glass effect)

A Figma BackgroundBlur blurs whatever sits behind the consumer node — the iOS-style frosted-glass look. FigSync ships a procedural backdrop:

⚠
Background blur requires a Camera-mode Canvas. Screen Space - Overlay canvases don't go through Unity's camera-render path, so the compositor has nothing to snapshot. Switch the canvas to Screen Space - Camera with a Render Camera assigned and the compositor picks up the change on its next tick. Scene view can show smeared sampling for the same reason; verify in Game view.

✏ Text & Fonts

TMP with automatic font download. Mixed inline styles. Locale-aware.

Text rendering

Every Figma TEXT node becomes a Unity TextMeshProUGUI. Size, alignment, line height, letter spacing, and colour map directly. Multi-line text wrap respects the bbox width; single-line bbox-height nodes get NoWrap so labels overflow horizontally instead of stacking.

Automatic font download

The DownloadFonts step resolves every font family used in the file through:

  1. Your Google Fonts API key, if you've set one in Settings → Auth (returns TTFs from gstatic.com directly).
  2. The public Google Fonts CSS endpoint as fallback.
  3. The Fontsource CDN for variable fonts Google ships only as WOFF2.
  4. The Adobe Edge font index for non-Google families.

Each resolved TTF is imported as a Unity Font and baked into a TMP _SDF.asset at Generated/Fonts/<Family>-<Weight>.asset. On subsequent syncs the baked asset is reused — no re-download.

Fonts that didn't resolve

For proprietary families (Whyte, Figma Hand, custom) or Google variable fonts that don't ship a TTF download path (Inter, Newsreader), the import surfaces a one-shot dialog at the end of the sync listing what to drop in. Save the TTFs to Assets/FigSync/Generated/Fonts/ using the convention <Family>-<weight>.ttf (e.g. Inter-400.ttf); the next sync auto-binds them to every matching text component.

Mixed inline styles

A single Figma text node can mix weights, italics, underlines, and strikethroughs per character. FigSync reads the per-segment styling and emits TMP rich-text tags (<b>, <i>, <u>, <s>) for the segments that diverge from the node-level base style. Cross-family inline segments keep their weight + italic but render in the base family asset (see Known Limitations for the TMP atlas reason).

🎨 Sprites & Atlases

Auto-classified, auto-deduped, auto-sliced.

Some nodes can't be drawn procedurally — complex vector icons, photo fills, baked illustrations. For those, FigSync pulls a PNG from Figma's /v1/images endpoint (or reads the pre-rendered PNG from the plugin bundle), saves it under Generated/<FolderName>/<NodeName>.png, and binds it as an Image.sprite.

How classification works

The ClassifyImages step decides per-node whether to route to the raster path:

Dedup

The DedupeSprites step hashes every freshly-written PNG; duplicates (same pixel content) collapse to a single asset on disk with the other nodes pointing at it via reference. Cuts asset count and binary size sharply on screens with repeated iconography.

Slicing

The SliceSprites step looks for the manual sidecar convention — a node named BG/9slice or with a tag suffix — and writes 9-slice border data into the sprite asset's border field so UGUI Image rendering with type Sliced reuses the same texture across multiple sizes.

☀️ FigJam Support

Stickies, connectors, shape-with-text, and the signature dotted canvas.

FigJam files are first-class. The pipeline auto-detects FigJam at the editor level (the editorType field in meta.json, with a node-type heuristic fallback for older exports) and switches on FigJam-specific rendering paths.

What's rendered natively

The dotted canvas backdrop

FigJam's signature dotted-grid canvas isn't a Figma node — it's painted by FigJam itself behind whatever's on the page. Frames that look "white on left, dotted on right" actually have a transparent right half with the canvas dots showing through. FigSync detects FigJam files and injects a _FigJamCanvasDots Image as the lowest sibling under each page. The tile is 24×24 px, opaque, with a soft grey dot — matches FigJam's rendered look in side-by-side comparisons.

Inline icons over text

FigJam tutorial frames often place an icon (.key / ctrl / esc) over a run of whitespace inside a text node ("Press           and drag"). Figma renders the space narrow enough to fit the icon; TMP renders it wider and forces an early wrap. FigSync rewrites runs of 3+ consecutive spaces as <space=Npx> TMP tags so the line wraps where Figma intends it to.

🔧 Settings

One ScriptableObject. Safe to commit.

Settings live as a ScriptableObject at Assets/FigSync/FigSyncProjectSettings.asset. Edit through the Hub window's tab views, or select the asset and use the Inspector.

FieldDefaultDescription
Output FolderAssets/FigSync/GeneratedWhere sprites, fonts, snapshots, and reports land. Must be inside Assets/.
BackendUGUITarget framework. UGUI is the shipping backend.
Force RefreshfalseSkips the 24h REST cache for the next sync. Use when Figma was edited very recently and you don't want a stale node tree.
Bridge Port8473Local HTTP port the Figma plugin pushes to. Change only on collision; tell the plugin to match.
Render ModeScreen Space - CameraDefault Canvas render mode for new imports. Switch to World Space for VR/AR or 3D-anchored UI.
Debug VerbosityInfoConsole log level — Silent / Errors / Warnings / Info / Verbose. Verbose prints per-node decisions.

Tokens (Personal Access Token, OAuth refresh, Google Fonts API key) are stored separately in EditorPrefs with base64-XOR obfuscation. They never write to assets or version control.

⚠ Known Limitations

What FigSync doesn't fully render, why, and how to work around it.

Gradients — capped at 4 stops

The SDF shader carries a 4-stop ramp uniform. Figma ramps with 5+ stops are flattened to first + last + two middle stops by FigmaJsonDeserialiser.AppendStop. Rare in practice.

Gradients — angular 2-stop seam

frac() produces a hard edge at the axis direction for 2-stop angular ramps. Designers can hide this by adding a 3rd wrap-around stop whose colour matches the first stop at position 1.0.

Auto-layout — Wrap mode not implemented

Figma's HORIZONTAL, VERTICAL, and GRID auto-layout modes are wired end-to-end. WRAP (a horizontal layout that wraps to a new row when it overflows) falls through to free-position layout. Designs using wrap render with baked positions and won't reflow.

Auto-layout — Grid is uniform cells only

UGUI's GridLayoutGroup requires one cellSize for every cell. Figma's auto-grid model supports variable column widths and row heights ("track sizing"); FigSync flattens that to equal cells derived from frame size minus padding/gaps. Visually correct when the designer used "fill" track sizing across the board; non-uniform tracks lose their per-cell sizes.

Auto-layout — SPACE_BETWEEN uses injected spacers

UGUI's LayoutGroup has no native SPACE_BETWEEN, so FigSync inserts invisible LayoutElement GameObjects (marked FigSyncLayoutSpacer) between every adjacent pair of children. They absorb the leftover slack equally — visually the same as Figma's SPACE_BETWEEN. Side effect: N-1 extra GameObjects per SPACE_BETWEEN frame.

Auto-layout — constraints on flow children are ignored

When a child sits inside an auto-layout FRAME and isn't marked ABSOLUTE-positioned, its Figma constraints (Stretch / Center / Pin) don't drive its anchors — the parent's LayoutGroup owns placement. The child still resizes correctly when the LayoutGroup re-flows; it just can't independently stretch to parent edges the way a free-position child can. Designers who need that behaviour should toggle the child to "absolute position" in Figma.

Gradients — runtime Image.color.a tween ignored

Gradient sampling uses per-stop alpha as the output alpha, so per-stop fades work, but a runtime Image.color.a tween on the Graphic itself doesn't dim the gradient. Designers who want a runtime fade should add a transparent stop to the ramp (or animate a CanvasGroup parent).

Effects — LayerBlur on a frame with children falls back to PNG

LayerBlur on a leaf node renders procedurally via SDF-edge widening. On a node with children, the procedural path can't blur the children, so the classifier routes the whole subtree to the Downloadable PNG path where Figma bakes the blur server-side. Slightly heavier on import, indistinguishable at runtime.

Mixed inline text — segment can't override base style downward

TMP's rich-text grammar has <b> / <i> / <u> / <s> to turn flags on, but no inline "force off" counterpart. If the node-level base style is bold and a segment is regular, the regular segment still renders bold. Pick the lightest weight as the base style and let segments add emphasis.

Mixed inline text — cross-family segments stay on the base family

TMP's <font="X"> tag only resolves under Resources/Fonts & Materials/. FigSync writes baked atlases under the per-import output folder. The segment's weight + italic carry through (synth-bold / synth-italic from the base font); only the family doesn't switch. DownloadFonts still pulls the segment-specific TTF + .asset, so a future TMP_StyleSheet integration could close this gap.

BackgroundBlur — Scene view shows smeared sample

The compositor's RenderTexture is filled by the Canvas's Render Camera. In Game view the rendering camera and the source camera match, so UVs line up. In Scene view the editor camera is rendering the consumer but the RT was filled by the source camera — UVs land on unrelated pixels and "smear" as the editor camera moves. Verify the look in Game view, not Scene view.

BackgroundBlur — Overlay canvas not supported

Screen Space - Overlay bypasses Unity's camera-render pipeline, so there's no Camera.Render hook to snapshot canvas content. Switch the canvas to Screen Space - Camera and assign a Render Camera. The compositor picks up the change on its next tick.

Strokes — image-fill strokes don't render

The procedural path renders solid + gradient strokes. Image-fill strokes are not represented in the SDF shader and silently disappear. Rare; designers typically use solid colours for strokes.

Effects — inner shadow caps at 4 layers

Figma allows arbitrary stacked inner shadows; FigSync renders the first 4. Real designs with 5+ stacked inner shadows are essentially nonexistent.

Render pipelines — HDRP untested

Shaders are written in Built-in HLSL and ship URP-tested. HDRP variants haven't been validated end-to-end. The BackgroundBlur compositor specifically needs URP's render-graph to accept the depth-buffered RT.

This is the single source of truth for FigSync's known trade-offs. If you hit something not listed here, mail ragendom@gmail.com with a minimal repro.

💡 Troubleshooting

Common failure modes, what they mean, how to recover.

Sync errors

403 Forbidden from FigmaAction required

Your Personal Access Token doesn't have access to the file, or the token was revoked.

What to do: In Figma, confirm you have at least view permission on the file. Generate a fresh token in figma.com/settings and paste it into Settings → Auth.

429 Rate LimitedAuto-retry

Too many Figma API requests in a short window.

What to do: The pipeline waits the cooldown Figma returns in the Retry-After header and resumes automatically. If it keeps tripping, wait for the cooldown to clear, or use the Figma plugin path, which exports directly from the file you're working on (no token or URL needed).

Bridge: port already in useAction required

Another process is holding port 8473 (or your configured Bridge Port).

What to do: Either close the offending process, or change Bridge Port in Settings → Auth and re-import the plugin manifest in Figma so they agree.

TMP Essential Resources missingAction required

The Text rendering relies on TMP's default fallback assets, which aren't in the project yet.

What to do: Run Window → TextMeshPro → Import TMP Essential Resources, accept the import dialog, re-sync.

Common issues

The whole canvas is white / nothing renders

The most common cause is that the Canvas's Render Camera was deleted between syncs. Check FigSync Canvas in the scene — its Canvas component should have Render Mode = Screen Space - Camera with a non-null Render Camera. Re-assign and the design appears.

Fonts are wrong / Inter when I asked for Roboto

Most often: the font wasn't on Google Fonts, fontsource, or any of the public CDNs. The diagnostic dialog at the end of the sync lists exactly which families didn't resolve. Drop the .ttf files into Assets/FigSync/Generated/Fonts/ as <Family>-<weight>.ttf and re-sync.

An icon appears blurry / pixelated

The node was routed to the PNG raster path and rendered at a smaller-than-display resolution. Try resizing the source frame in Figma (Figma's /v1/images renders at the frame's absoluteRenderBounds), or convert the node to a Vector if Figma can express it that way — vector nodes mesh procedurally and stay crisp at any scale.

A shadow / corner radius is missing on one node

The node was probably classified Downloadable (raster path), in which case Figma's PNG already contains the shadow/corner baked in. If the PNG is missing the effect, check the effect's Visible toggle in Figma — FigSync respects it. For procedural-path nodes (solid colour frames), inspect the GameObject's FigSyncRoundedCorner and confirm the radii are non-zero.

Re-sync overwrote my edits

FigSync preserves edits on nodes whose hash didn't change. If a node's content changed in Figma, its hash changes, and the GameObject is rebuilt — that's the intended behaviour. To prevent that for a specific frame, untick it in the Diff Window before clicking Continue; the frame stays as-is until you re-include it.

Sync hangs at DownloadFonts

Google Fonts or fontsource is unreachable, or your network is filtering them. Sync can complete without fonts — the import surfaces a Missing fonts dialog and you can drop TTFs in manually. If you're behind a corporate proxy, set the Google Fonts API key in Settings → Auth to use the Webfonts Developer API instead of the public CSS endpoint.

Enable verbose logging when in doubt

Set Debug Verbosity = Verbose in Settings → Debug and re-sync. Every step logs its inputs, decisions, and counts to the Console with the [FigSync] prefix. The diagnostic report at Generated/_figsync_report.json also captures the full per-node decision trace.

❓ FAQ

Quick answers to the questions that come up most.

Does FigSync work with FigJam?

Yes, through the Figma plugin path. FigJam-only node types (Sticky, ShapeWithText, Connector) aren't exposed by Figma's public REST API, so the plugin is required for FigJam files. Plain Figma Design files work over both paths.

Does it work offline?

Yes, after the first sync. Each plugin push saves a .figsync.zip bundle under Library/FigSync/Bridge/; you can re-sync from that bundle without Figma open. The REST path requires internet to fetch the latest node tree (a 24-hour cache softens that).

Can I use the synced UI in builds (mobile / WebGL / console)?

Yes. The output is a plain Unity UGUI Canvas — sprites are .png assets, fonts are TMP _SDF.asset, layout is LayoutGroup + ContentSizeFitter. Nothing in the runtime is editor-only. FigSync's own runtime components (rounded corner, drop shadow, blur compositor) are plain MonoBehaviours that work in any build target.

Does it support HDRP?

The UGUI canvas itself works on any pipeline. The shipping shaders target Built-in / URP and haven't been end-to-end-tested on HDRP. BackgroundBlur specifically needs URP's render-graph; on Built-in it works without the depth buffer. We'd love HDRP reports if you hit issues.

What about other UI frameworks (UI Toolkit etc.)?

UGUI is the shipping backend in v1.0. Other UI frameworks (UI Toolkit and similar) aren't supported today.

What happens to my scene if a sync fails halfway?

Steps before Draw are read-only — if they fail, the scene is untouched. Once Draw starts, the prior snapshot is loaded; if a step fails mid-Draw, the scene shows whatever was drawn up to that point, with a HelpBox naming the failing step. The hash snapshot is only written on full success, so the next sync diffs against the last known-good state — mid-failure runs don't poison the diff baseline.

Can two designers work on the same file?

Yes — both sync the same URL. Each sync writes deterministically (same node id → same GameObject name → same file path), so a teammate pulling your branch and re-syncing produces the same scene. Commit the Generated/ folder if you want the sprites + fonts to ship with the repo; or treat it as a build artifact and re-sync on first checkout.

How big is the runtime overhead?

The synced Canvas is a plain UGUI Canvas. FigSyncRoundedCorner is a BaseMeshEffect that adds <0.1 ms per canvas rebuild for typical screens. FigSyncBackgroundBlurCompositor samples one RenderTexture per consumer per frame — cheap on desktop, noticeable on mobile if you have 10+ blurred panels visible at once. Drop shadows are mesh-vertex effects, not extra draw calls.

How do I roll back a bad sync?

Ctrl-Z. Every sync wraps its scene mutations in a single Undo group, so one undo reverts the entire sync. The Generated/ folder is also versionable — if you commit it, git revert rolls back the sprite/font side too.

My team has multiple developers. Do we each need a license?

Yes. Per Unity Asset Store policy, each seat that uses the asset needs its own license.

📬 Support

Need help? Reach out.

📧

Get in Touch

Bug reports, feature requests, licensing questions — reach out and we'll get back to you.

✉ ragendom@gmail.com

We typically respond within 24–48 hours.

💬 Before contacting
  • Check the Troubleshooting section
  • Skim Known Limitations
  • Set Debug Verbosity = Verbose and re-sync to capture context
  • Open Generated/_figsync_report.json — the failure is usually visible there
📋 Helpful info to include
  • Unity version (e.g. 2022.3.30f1)
  • FigSync version (v1.0.0)
  • Render pipeline (Built-in / URP / HDRP)
  • Auth path (Token / OAuth / Plugin)
  • Steps to reproduce + Console output
ⓘ
Refunds within 14 days are handled by Unity directly via the Asset Store FAQ.
⭐

Enjoying FigSync?

A short Asset Store review helps other developers discover FigSync and keeps the updates flowing. It only takes 30 seconds.

⭐ Leave a Review on the Asset Store

u3d.as/1oGH

FigSync v1.0.0 · Built for Unity 2021.3+

Support: ragendom@gmail.com · Asset Store: u3d.as/1oGH