Guides

What a Figma plugin sends to tode, and what comes back.

The three events the tode SDK sends from a Figma plugin, the dashboard numbers they become, what never leaves the plugin, and how to read the Growing, Steady, Slipping and Changed lines.

, by Ilia

Before I put analytics into a plugin I want the full list of what leaves it, and I want to know what each item becomes on the other end. The docs have that list arranged field by field. This is the same list arranged by the questions plugin authors ask me, with a font replacer and an export plugin as the running examples.

What the SDK sends

Three things, and you call two of them. An action is a name at a moment: the user pressed Replace, or the export finished.

tode.trackAction('replace_fonts');

An action with a number is the same call with a value attached: how many fonts were replaced, or how many seconds the export took. Any finite number up to 1012 is accepted, which no real count gets near.

tode.trackActionWithMetric('fonts_replaced', 42);

The third thing is a session, and there is no call for it. When the plugin UI opens, the SDK opens a WebSocket to tode. When the plugin closes, the socket closes, and the server writes down how long it was open. The SDK never builds or sends a session payload itself.

What one event looks like on the wire

Here is the press of Replace in a font replacer, as the ingest service receives it. The name and the number are yours. Everything else the SDK fills in, and you never set it by hand.

{
  "type": "action_with_metric",
  "actionName": "fonts_replaced",
  "actionValue": 42,
  "timestamp": 1761120000000,
  "appSource": "figma",
  "eventSource": "ui",
  "userCountry": "DE",
  "figma_userId": "1234567890",
  "figma_pluginId": "9876543210",
  "figma_widgetId": null,
  "figma_editorType": "figma",
  "figma_mode": "default",
  "figma_apiVersion": "1.0.0"
}

timestamp comes from the user's clock, in milliseconds. If that clock is more than ten minutes away from the server's, the server stamps the event with its own time instead. So history cannot be backfilled, by accident or on purpose.

figma_userId is the Figma account id as a string, read from figma.currentUser. It is the field that turns a counter into analytics, and it goes out empty unless your manifest asks for it. More on that below.

figma_editorType is where the plugin ran: figma, figjam, dev, slides or buzz. Anything else is dropped on arrival rather than stored as a category of its own. figma_mode is the editor's mode at the time: default, inspect, codegen, textreview, linkpreview, auth, query or textrun. eventSource says which half of the plugin fired the event: ui for the iframe, plugin for the controller in code.ts, or widget.

userCountry is a two-letter code. When the plugin starts, the SDK asks tode once where the connection is coming from; the server answers from the IP of that request, and the SDK repeats the code on every event. The IP itself is not written anywhere.

The last four are housekeeping. figma_pluginId and figma_widgetId let the server check that the event belongs to the plugin your API key was issued for. figma_apiVersion and appSource are kept for support. None of the four appears on the dashboard.

What you have to do in the manifest

Two entries. The currentuser permission is what lets the SDK read figma.currentUser.id. Without it the id goes out as an empty string and tode cannot tell one person from another. Actions and sessions still count; users, returning users, retention and stickiness have nothing to count. The second entry is Figma's allow-list for outgoing requests, which has to name the ingest host over both https and wss.

{
  "permissions": ["currentuser"],
  "networkAccess": {
    "allowedDomains": [
      "https://processing.usetode.dev",
      "wss://processing.usetode.dev"
    ]
  }
}

What comes back

The dashboard turns the events above into answers to a few questions, in the order I check them.

How many people, and do they return. From the user id: unique users in the range, active users per day split into new and returning (new means their first ever event for this plugin fell inside the range), stickiness as DAU over MAU for the last 28 days, and week-one retention: the people first seen in a given week, and how many of them had any event the week after. Feature adoption uses the same id: the share of active users who did a given action at least once in the range.

Which features get used. From the action names: actions per day stacked by name, and each name's share of the total. For the font replacer this is where you find out that bulk replace is used by a third of people and the preview by almost nobody. The dashboard groups by the exact string, so a renamed action is a new feature as far as the charts know, and each distinct name counts against the event-type limit of your plan.

How hard the plugin works. From the numbers you attach: metric totals per day, and per metric name the count, average, median (p50), p95 and maximum. An export_seconds metric with a median of 3 and a p95 of 40 says most exports are quick and a few are painful, and the few are the ones people remember.

Whether it does a job. From the sessions: sessions per day with the average length, the average session tile, and a histogram of session length with p50 and p95. The buckets are under 10 seconds, 10 to 30 seconds, 30 seconds to a minute, one to two minutes, two to five, five to ten, ten to thirty, and over thirty. The first bucket is the one to watch: an open and a close with nothing between. An export plugin with most sessions between one and five minutes is doing work; the same plugin with a tall first bucket has a first-run problem, or a lot of people checking what it is.

Where and when. The country code feeds visitors by country and the world map. The timestamp feeds every per-day chart, an activity heatmap by weekday and hour in your local time, and the busiest-on note. Editor type, mode and event source make up the Environment widget, where you find out whether the plugin you test in Figma Design is also being opened in FigJam or Dev Mode. For a codegen plugin, the split between inspect and codegen is the split between browsing and the thing you charge for.

How to read the one-line verdicts

Two lines on the dashboard are stored nowhere. They are derived from the events when you ask for them.

The first is one of Growing, Steady, Slipping or Quiet. It compares users in the selected range with users in the period of the same length just before it, and shows actions alongside. More than 15 percent more users is Growing, more than 15 percent fewer is Slipping, anything between is Steady. Quiet means the range holds fewer than 50 events, which is too few to say anything, so nothing is said. A plugin with no previous period at all reads Growing. When a round total is crossed inside the range, say 1,000 users, the same line mentions it. Slipping over a short range often means the range before it had a launch in it; make the range longer before deciding anything.

The second is Changed. It names the single strongest shift in the range, and there are two kinds. A day whose event count sits far above or below the median of the seven days before it. Or a category, meaning one editor, one mode, one country or one action name, whose share of events moved by 1.5 times or more against the previous period. Six percent of events from Poland last month and ten percent this month is a Changed line; so is a launch day. When nothing moved that much, the line stays empty, which is the common case.

What stays in the plugin

The document, all of it: file names, page names, layer names, text, the selection, images, comments. Nothing in the SDK touches a node. The person beyond the id: no name, no email, no avatar, no team. The network: the IP is read once, turned into a country code, and discarded. Cookies: none; the SDK sets nothing in the browser, and the plugin iframe would not keep it anyway.

One more thing that is absent by design: a per-user view. The dashboard shows users in aggregate and nothing else, so there is no screen where you look up what one person did. I think that is the right trade for a plugin, and it keeps the privacy paragraph in your Community listing short.

The complete field list is the SDK's types.ts, which is open source. The docs keep the same facts as a reference, one field at a time, at What tode tracks and why.