How to get analytics and observe a Figma plugin in production.
Who uses a Figma plugin, which features they touch, how long the slow part takes and how often it fails: what the sandbox rules out, what to send, and how to read it on one dashboard.
, by Ilia
A plugin goes out to Community, the install count starts moving, and a week later the questions arrive that the count cannot answer. How many people ran it this week, not since launch? Which of the five things it does do they actually use? Do they come back on Monday? How long does the slow part take on a real file, and how often does it fail? The first three are analytics. The last two are what backend people call observability. In a plugin they come from the same place, so this is how I get both, and what I gave up on along the way.
What the Community page gives you
Lifetime users, likes and comments. Those numbers are about reach: they grow when the plugin is found and never go down when it is abandoned. A plugin that a thousand people opened once in March and nobody has touched since looks exactly as healthy as one a thousand people open every morning. Keep watching the page, it tells you whether the listing works. For anything about use you need events from inside the plugin. The longer version of this argument is in installs are not usage.
Why the usual tools stumble
The first thing most of us try is whatever the last web project used. It fails in a way that looks like a bug and is not one. The plugin UI is an iframe that Figma loads from a data: URL, so its origin is null. A null origin has no cookies, no localStorage and no IndexedDB, and touching any of them throws. The main code, code.ts, has no DOM at all. Every request either half makes has to go to a host named in the manifest.
Browser analytics remembers a visitor with a cookie and names what they look at by URL. A plugin has neither. What it has instead is better: figma.currentUser.id, the same id on every machine the person uses Figma on. The whole question of plugin analytics is which tool lets you hand it that id and get honest user counts back.
What I tried before
Google Analytics. The gtag script dies on the cookie. The Measurement Protocol works from the main code, but then you are writing the client: a client id, a session id, engagement time, and an API secret that ships inside your bundle. The reports still think in pages. The details are in the note on Google Analytics.
Mixpanel. Runs in the iframe once persistence is off and you call identify with the Figma user id. Funnels and cohorts are genuinely good, and if your plugin has a five-step flow with a payment at the end, this is the tool. For a utility that does one job well, I spent more time building reports than reading them, and session length was mine to define. Mixpanel compared.
A table of my own. A fetch from code.ts to a small server, one row per event. Collecting took an evening. The server has to answer with Access-Control-Allow-Origin: *, because the request comes from a null origin. Then came everything else: the query for returning users, cohorts by first week, percentiles, a page to look at them on, and keeping all of it running. That is the part I ended up turning into tode, so read the rest knowing I make it.
Decide what to watch before you install anything
The tool matters less than the list. For a plugin I watch five things, and each one maps to something the plugin can send:
- Who uses it and whether they return: the Figma user id on every event.
- Which features earn their place: one named action per feature, sent when the feature finishes, not when the button is pressed.
- How long people stay: the time the UI is open.
- How hard the plugin works: a number attached to an action, such as how many layers it touched or how many seconds it took.
- How often it fails: a failure is an action too, with its own name.
Naming is where most setups rot. replace_fonts today and replaceFonts after a refactor are two features as far as any dashboard can tell. Pick snake_case, verbs for actions, nouns with a unit for numbers, and write the list down. More in how to track usage.
Wiring it up
The manifest first, because without it nothing else matters. The currentuser permission lets the SDK read the user id. The allowed domains let requests leave the plugin: events go over HTTPS, sessions over a WebSocket, and Figma treats the two schemes as separate entries.
{
"permissions": ["currentuser"],
"networkAccess": {
"allowedDomains": [
"https://processing.usetode.dev",
"wss://processing.usetode.dev"
],
"reasoning": "Usage analytics (tode). No document content is sent."
}
}Then the controller. init once, and a call wherever something finishes. Here is a font replacer that times its own work and reports failures:
// code.ts
import { tode } from '@tode-sdk/core';
figma.showUI(__html__, { width: 400, height: 560 });
await tode.init({ apiKey: 'tode_...' });
figma.ui.onmessage = async (msg) => {
if (msg.type !== 'replace') return;
const started = Date.now();
try {
const replaced = await replaceFonts(figma.currentPage.selection, msg.from, msg.to);
tode.trackActionWithMetric('fonts_replaced', replaced.length);
tode.trackActionWithMetric('replace_seconds', (Date.now() - started) / 1000);
} catch (err) {
tode.trackAction('replace_failed');
figma.notify('Could not replace fonts in this selection.');
}
};There is no call for sessions. When the UI opens, the SDK opens a socket; when the plugin closes, the socket closes and the server writes down how long it was open. The editor, the mode and the country come with every event without you asking. The full list of what leaves the plugin, field by field, is in what the SDK sends.
One thing to plan for: every distinct action name counts as an event type, and the free plan allows two. The example above uses three. On Free I would keep fonts_replaced and replace_failed, and add the timing when the plugin earns a paid plan.
Observability without an APM
Backend tools measure response times and catch exceptions for you. In a plugin you measure the one or two operations people actually wait on, and you count the ways they end badly. That turns out to be enough.
Speed. A number attached to an action gets a count, an average, a median, a p95 and a maximum per name. The median says what a typical run feels like; the p95 says what the unlucky twentieth person sees, and that person is the one who writes the review. A replace_seconds median of 0.8 with a p95 of 14 is a plugin that is fine on small files and stalls on big ones. You learn that before anyone complains.
Failures. replace_failed per day, next to fonts_replaced per day, is an error rate you can read at a glance. It will not give you a stack trace. When the line moves, open the plugin with debug: true in init, which logs every call and every server answer to the plugin console, and reproduce it there.
Sessions as a health check. The session-length histogram has a first bucket of under ten seconds: opened, closed, nothing in between. If that bucket grows after a release, people are opening the plugin and not finding what they came for. No error tracker catches that.
The day something changed. The dashboard writes one line for the strongest shift in the range: a day far off its usual level, or one editor, country or action whose share of events moved a lot. A spike in FigJam usage after Figma featured a plugin shows up there without anyone building a report.
What the dashboard looks like
Top to bottom: a verdict line (Growing, Steady, Slipping or Quiet) against the previous period, the biggest change, and totals for actions, metrics, sessions, unique users and average session. Then the audience: returning users, week-one retention, stickiness and how far the main feature reaches. Then the usage charts: actions per day by name, metric totals and percentiles, sessions, session length, an activity heatmap by hour and weekday, the editors and modes, and a map of countries. The demo is the same page with sample numbers, and you can click through it without an account.
It is a fixed page on purpose. There are no funnels, no replay and no custom properties. If you need those, the comparison pages say honestly which tool to take instead.
What I still look at elsewhere
Installs and likes stay on the Community page; tode does not copy them in. Stack traces need an error tracker of their own, and most plugins will never need one. Payments, if the plugin has them, live with whoever takes the money. Three tabs instead of one, which I have made peace with, because the one I open every morning answers the questions the other two cannot.
The short version
- Cookie-based analytics fails in a plugin because the UI has a null origin. Use the Figma user id instead of a cookie.
- Write the list of actions before choosing a tool. Five kinds of question cover most plugins: users, returns, features, speed, failures.
- Time the slow operation as a number and read its p95. Count failures as their own action.
- Put both
processing.usetode.deventries andcurrentuserin the manifest, or the published plugin goes quiet. The publishing guide has the rest of that list.
tode is free for one plugin. The setup is in the docs, and the key comes from the dashboard.