Here’s the short version: clientStorage belongs to the person running your plugin, on the machine they’re running it on. Plugin Data belongs to the file, or the node inside that file, no matter who opens it or where. Mix those two up and you’ll eventually ship a plugin that forgets a user’s settings every time they switch computers, or one that quietly leaks per-user preferences into a shared file that a teammate opens the next morning.
That distinction sounds simple written out like this. In practice, it’s easy to reach for whichever API you used last, without stopping to ask which entity — the user or the file — actually owns the data you’re saving. Below is a ranked list of five storage decisions, ordered from the ones developers get right almost automatically to the ones that trip up even experienced plugin authors.
1. Storing a User’s Personal Preferences — The Easiest Call
This is the case nobody gets wrong, and it’s worth starting here so the contrast is clear later on. Font defaults, a preferred color mode, whether someone wants confirmation dialogs before destructive actions — none of this belongs in the file. It belongs to the person, and it should follow them across whatever file they happen to open next.
clientStorage is built exactly for this. It’s local to the machine and the plugin, persists across sessions, and has no connection to any particular document. A user sets their preference once, and it’s there the next time they launch the plugin, regardless of which file is open.
The only wrinkle worth flagging: clientStorage doesn’t sync across devices. A designer’s preferences on their laptop won’t follow them to a desktop machine unless you build a separate sync mechanism, which most plugins never need. For personal settings, that’s rarely a problem worth solving.
2. Tagging a Node With Metadata the Plugin Needs Later
This is where Plugin Data earns its keep. Say your plugin marks certain frames as “reviewed,” or tags components with a custom category, or stores a reference ID that links a Figma node to an external system. That information describes the node itself — it needs to travel with the file and be visible to the plugin no matter who opens it.
setPluginData and getPluginData attach key-value strings directly to a node. When the file is shared, duplicated, or opened by a teammate running the same plugin, that data comes along with it. This is the mechanism that makes cross-session, cross-user consistency possible at the node level — something clientStorage was never designed to do, since it never leaves the original machine.
One limitation worth planning around early: Plugin Data is namespaced to the plugin that wrote it by default, and values are stored as strings, so anything structured needs to be serialized and parsed on the way in and out. Neither of these is a dealbreaker, but both should shape how you design your data schema from the start rather than after you’ve already hit the ceiling.
3. Storing File-Wide Settings That Aren’t Tied to One Node
Slightly less obvious than tagging individual nodes: what about settings that apply to the whole file, rather than any single element in it? Think a project-wide color palette a team has agreed on, or a naming convention configuration that should stay consistent for everyone touching that file.
This is still Plugin Data’s territory, just applied at the document root rather than an individual node. figma.root.setPluginData() stores information at the file level, so it persists with the document and is visible to any collaborator running the plugin — not just the person who set it.
The temptation here is to reach for clientStorage because the setting feels global, in the sense of applying broadly. But “broad” and “user-specific” aren’t the same thing. If the setting should be consistent for everyone who opens that file, it belongs on the file, not on one person’s machine. Getting this one backwards is a common source of the “why did my settings disappear” bug reports that show up after a plugin gets used by more than one person.
4. Caching Data to Avoid Repeated API Calls or Recomputation
This case ranks fourth because the right answer depends on what’s being cached, and that dependency is exactly what makes it trip people up. Say your plugin fetches data from an external API, or runs an expensive computation across a large selection, and you want to avoid repeating that work every time the plugin runs.
If the cached result is useful across any file the user opens — an API token, a lookup table, a set of user-specific external account details — clientStorage is the right home for it. It’s local, persistent, and doesn’t bloat the file itself with data that has nothing to do with the document’s actual content.
If the cached result is specific to that file’s content — say, computed layout metrics for a particular set of frames, tied to those exact nodes — Plugin Data is the better fit, since the cache needs to travel with the nodes it describes. Storing file-specific computed data in clientStorage means it vanishes the moment a teammate opens the same file on their own machine, and the expensive computation runs all over again for no good reason.
5. Deciding Where Undo History and Collaboration Boundaries Matter
This is the trickiest case on the list, and it’s the one that separates a plugin that behaves predictably in team settings from one that generates confusing bug reports. Plugin Data writes are part of the document, which means they interact with Figma’s undo stack and version history the same way any other document change does. clientStorage writes don’t — they sit entirely outside the document’s change history, invisible to undo, invisible to other collaborators in real time.
That has real consequences. If your plugin needs its stored state to respect a user hitting Cmd+Z, or needs collaborators to see consistent data during a live multiplayer session, Plugin Data is the only option of the two that participates in that system at all. If your plugin’s stored state is meant to be invisible scaffolding — something the user never directly interacts with and wouldn’t expect undo to touch — clientStorage avoids cluttering the undo stack with internal bookkeeping nobody asked to track.
Getting this backwards produces two distinct failure modes: plugins where a random Cmd+Z undoes something the user never consciously did, or plugins where collaborators see inconsistent state because the data never made it into the shared document at all. Both are hard to diagnose from a bug report alone, since neither looks like an obvious storage bug at first glance.
A Ranked Summary Table
| Rank | Scenario | Right Choice | Why |
|---|---|---|---|
| 1 | Personal user preferences | clientStorage | Belongs to the person, not the file |
| 2 | Node-level metadata/tags | Plugin Data (setPluginData) | Must travel with the node across users |
| 3 | File-wide shared settings | Plugin Data (figma.root) | Consistent for every collaborator, not one machine |
| 4 | Cached computation results | Depends on scope | User-general → clientStorage; file-specific → Plugin Data |
| 5 | Undo/collaboration-sensitive state | Plugin Data | Only Plugin Data participates in undo and live sync |
The One Question That Resolves Most of These Cases
If you’re still unsure which API fits a given piece of data, ask one question before writing any storage code: if a teammate opened this exact file on their own machine tomorrow, should they see this data, or shouldn’t they?
If the answer is yes, it belongs in Plugin Data, at whichever level — node or root — matches the scope of what you’re storing. If the answer is no, because the data is about the person rather than the document, clientStorage is the right home for it. Most of the edge cases above collapse down to that single distinction once you frame the decision this way, which is usually faster than working through the specifics of undo behavior or sync scope from scratch every time.