# Publish checklist

Most "tode shows nothing" reports come from the published build, not from development: a domain missing from the manifest, a key that was not in `.env` when the bundle was built, or a plugin that was never registered. Go through the five steps in order before you press Publish; each one is a minute.

## 1. The manifest

Figma reads `manifest.json` from the project root. These fields decide whether the SDK can talk to tode at all.

```json title="manifest.json"
{
  "name": "Font Replacer",
  "id": "1100808562040625391",
  "api": "1.0.0",
  "main": "dist/main.js",
  "ui": "dist/ui.html",
  "editorType": ["figma", "figjam", "slides"],
  "documentAccess": "dynamic-page",
  "permissions": ["currentuser"],
  "networkAccess": {
    "allowedDomains": [
      "https://processing.usetode.dev",
      "wss://processing.usetode.dev"
    ],
    "reasoning": "Usage analytics (tode). No document content is sent."
  }
}
```

| Field | What tode needs | What goes wrong without it |
|---|---|---|
| `networkAccess.allowedDomains` | Both entries: `https://processing.usetode.dev` for events and `wss://processing.usetode.dev` for sessions | Figma blocks the request. The SDK logs it and the plugin keeps working, but nothing reaches the dashboard. This is the most common cause. |
| `permissions` | `"currentuser"` | `figma.currentUser` is `null`. Events arrive, but every one is anonymous: no unique users, no retention, no stickiness. |
| `id` | The numeric id Figma assigned to this plugin | A plugin published with another plugin's id, or with a placeholder, fails Figma's validation. See step 2. |
| `documentAccess` | `"dynamic-page"` | Required by Figma for every new plugin. The template sets it; keep it. |
| `editorType` | The editors the plugin should appear in: `figma`, `figjam`, `dev`, `slides`, `buzz` | Figma does not accept `["figjam", "dev"]`. |
| `main`, `ui` | Paths relative to the manifest, pointing at the production build | A path that points at a source file or at a missing build publishes a broken plugin. |

`reasoning` is optional unless `allowedDomains` includes `*` or `localhost`; Figma shows the allowed domains on the Community page either way, so the sentence is worth having. `devAllowedDomains` is for local servers during development and must not carry the tode hosts; they belong in `allowedDomains` so the published build has them.

Projects from [the template](https://docs.usetode.dev/template) ship this manifest and `npm run check` fails if any of these entries goes missing.

## 2. The id

Figma assigns the id. You get it in one of two places:

- **Plugins → Development → New plugin** in Figma Desktop writes a manifest with a fresh id. Copy that id into your own `manifest.json` if you already have a project.
- **Publish** assigns one at the time of publishing if the manifest did not have a real one yet. The template ships `"id": "0000000000000000000"` as a placeholder: development works with it, and you replace it with the id Figma shows in the publish dialog.

One id, one plugin. Reusing the manifest of another plugin publishes an update to that plugin, not a new one.

tode ties events to the API key, not to this id, so a wrong id does not route events elsewhere. The id matters for Figma, and for the next step: the Community URL contains it.

## 3. The key in the build

The SDK reads the key from `tode.init({ apiKey })` in the controller. In a template project the key comes from `TODE_API_KEY` in `.env`, which the bundler inlines at build time as `__TODE_API_KEY__`. That means:

- The key has to be in `.env` **when you run `npm run build`**. A build on a machine without `.env`, or in CI, ships a plugin with analytics off; the controller prints `[tode] TODE_API_KEY is empty, analytics is off` and nothing else happens.
- Build locally with `.env` in place, then publish from that build.
- The key ends up in the bundle. That is expected: it is a public identifier tied to one plugin, rate-limited per IP and per user, and you can rotate it in the dashboard (the old key keeps working for 24 hours).

To get the key, register the plugin in the [dashboard](https://app.usetode.dev): paste its Community URL (`https://www.figma.com/community/plugin/<id>/...`). tode reads the id and the name from the URL and issues the key. If the plugin is not on Community yet, publish it first with the key empty, register it, add the key, rebuild, and publish an update.

## 4. Publish

Publishing happens in Figma Desktop only: Plugins → Manage plugins → the plugin's menu → Publish. Before that:

```bash
npm run check   # typecheck, lint, tests, production build, manifest check
```

The publish form asks for a name, a tagline, a description and a category, an icon (128×128) and a thumbnail (1920×1080). Figma derives the network access label from the manifest; a plugin without `networkAccess` is listed as "Unknown network access". Public listings go through review, which can take days; organisation-only plugins do not.

An update is the same flow: rebuild, Publish → publish an update. Editing the listing text does not need a new build.

## 5. Verify

Run the published plugin once from Community, not from the development entry, and check:

1. **The dashboard shows the session.** Sessions per day gets a bar within a minute of the plugin UI opening. If not, the WebSocket host is missing from `allowedDomains`.
2. **An action arrives.** Trigger one tracked feature. Actions per day shows it. If sessions arrive and actions do not, `init()` is not being called or the key is empty in the build.
3. **Unique users is not zero.** If it is, `currentuser` is missing from `permissions`.

With `debug: true` in `init()` the SDK logs each call and each server answer to the plugin console (Plugins → Development → Open console), which turns any of the three into a one-line diagnosis. Turn it off for the release build. The [Error Handling](https://docs.usetode.dev/error-handling) page lists every server response code.

## Before you press Publish

- `allowedDomains` has both `processing.usetode.dev` entries.
- `permissions` has `currentuser`.
- `id` is the one Figma gave this plugin.
- `.env` holds the key and the build is fresh.
- `npm run check` exits 0.
- One run of the production build shows a session and an action on the dashboard.

Source: https://docs.usetode.dev/publish
