Git

Integration

Git integration in SXL Studio: syncing tokens and data with GitHub and GitLab, Push, Pull, branches, and connection setup.

Why Git integration

Git integration turns SXL Studio into a two-way bridge between Figma and the code repository. Designers update tokens in the plugin → Push → developers receive changes. Developers update JSON → designer Pulls → designs update automatically.

What gets synchronized

DataRepository path
Data (Database)dataPath/ — JSON, CSV files, mappings, images
TokenstokensPath/ — token JSON files, config.json, diff-id, code-connect.json

Dev Mode can also use a Code folder for the .vue repository editor. That source-code folder is separate from data/token synchronization.

Supported providers

  • GitHub — github.com and GitHub Enterprise Server (GHES)
  • GitLab — gitlab.com and self-hosted instances

Git integration works through the provider's REST API (not git CLI). Connection uses a Personal Access Token.

Sync diagram: Figma ↔ SXL Studio ↔ Git

  1. Install and run Bridge @sxl-studio/bridge ≥ 1.7.0 on your machine — see Bridge utility. Smoke test: curl http://127.0.0.1:37830/api/status should return JSON with ports (use your port if you changed it).
  2. Create a Personal Access Token on your provider with repo access — step-by-step: GitHub, GitLab.
  3. In Figma open SXL StudioSyncNew Sync. Fill repository, branch, Data Path, Tokens Path, and paste the PAT. To stay under Figma clientStorage limits (~5 MB), enable Local Storage (Bridge must be running) → Save.
  4. Click Pull and wait until it finishes. Then use Push, branches, and indicators as described below.

Git Sync works without Bridge; Local Storage is for large workspaces that approach the plugin storage quota.


Local Mode vs Git Sync vs Local Storage

These are different concepts:

  • Local Mode — selected in Sync Mode when no Git connection is active. You work only with local plugin data in this Figma file.
  • Git Sync mode — an active GitHub/GitLab connection is selected. Pull/Push, branch operations, and Git status are enabled.
  • Local Storage — per-connection toggle that stores large synced data on disk through Bridge to avoid Figma clientStorage limits.

Use Local Storage for medium and large workspaces. It reduces quota pressure, improves stability for large token/data sets, and helps avoid data loss from storage overflow.


Setting up a connection

Step 1 — Create a connection

  1. Open the Sync panel in the plugin (icon in the bottom bar)
  2. Click New Sync
  3. Fill in the fields:
FieldDescriptionExample
ProviderGitHub or GitLabgithub
NameConnection name (for you)Design System
RepositoryRepository pathorg/design-system
BranchWorking branchmain
Access TokenPersonal Access Tokenghp_xxxx...
Data PathData folder in the repodata
Tokens PathTokens folder in the repotokens
Code folderDev Mode source folder for the code editor; does not sync Database/Tokenssrc/components
Local StorageDisk-backed cache via Bridge (recommended for large workspaces)On
Enterprise URLURL for self-hosted (optional)https://git.company.com
  1. Click Save

Older saved connections are migrated automatically from legacy field names into the current Access Token / Repository shape. If a migrated connection looks incomplete, open it once, verify the fields, and save.

In Figma Dev Mode the connection form focuses on Code folder. A code-only connection is valid for the Dev Code tab, but Pull/Status can return "nothing to pull" because Database and Tokens are managed from Design Mode sync settings.

Git connection creation form

Step 2 — First Pull

After creating the connection, perform a Pull — the plugin will download all files from the specified folders.


Push — sending changes

What happens during Push

  1. The plugin collects all local changes:
    • Data (datasets, mappings, images)
    • Tokens (JSON files, config.json, diff-id, code-connect.json, diff-grid.*)
  2. Compares with the remote state (by SHA)
  3. Forms a single commit with changes
  4. Sends to the selected branch

How to Push

  1. Click the Push button in the bottom bar (or via the Git menu)
  2. The Commit Modal opens with an embedded inline side-by-side diff:
    • Left — list of changed files with status icons (+ / ~ / ×) and counts
    • Right — Monaco DiffEditor: Remote on the left, Local on the right. Click files in the list to switch views. Binary assets are marked in the list and skipped by the diff engine
    • Hunk navigation: F7 — next change, Shift+F7 — previous (or the arrow buttons in the top-right corner of the editor)
    • Below — the commit message field with provider and branch hints
  3. Enter a commit description
  4. Click Push

Commit message behavior

  • A commit message is required (cannot be empty).
  • SXL Studio does not enforce a specific format by default.
  • Some repositories (especially GitLab) enforce server-side commit policies (for example Conventional Commits). If the server rejects the message, Push fails and the plugin shows an error.

All text files are normalized to a trailing newline (POSIX convention). This eliminates "phantom" diffs on blank last lines when there are no semantic changes. Files that turn out identical after normalization are marked in the list with and open with an explanation instead of the DiffEditor — so the Push list stays honest.

If there's nothing to send, the plugin shows a notification. If there are tokens but no Tokens Path is set, it shows an error with guidance.

Scope changed

If since the last Pull you changed the connection's repository, branch, dataPath, or tokensPath, the Commit Modal shows a ⚠ Scope changed badge. Push is not blocked by an extra acknowledgement checkbox—read the warning and decide; usually you should Pull into the new scope first so you don't push with stale context against a different branch.

Pushing changes to the repository

Pull — receiving changes

Soft Pull vs Hard Pull

TypeWhenWhat it does
Soft PullRepeat pull in the same scope (branch, repo, paths unchanged)Downloads only changed files. Local data is preserved
Hard PullFirst pull, branch/repo/path change, or explicit requestClears all local data and downloads fresh

How to Pull

  1. Click the Pull button in the bottom bar
  2. The Pull Modal opens with an embedded inline side-by-side diff:
    • Left — list of incoming files with counts and statuses
    • Right — Monaco DiffEditor: Local on the left (current state), Remote on the right (what will arrive). For new files the left side is empty — that's exactly what "added" looks like
    • Navigation: F7 / Shift+F7 between changes, arrow buttons in the top-right corner
  3. Below you can see the last remote-branch commit (author, message, time)
  4. Click Pull

Hard Pull

When you need a full resync:

  • Use Hard Pull from the menu
  • All local data and tokens will be replaced with repository contents

Progress

Pull shows progress through phases:

  • listing — scanning the file tree
  • data — downloading data
  • tokens — downloading tokens
  • finalizing — finalizing
  • done — complete

Speed with many files

Data and token files are downloaded in parallel (a bounded number of concurrent Git requests) to speed up Pull on large trees without hammering the provider. For huge repos, tree listing and network latency may still dominate; narrow dataPath / tokensPath in the connection settings if needed.

With Local Storage enabled, each downloaded token file is also written to disk via HTTP to localhost — typically faster than provider round-trips, so tree listing and Git downloads still dominate total time. Run Bridge on the same machine as the browser running Figma.

Auto-export after Pull

If autoExportOnPull: true is set in config.json, token export to Figma Variables runs automatically after Pull.

Pulling changes from the repository

Branches

Switching branches

  1. Click the current branch name in the bottom bar
  2. A menu shows available branches
  3. Select the desired branch
  4. The plugin updates the connection setting
  5. Perform Pull to synchronize

Switching branches does not download files automatically — you need to Pull. The first Pull after switching is a Hard Pull.

Creating a branch

You can create branches in two ways:

  1. Create Branch — creates from the current connection branch.
  2. Create Branch From... — pick any source branch, then enter a new branch name.

After creation, SXL Studio automatically switches the active connection to the new branch and refreshes branch status.

Branch creation does not download files automatically. Run Pull immediately after creating/switching a branch.

Creating and switching branches

Status indicators

The plugin's bottom bar shows:

IndicatorMeaning
Provider icon + branchCurrent active connection
🟢 Green dot on PullChanges available on remote
🟢 Green dot on PushLocal changes pending
SpinnerOperation in progress (pull/push/refresh)
Progress counterPull progress (downloaded / total)
⚠ Truncated tree bannerGitHub returned a partial tree (repo too large) — Push/Pull are blocked until dataPath / tokensPath is narrowed
⚠ Hard pull required (in the Pull modal)repo / branch / dataPath / tokensPath changed — the next Pull will wipe local data

Status auto-refresh

  • Background polling — every 60 seconds, but it's not a rigid setInterval: after any refresh (background, manual, or from local events) the next tick is rescheduled. This removes duplicates like "I pressed Refresh → the plugin fires another request 5 seconds later"
  • Conditional GET. GitHub uses ETag / If-None-Match → a 304 Not Modified response doesn't consume the primary rate limit. GitLab checks the branch head commit SHA in one lightweight request → if the SHA hasn't changed, the client reuses the cached tree without paginating through files. Idle cost is close to zero
  • Instant refresh on focus. When the plugin window becomes visible again (visibilitychange → visible / focus), the plugin triggers check-git-status with a 10-second throttle — this makes the Pull indicator react to external pushes without noisy round-trips
  • Any manual Refresh, as well as background polling and visibility triggers, bypass the tree TTL cache while keeping the conditional check (ETag / head-SHA). This means a fresh branch commit is visible to the plugin on the next tick — rather than after two missed ticks as it used to behave
  • Local operations (save, export, push, pull) trigger triggerGitStatusRefresh() in-process — no UI roundtrips
  • The GitClient is cached for 60 seconds by (connection, repo, branch, paths, token-signature), so a single Push or Pull doesn't re-fetch the tree multiple times
  • Each refresh publishes the fresh tree snapshot into the UI store immediately — the inline diff in Push/Pull modals opens without extra loading and reflects current changes, even if you never opened the Git Browser

Local storage (Bridge)

Figma clientStorage is limited to roughly 5 MB per plugin. With large token/data projects this limit can be reached quickly.

Local Storage (per-connection toggle in the plugin Sync form) stores large synced data on disk via SXL Studio Bridge:

  1. Run Bridge (@sxl-studio/bridge 1.7.0+; the Cache folder field and Open button require 1.8.0+).
  2. Enable Local Storage in the connection form.
  3. If Bridge is unavailable when Pull starts, Pull stops before changing local data and asks you to start Bridge first.
  4. When Bridge is running again, retry Pull (or temporarily disable Local Storage for that connection).

Security note: data is stored only on the machine where Bridge runs.

Default cache locations

  • macOS: ~/Library/Caches/sxl-studio-bridge/workspace-blobs
  • Windows: %LOCALAPPDATA%\sxl-studio-bridge\Cache\workspace-blobs

If you set SXL_BRIDGE_WORKSPACE_BLOB_ROOT, Bridge uses that custom directory.

Cache folder field and Open button

Starting with Bridge 1.8.0, when Local Storage is enabled for a saved connection, the Sync form shows a Cache folder field:

  • it displays the exact path to this connection's local cache on your machine;
  • the Open button opens that folder in the OS file manager (Finder on macOS, Explorer on Windows).

Each connection has its own cache subfolder (isolated by repo/branch), so multiple connections never overlap. This is handy for checking where the data physically lives, or for clearing the cache manually if needed. If Bridge is not running or is older than 1.8.0, the field shows a hint to start Bridge and the Open button is disabled.

Optional BRIDGE_AUTH_TOKEN protects Bridge endpoints when your environment requires it. Generate a long random local secret, start Bridge with it, and put the same value into the connection form field Bridge Auth Token so the plugin can read/write /api/status and /api/workspace-blob.

BASH
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
BRIDGE_AUTH_TOKEN=<generated_secret> sxl-bridge

This field is not a GitHub/GitLab access token and not a Figma REST token. Leave it empty when Bridge runs without BRIDGE_AUTH_TOKEN.

Local Storage itself works via Bridge availability and does not require a separate Remote Connect websocket session.

Before a Local Storage Pull, the plugin probes GET /api/status and then reads/writes large payloads through /api/workspace-blob. If BRIDGE_AUTH_TOKEN is enabled, both requests use the Bridge Auth Token from the connection form.

More Bridge detail: Bridge utility.


Performance and resilience

  • Truncated tree protection. For very large monorepos, the GitHub API may return a partial tree (truncated flag). In that case SXL Studio cannot guarantee a complete list of deletions, so Push and Pull are disabled and a warning banner is shown in the bottom bar. Fix: narrow dataPath / tokensPath to the actual design-system folders
  • Trailing newline normalization. All text files (JSON, CSV, Vue, Code Connect) are normalized to a single trailing \n before push. This removes "phantom" diffs that appear when different editors add or strip the final blank line
  • Real deletions only. A file is marked as "deleted" in the Push modal only if it actually exists on remote. This prevents false diffs after local renames/cleanups
  • Full diff for configs. The inline diff covers not only tokens and datasets, but also code-connect.json, diff-id.*, diff-grid.*, config.json — so every change that goes into the commit is visible
  • Size guard for huge files. Files larger than 1 MB or over 10 000 lines aren't loaded into the inline DiffEditor — an "Inline diff disabled" placeholder is shown instead. The file still ends up in the commit; it's just easier to review in an IDE or the provider's UI. This keeps the plugin iframe from freezing on heavy JSON
  • Noop filter. If the local and remote versions are byte-identical after normalization, the file is still committed (its Git blob SHA differs), but no DiffEditor opens for it — a short placeholder is shown instead. Such files are flagged in the list with a marker

Conflicts

SXL Studio does not use git merge. Synchronization works via SHA blob API:

  • During Pull — local files are overwritten with remote contents
  • During Push — if the remote already has a newer commit, the API may return an error

Recommendation: use a standard workflow — one person Pushes, others Pull. For parallel work, use separate branches.