Instrument x Seth Akkerman
Projects Float Availability FigJam Plugin

Float Availability FigJam Plugin

A FigJam plugin that syncs live capacity data from Float directly onto a team availability board — recoloring each person's card by discipline and appending their open hours for the next four weeks. One click replaces what would otherwise be a manual update across 60+ cards.

Team availability boards are one of those things that’s always slightly out of date. Someone updates Float, but the FigJam board stays stale — and nobody has time to manually recolor 65 cards. This plugin closes that gap: one button pull from Float, cards recolored by discipline, availability summary appended under each name.

How it works

FigJam plugin iframes run with null origin, which means Float’s API — which doesn’t send CORS headers — is completely unreachable from the client. The solution is a thin Netlify proxy that sits between the plugin and Float: the plugin posts a list of names and a date window, the function calls Float server-side, runs the capacity math, and returns clean JSON. The Float bearer token never touches the client. A shared secret in x-plugin-secret guards the endpoint.

On the canvas side, the plugin has to navigate a messier-than-expected board structure. The team availability board uses two card formats: ~65 “loose composite” cards where a SHAPE_WITH_TEXT, a photo RECTANGLE, and a single combined TEXT node (name + title separated by \n) are just siblings overlapping at the same position — not grouped — plus one actual Figma GROUP card. The detector handles both, using spatial overlap math to match text nodes to their parent shape, then caching the original name and title via setPluginData so repeated syncs don’t compound availability lines into the stored baseline.

The build

Detection bugs dominated the early development: cards returning 0 matches (the shape’s own .text.characters was empty, causing a premature rejection), then 1/66 matches (name and title aren’t two separate text nodes — they’re one node with a \n inside), then names getting silently wiped on apply (the combined node was being overwritten starting from the title line, dropping the name). Each fix is documented and locked in the context file so they don’t get re-introduced. Name matching on the Float side uses normalized first+last token matching, with a known edge case for single-word names and initials that gets resolved by correcting the on-canvas card text to match Float exactly — a manual fix, not a code change.

Why it matters

The pattern here — FigJam as a live ops surface, not just a static artifact — is the interesting thing. Float is the source of truth for capacity; FigJam is where producers actually plan work. Keeping those two in sync manually is friction that compounds every week. A plugin that bridges them means the board can be trusted again, which changes how it gets used. The same proxy architecture works for any internal API that lacks CORS support, and the card-detection approach generalizes to any FigJam board that uses photo-plus-label card patterns.

Applying this to your project

  1. Copy, don’t fork whole: take Figjam Resourcing/ (the plugin — code.ts, ui.html, manifest.json, tsconfig.json, package.json) and instrument-figjam-capacity-api/ (the proxy — netlify/functions/_capacity.js, figjam-capacity.js, health.js, netlify.toml). Discard final/ (the raw screen-recording video), both node_modules/ folders, the .netlify/ local state folder, and PROJECT_CONTEXT.md/pipeline.log — those last two are portfolio-pipeline artifacts, not part of the tool itself.
  2. Set up accounts/keys: this repo has no .env.example — the three secrets (FLOAT_API_KEY, FLOAT_UA, FIGJAM_PLUGIN_SECRET) are set directly as Netlify site environment variables. Generate the plugin secret with openssl rand -hex 24. You’ll need a Netlify account (for hosting the proxy) and an account with whatever data source you’re pulling from (Float, or its replacement).
  3. Swap for your own branding/data: the ENDPOINT and SECRET constants hardcoded near the top of ui.html’s <script> block (around line 299) must be regenerated — don’t reuse the committed values. The KNOWN_ROW_LABELS/row-color maps in code.ts and the matching ROW_COLORS object in ui.html are your board’s category labels and hex colors — replace both with your own team/discipline structure. Update manifest.json’s networkAccess.allowedDomains to point at your new Netlify domain.
  4. Where your data plugs in: findGreenPill, findRowLabels, and looseShapeToCard in code.ts are the “reader” that scans the FigJam canvas — this is the part you likely keep as-is. _capacity.js (specifically matchPerson/normalizeName/capacityForNames) is where an external data source’s records get matched to the names on the board — this is the part you replace if you’re pulling from something other than Float. The contract between plugin and proxy (POST { startDate, days, names } in, { people, unmatched } out) is the seam to preserve so you only have to rewrite one side.
  5. Gotchas from reading the code:
    • FigJam’s plugin sandbox is QuickJS-based and can’t parse ??/?. — you must bundle with esbuild targeting es2017 (or avoid syntax it can’t downlevel).
    • There’s no npm build script — after every code.ts edit you must manually run npx esbuild code.ts --bundle --outfile=code.js --target=es2017. Figma auto-reloads code.js from disk on next run, but it will not rebuild it for you.
    • The card-detection logic assumes a very specific board convention (loose shape + one overlapping text node split on \n, vs. a rare grouped 2-text-node card). A different board’s actual structure needs to be re-verified with the recon.js diagnostic plugin before assuming this detection code will just work.
    • The plugin secret and endpoint URL are hardcoded in plaintext directly inside ui.html, a client-visible file — fine for a private internal team tool, but don’t copy this pattern into anything with a wider audience.
    • The proxy’s pagination loop exists because the external API silently caps per-page at 200 and would otherwise return a fraction of the real data — preserve an equivalent “walk pages until short page” loop for any similarly paginated data source.

Other ways to repurpose this

Product Design

Web Design

Marketing

Branding

Dependencies

typescript 5.9.3 Type-checks code.ts against the Figma plugin API types before esbuild bundles it to code.js.
@figma/plugin-typings 1.125.0 Supplies TypeScript type definitions for the figma.* Plugin API used throughout code.ts (node types, SHAPE_WITH_TEXT, setPluginData, etc.).
esbuild ^0.28.0 Bundles code.ts into a single ES2017 code.js file the FigJam plugin's QuickJS sandbox can load (QuickJS can't parse raw ?? / ?. without downleveling).
netlify-cli 17.38.1 Runs the Float-capacity proxy locally (netlify dev) and deploys it (netlify deploy --prod) as the CORS-clean Netlify Functions site the plugin calls.
Role
Design Engineer
Tools
FigJam Plugin API TypeScript Netlify Functions Float API ESBuild
Metrics
Automates availability updates across 65+ team member cards in a single sync.
Video thumbnail for Float Availability FigJam Plugin