packages/devtools/skills/devtools-marketplace/SKILL.md
Publish plugin to npm and submit to TanStack Devtools Marketplace. PluginMetadata registry format, plugin-registry.ts, pluginImport (importName, type), requires (packageName, minVersion), framework tagging, multi-framework submissions, featured plugins.
npx skillsauth add tanstack/devtools devtools-marketplaceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Prerequisite: Build a working plugin first using the devtools-plugin-panel skill. The marketplace submission assumes you already have a published npm package that exports either a JSX panel component or a function-based plugin.
The TanStack Devtools Marketplace is a built-in registry inside the devtools shell. Users browse it from the Marketplace tab, and can install plugins with a single click. Submission is a PR to the packages/devtools/src/tabs/plugin-registry.ts file in the TanStack/devtools repository.
Every marketplace entry conforms to the PluginMetadata interface exported from packages/devtools/src/tabs/plugin-registry.ts:
export interface PluginMetadata {
/** Package name on npm (e.g., '@acme/react-analytics-devtools') */
packageName: string
/** Display title shown on the marketplace card */
title: string
/** Short description of what the plugin does */
description?: string
/** URL to a logo image (SVG, PNG, etc.) */
logoUrl?: string
/** Required base package dependency */
requires?: {
/** Required package name (e.g., '@tanstack/react-query') */
packageName: string
/** Minimum required version (semver) */
minVersion: string
/** Maximum version (if there's a known breaking change) */
maxVersion?: string
}
/** Plugin import configuration -- enables one-click auto-install */
pluginImport?: {
/** The exact export name to import from the package
* (e.g., 'FormDevtoolsPlugin' or 'ReactQueryDevtoolsPanel') */
importName: string
/** 'jsx' = component rendered via { name, render: <Component /> }
* 'function' = called directly as FnName() in the plugins array */
type: 'jsx' | 'function'
}
/** Custom plugin ID for matching against registered plugins.
* The default behavior lowercases the package name and replaces
* non-alphanumeric characters with '-'.
* Example: pluginId: 'tanstack-form' matches 'tanstack-form-4'. */
pluginId?: string
/** URL to the plugin's documentation */
docsUrl?: string
/** Plugin author/maintainer */
author?: string
/** Repository URL */
repoUrl?: string
/** Framework this plugin supports */
framework: 'react' | 'solid' | 'vue' | 'svelte' | 'angular' | 'other'
/** Mark as featured -- appears in the Featured section with animated border.
* Reserved for official TanStack partners. */
featured?: boolean
/** Mark as new -- shows a "New" banner on the card */
isNew?: boolean
/** Tags for filtering and categorization */
tags?: Array<string>
}
Only two fields are strictly required by the TypeScript interface: packageName, title, and framework. In practice, always provide requires, pluginImport, and description -- without them the marketplace card is functional but auto-install cannot wire up the plugin.
A function-based plugin exports a factory function that returns a plugin object. The auto-injector calls it as FormDevtoolsPlugin() inside the plugins array:
// In packages/devtools/src/tabs/plugin-registry.ts
'@acme/react-analytics-devtools': {
packageName: '@acme/react-analytics-devtools',
title: 'Acme Analytics Devtools',
description: 'Inspect analytics events, funnels, and session data',
requires: {
packageName: '@acme/react-analytics',
minVersion: '2.0.0',
},
pluginImport: {
importName: 'AnalyticsDevtoolsPlugin',
type: 'function',
},
pluginId: 'acme-analytics',
docsUrl: 'https://acme.dev/analytics/devtools',
repoUrl: 'https://github.com/acme/analytics',
author: 'Acme Corp',
framework: 'react',
isNew: true,
tags: ['Analytics', 'Tracking'],
},
When a user clicks "Install" in the marketplace, the Vite plugin:
@acme/react-analytics-devtools<TanStackDevtools />import { AnalyticsDevtoolsPlugin } from '@acme/react-analytics-devtools'AnalyticsDevtoolsPlugin() into the plugins arrayA JSX-based plugin exports a React component. The auto-injector wraps it in { name, render: <Component /> }:
'@acme/react-state-devtools': {
packageName: '@acme/react-state-devtools',
title: 'Acme State Inspector',
description: 'Real-time state tree visualization',
requires: {
packageName: '@acme/react-state',
minVersion: '1.5.0',
},
pluginImport: {
importName: 'AcmeStateDevtoolsPanel',
type: 'jsx',
},
author: 'Acme Corp',
framework: 'react',
tags: ['State Management'],
},
The injected code looks like:
import { AcmeStateDevtoolsPanel } from '@acme/react-state-devtools'
;<TanStackDevtools
plugins={[
{ name: 'Acme State Inspector', render: <AcmeStateDevtoolsPanel /> },
]}
/>
When your devtools package supports multiple frameworks, add one entry per framework. Each entry is keyed by its own npm package name:
'@acme/react-analytics-devtools': {
packageName: '@acme/react-analytics-devtools',
title: 'Acme Analytics Devtools',
description: 'Inspect analytics events, funnels, and session data',
requires: {
packageName: '@acme/react-analytics',
minVersion: '2.0.0',
},
pluginImport: {
importName: 'AnalyticsDevtoolsPlugin',
type: 'function',
},
pluginId: 'acme-analytics',
author: 'Acme Corp',
framework: 'react',
isNew: true,
tags: ['Analytics', 'Tracking'],
},
'@acme/solid-analytics-devtools': {
packageName: '@acme/solid-analytics-devtools',
title: 'Acme Analytics Devtools',
description: 'Inspect analytics events, funnels, and session data',
requires: {
packageName: '@acme/solid-analytics',
minVersion: '2.0.0',
},
pluginImport: {
importName: 'AnalyticsDevtoolsPlugin',
type: 'function',
},
pluginId: 'acme-analytics',
author: 'Acme Corp',
framework: 'solid',
isNew: true,
tags: ['Analytics', 'Tracking'],
},
The marketplace auto-detects the user's framework from their package.json dependencies and shows only matching entries. Users can still browse other frameworks via the filter controls.
The auto-install pipeline lives in packages/devtools-vite/src/inject-plugin.ts. Understanding it clarifies why pluginImport matters:
@tanstack/react-devtools, @tanstack/solid-devtools, @tanstack/vue-devtools, etc.<TanStackDevtools /> JSX element, and modifies the plugins prop via MagicString.import { <importName> } from '<packageName>' after the last existing import.pluginImport.type:
'function': Appends ImportName() directly to the plugins array'jsx': Appends { name: '<title>', render: <ImportName /> } to the plugins arrayIf pluginImport is missing, step 3-5 are skipped entirely. The package gets installed but the user must manually wire it into the plugins prop.
Publish your package to npm. The marketplace links to npm for installation; the package must be publicly available.
Fork and clone the TanStack/devtools repository.
Edit packages/devtools/src/tabs/plugin-registry.ts. Add your entry to the PLUGIN_REGISTRY object under the THIRD-PARTY PLUGINS comment section:
// ==========================================
// THIRD-PARTY PLUGINS - Examples
// ==========================================
// External contributors can add their plugins below!
Open a PR against the main branch. Title format: feat(marketplace): add <your-plugin-name>.
The PR will be reviewed by TanStack maintainers. Common review feedback:
pluginImport -- reviewers will ask you to add itframework -- required for marketplace filteringrequires.minVersion -- avoids runtime errors for users on older versionsimportName -- must match the exact named export from your packageThe marketplace determines the user's current framework by scanning their package.json dependencies for known framework packages:
| Framework | Detected packages |
| --------- | -------------------- |
| react | react, react-dom |
| solid | solid-js |
| vue | vue, @vue/core |
| svelte | svelte |
| angular | @angular/core |
Plugins with framework: 'other' are shown regardless of the detected framework.
The featured field is reserved for official TanStack partners and select library authors. Featured plugins appear in a dedicated section at the top of the marketplace with an animated border.
To request featured status, email [email protected].
Do not set featured: true in your PR submission -- it will be rejected. The TanStack team sets this flag.
When the marketplace checks if a plugin is already active, it uses pluginId for matching. The matching logic in packages/devtools/src/tabs/marketplace/plugin-utils.ts does:
pluginId is set, checks whether any registered plugin's ID starts with or contains the pluginId (case-insensitive).packageName and extracting keyword segments.Set a custom pluginId when your plugin registers with an ID that differs from the default (lowercased package name with non-alphanumeric characters replaced by -). For example, @tanstack/react-form-devtools registers as tanstack-form-4 at runtime, so the registry entry uses pluginId: 'tanstack-form' to match it.
Without pluginImport.importName and pluginImport.type, the marketplace auto-install pipeline installs the npm package but cannot inject the plugin into the user's code. The user sees a successful install but the plugin tab never appears -- they must manually add the import and wire it into the plugins prop.
Wrong -- no pluginImport:
'@acme/react-analytics-devtools': {
packageName: '@acme/react-analytics-devtools',
title: 'Acme Analytics Devtools',
requires: {
packageName: '@acme/react-analytics',
minVersion: '2.0.0',
},
author: 'Acme Corp',
framework: 'react',
},
Correct -- pluginImport provided:
'@acme/react-analytics-devtools': {
packageName: '@acme/react-analytics-devtools',
title: 'Acme Analytics Devtools',
requires: {
packageName: '@acme/react-analytics',
minVersion: '2.0.0',
},
pluginImport: {
importName: 'AnalyticsDevtoolsPlugin',
type: 'function',
},
author: 'Acme Corp',
framework: 'react',
},
The importName must be the exact named export from your package. The type must match how the export is consumed:
'function' if your export is a factory like export function AnalyticsDevtoolsPlugin() { return { name: '...', ... } }'jsx' if your export is a component like export function AnalyticsDevtoolsPanel() { return <div>...</div> }When requires is present but minVersion is omitted or set too low, users running older versions of the base package get runtime errors when the devtools plugin tries to access APIs that do not exist in their version.
Wrong -- missing minVersion:
requires: {
packageName: '@acme/react-analytics',
},
This does not type-check -- minVersion is a required field inside requires. But setting it to '0.0.0' or an arbitrarily low version has the same practical effect: the marketplace shows the plugin as installable even when the user's version lacks the APIs your devtools plugin depends on.
Correct -- specify the actual minimum version your plugin is tested against:
requires: {
packageName: '@acme/react-analytics',
minVersion: '2.0.0',
},
If there is a known breaking change in a later version, also set maxVersion:
requires: {
packageName: '@acme/react-analytics',
minVersion: '2.0.0',
maxVersion: '3.0.0',
},
The marketplace uses semver comparison (packages/devtools/src/tabs/semver-utils.ts) to determine if the user's installed version satisfies the range. When it does not, the card shows a "Bump Version" action instead of "Install".
The framework field enables marketplace filtering. Without it (or with it set incorrectly), users cannot find your plugin when browsing by framework, and the marketplace cannot determine whether to show it for the current project.
The framework is required by the TypeScript interface, so omitting it is a compile error. The real mistake is setting it to 'other' when the plugin is framework-specific. A React-only plugin tagged 'other' will appear for Solid, Vue, and Angular users who cannot use it.
Wrong:
framework: 'other', // but the plugin only works with React
Correct:
framework: 'react',
Use 'other' only for truly framework-agnostic plugins that work in any environment.
tools
Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns.
tools
Configure @tanstack/devtools-vite for source inspection (data-tsd-source, inspectHotkey, ignore patterns), console piping (client-to-server, server-to-client, levels), enhanced logging, server event bus (port, host, HTTPS), production stripping (removeDevtoolsOnBuild), editor integration (launch-editor, custom editor.open). Must be FIRST plugin in Vite config. Vite ^6 || ^7 only.
tools
Analyze library codebase for critical architecture and debugging points, add strategic event emissions. Identify middleware boundaries, state transitions, lifecycle hooks. Consolidate events (1 not 15), debounce high-frequency updates, DRY shared payload fields, guard emit() for production. Transparent server/client event bridging.
tools
Create typed EventClient for a library. Define event maps with typed payloads, pluginId auto-prepend namespacing, emit()/on()/onAll()/onAllPluginEvents() API. Connection lifecycle (5 retries, 300ms), event queuing, enabled/disabled state, SSR fallbacks, singleton pattern. Unique pluginId requirement to avoid event collisions.