tode

Guides

How to track usage of a Figma plugin

Which events are worth sending, where to call them in a plugin, how to name them so the numbers stay comparable, and what the manifest needs.

, by Ilia

Most plugins need to answer three questions. How many people use it. Whether they come back. Which of its features they touch. Everything past that is a nice-to-have, and most of the analytics mistakes I see come from starting with the nice-to-haves.

This is the order I would do it in. The code uses tode because that is what I built, but the decisions about what to track and how to name it apply to any tool.

1. Decide what counts as a feature

Write down the five or so things your plugin does from the user's side. Not the UI elements, the outcomes: "replaced fonts in the selection", "exported a sheet", "copied the token list". Those are your actions. If the list is longer than ten, your plugin does too much or your list is too fine-grained.

Opening the plugin is not on the list. Sessions already cover it, and a session also tells you how long it stayed open.

2. Name them so they stay comparable

An action name is a key you will group by for years. Treat it like a database column.

  • Lowercase with underscores, a noun and a past-tense verb: fonts_replaced, sheet_exported, tokens_copied.
  • Never put a value in the name. export_png_2x and export_png_3x split one feature into two lines. Use png_exported and send the scale as a metric, or accept that you do not need the scale.
  • Never put user data in the name. Not a file name, not a layer name, not a user name. Event names end up in dashboards and in exports.
  • Do not rename. A renamed action is a new action with no history. If a name is wrong, live with it or accept the cut.

3. Pick numbers for the things that have a size

Some actions carry a quantity: layers processed, fonts replaced, seconds the export took. Send that quantity with the action and you get totals per day and a distribution (median, 95th percentile, max) instead of a bare count. The distribution is where the surprises are. A median of six fonts and a max of twelve hundred tells you someone ran your plugin on a whole design system, and that your progress bar matters more than you thought.

tode.trackActionWithMetric('fonts_replaced', replaced.length);
tode.trackActionWithMetric('export_seconds', (Date.now() - started) / 1000);

4. Put the calls where the work happens

A plugin has two places to call from. The controller (code.ts) knows what was done to the document. The UI knows what the user clicked. Track outcomes from the controller and intent from the UI, and do not track both for the same feature or every number doubles.

// 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') {
    const replaced = await replaceFonts(figma.currentPage.selection, msg.from, msg.to);
    tode.trackActionWithMetric('fonts_replaced', replaced.length);
  }
};
// ui.tsx
import { useTode } from '@tode-sdk/figma-react';

function App() {
  const { tode, isReady } = useTode();
  return (
    <button onClick={() => {
      if (isReady) tode.trackAction('preview_opened');
      parent.postMessage({ pluginMessage: { type: 'preview' } }, '*');
    }}>
      Preview
    </button>
  );
}

The hook also opens the session connection, so session length is measured without any call from you. There are adapters for Vue, Angular, Svelte and plain TypeScript that do the same job.

5. Fix the manifest

Two lines in manifest.json decide whether any of this works.

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

currentuser gives you figma.currentUser.id, which is the only stable identity a plugin has. Without it every event is anonymous: actions and sessions still count, but "users" and anything built on users (returning, retention, stickiness) have nothing to count. networkAccess is Figma's allow-list for outgoing requests. Miss it and the SDK logs a failed request and carries on; nothing reaches the dashboard.

6. Keep development out of the numbers

You will run your plugin a hundred times while building it. Either register a second plugin in tode and use its key in development, or gate init behind a build flag. The free plan covers one plugin, so the flag is the cheaper option there.

if (process.env.NODE_ENV === 'production') {
  await tode.init({ apiKey: 'tode_...' });
}

7. On the free plan, spend your two event types well

Free allows two action names per plugin. Sessions do not count toward that, so opens are covered. Spend the two on the feature you think is the main one and the feature you are not sure anyone uses. The second one is the useful number.

What to look at, and when

In the first week, look at actions by name and the daily users chart. That answers "does anyone use the thing I spent a month on". After two full weeks the first week-one retention cohort appears: of the people who found the plugin in a given week, how many came back the next. After 28 days stickiness shows up, which is how many days a month a typical user opens the plugin. There is a longer note on reading those numbers without jumping to conclusions.

Resist adding events for a month. The first set will tell you what you actually wonder about, and that is a better guide than guessing up front.