@spree/cli plugin new scaffolds the npm package shape, but you still need to fill in publishing-specific metadata before pnpm publish. This page covers what to set and why.
Set the right peerDependencies
Your plugin imports React, the dashboard, Tailwind, i18next, etc., but you don’t bundle them — the host app has those, and bundling them twice causes either duplicate module instances or a tree-shaken nightmare. Use peerDependencies:
0.x versions, ^ only allows patch-level drift (^0.1.0 matches 0.1.x, not 0.2.0), so expect to bump your @spree/dashboard-* peers alongside dashboard releases until 1.0. spree plugin new scaffolds these ranges for you, matched to the current release.
dependencies is for things your plugin uses that the host doesn’t — small utilities (e.g., clsx, date-fns if you need a specific version, your own helper packages). When in doubt, peer-dep it. The trade-off is “user has to install one more thing” vs. “user has two copies of React” — always pay the first cost.
Choose what to publish
The scaffold’spackage.json ships TS source by default. Two options:
Option A — Ship pre-built JS
Runtsup on publish, ship dist/:
Option B — Ship source
Side-effects flag
The plugin’s whole purpose is side effects (defineDashboardPlugin registers stuff at module-load time). Tell bundlers not to tree-shake it:
import '@your-scope/your-plugin' does nothing useful and drop it. The list is what’s allowed to have effects; everything else still tree-shakes.
Tell the Vite plugin where to find you
Declare your package as a dashboard plugin in its ownpackage.json:
routes (optional) points at your TanStack file-routes directory; the host build compiles it into its typed route tree — see the routes guide.
This marker is what powers auto-discovery: when a host calls spreeDashboardPlugin() without an explicit plugins array (the default), the Vite plugin walks the host’s package.json deps + devDeps, reads each one’s manifest, and picks up anything with spree.dashboard.plugin: true. The host doesn’t have to know your plugin’s name — pnpm add @your-scope/your-plugin is the entire installation step.
The CLI scaffold (spree plugin new) writes this field for you. If you’re hand-rolling a plugin, add it before publishing — without it, hosts that rely on auto-discovery won’t find your plugin and Tailwind won’t pick up your class names.
Export the Tailwind source
spreeDashboardPlugin uses require.resolve(...) to find your package, then walks src/ for Tailwind class scanning. As long as you ship src/, it works — Tailwind picks up className="..." strings from your built code without you publishing CSS.
If you only ship dist/ and your built code mangles class names (Tailwind can’t see text-${variant}-500), use static class names or expose a tailwind.config.js content array.
Versioning with Changesets
Spree’s own packages are versioned with Changesets; plugin authors don’t have to use it, but it pairs well with peer-dep ranges. Workflow:Publishing the npm package
"private": false and remove the "publishConfig.access": "restricted" if your scaffold has it.
Avoiding lock-step releases
Your plugin should compile against a range of dashboard versions. Run the build against the lowest supported version locally (pnpm add -D @spree/dashboard-core@6.0.0) and add a CI matrix job that bumps to the next minor and rebuilds. That’s the cheapest way to catch “I depended on something that doesn’t exist in 6.0.0”.

