Skip to content

Manifest generators compared

Pick the tool that writes your custom-elements.json, by what each one reads from your components and where its paths point. Skip this page if you only want to turn the plugin’s manifest option on.

Every cell below comes from running the four tools on one small Lit library in October 2026: an element that extends a mixin over a base class, with decorators, JSDoc, a constructor, dispatched events, a vanilla element and a barrel file. The versions were Lit DevTools 0.24, @custom-elements-manifest/analyzer 0.11.0 with --litelement, cem 0.11.4 and @lit-labs/gen-manifest 0.3.6 through lit labs gen --manifest. Follow the links for their current state.

Lit DevTools CEM analyzer cem Lit gen-manifest
Runs as Vite plugin option, lit-devtools manifest CLI, or create() in the browser CLI (a Go binary on npm) lit labs gen --manifest
Schema version written 2.1.0 1.0.0 2.1.0 1.0.0
Module paths The built files under vite build, else source Source (src/x.ts) Source, renamed to .js The tsconfig outDir (out/x.js)
@property() attribute and reflects Yes Yes Yes, but lost on a class that extends a mixin Yes
Types As written; the type: option when unannotated As written As written From the type checker
@state() and private members Left out Listed, with privacy Private listed, @state() left out Listed, with privacy
Methods, with parameters and return type Yes Yes; the return type came out empty Yes, without the @returns description Yes
render() and @internal methods Left out Left out Listed Listed
Events from @fires Yes Yes Yes Yes
Events from this.dispatchEvent(…) Yes, literal names only Yes, and one named after a variable No No
Defaults set in the constructor Yes Yes No No
Slots, parts, CSS custom properties Yes Yes Yes Yes
CSS states (@cssstate) Yes Yes Yes No
@attr, @prop, @default, @tag tags Yes Yes @attr and @default No
Inherited members, marked inheritedFrom Yes, across modules, mixins and dependencies’ manifests Yes No No
Mixin declarations Yes Yes No Yes
A vanilla HTMLElement element Yes Yes No No
observedAttributes Yes Yes No No
Barrel files (export * from) Resolved to each name, through chains of barrels Listed as a * export Resolved to each name Resolved to each name
Watch mode The dev server, through HMR and a file watcher --watch --watch No option
Also does The same docs in the DevTools panel and Lit Inspector A plugin API; FAST, Stencil and Catalyst A language server, MCP, a dev server, validate React and Vue wrappers

The analyzer is the reference most tools read against, and it reads the widest syntax, frameworks beyond Lit included, through plugins. It lists private members and internal state with their privacy, so a reader that wants the public API filters them out. Its paths name your source files, so a published package needs a plugin to point them at the built ones.

cem writes schema 2.1 and comes with tooling built on the manifest: a language server, an MCP server, a dev server and a validator. On our library it read decorated properties well on an element that extends LitElement, but not on one that extends a mixin call. It doesn’t merge inherited members, infer dispatched events or read constructor defaults.

The Lit Labs generator runs the TypeScript checker, so it reports types you never wrote, such as string for a parameter that defaults to 'info'. Its paths follow your tsconfig’s outDir. It lists lifecycle and @internal methods and doesn’t merge inherited members. It is experimental, like the rest of Lit Labs.

The plugin reads your components the way its DevTools panel does, so the manifest, the panel and Lit Inspector show the same docs. Under vite build its paths name the files you ship, and tree-shaken components are left out. In dev the manifest follows every edit. Its types are the text you wrote, and the table’s “No” rows are what it doesn’t read yet.