Scaffolds a new dprint formatter plugin that wraps an existing formatter library and bridges it to the dprint plugin protocol. Use when building or scaffolding a dprint plugin, wrapping a Rust, Go, or JavaScript formatter, or working on dprint plugin config resolution, schema generation, handler traits, registry metadata, npm distribution, or release flow. Selects Rust/Wasm, Go/TinyGo Wasm, or a V8-backed JavaScript process plugin.
Bridge an existing formatter to dprint; do not reimplement its formatting algorithm. The plugin adapts
the formatter's options, input, and output to the dprint protocol.
Core mental model
A dprint plugin is a small adapter implementing four responsibilities:
Config resolution — read the user's dprint.json slice + global config, produce a typed config
and a list of diagnostics for bad/unknown keys. Never hard-fail; diagnose.
Format — decode bytes (UTF-8), run the wrapped formatter, return None when the input is already
canonical (this is what makes dprint check work) or the new bytes otherwise.
Schema — a JSON Schema describing the config, published alongside the artifact.
Idempotence — formatting already-formatted output must produce no further change. Always add a test
that formats twice and asserts the second pass is a no-op.
Diagnostic-first config — invalid or unknown config keys produce ConfigurationDiagnostics, not
panics or errors.
Step 0 — Before you build: does it already exist?
Before writing code:
Search GitHub for dprint-plugin-<TOOL> across all owners, then check dprint's own organization,
dprint.dev/plugins, crates.io, and npm.
Prefer an acceptable Rust- or Go-native formatter over a JavaScript process plugin; for JS/TS, check
dprint's TypeScript and Biome plugins before reaching for Prettier.
If a plugin already exists, link it and scaffold another only when it is unsuitable or explicitly
requested.
Step 1 — Pick the architecture
Ask (or infer) what language the formatter library is written in, then route:
Wrapped formatter is…
Architecture
Reference
When
A Rust crate
Rust → Wasm (SyncPluginHandler)
references/rust-wasm.md
Default. Smallest artifact, simplest release, sandboxed in dprint's Wasm host.
A Go package
Go → TinyGo → Wasm
references/go-wasm.md
Needs a runtime bridge and codegen, but still ships as one sandboxed .wasm.
JS/Node only
Process plugin over V8 (deno_core)
references/process-v8.md
Last resort when no suitable Wasm formatter exists. Ships native per-platform binaries.
Decision rule: Wasm if you possibly can. Only reach for the V8 process plugin when the formatter is
JavaScript with no Rust/Go equivalent and can't reasonably be ported. If a formatter exists in multiple
languages, prefer the Rust one.
Process plugins can't be loaded from a remote extends config and can't run through
@dprint/formatter. They can be packaged on npm for the dprint CLI; do not confuse npm transport with
the Wasm-only programmatic host. Read distribution.md before publishing.
State the choice and why, then open only the matching architecture reference. The shared rules below
apply to every path.
Step 2 — Shared conventions (bake these in by default)
These are house defaults. Apply them unless the user says otherwise.
Naming & the proxy
House repo: github.com/<USER>/dprint-plugin-<NAME>. Any public repo works; this prefix enables the
short dprint add <USER>/<NAME> form, e.g. kjanat/dprint-plugin-svg → dprint add kjanat/svg.
The published wasm asset on each release must be named plugin.wasm — that's the file the proxy
serves for dprint add <USER>/<NAME>.
Crate/lib name: dprint-plugin-<NAME> / dprint_plugin_<NAME>. Config key in dprint.json: the short
camelCase form, e.g. texFmt, svg, jsonSchemaSort.
Registry metadata should accurately declare configKey, fileExtensions, and fileNames. It may also
provide defaultConfig and file-matched configItems, which dprint init uses to select plugins and
scaffold useful configuration (#1185, #1186, #1187).
URLs (all interpolate the repo path <USER>/<NAME> or short <USER>/<short>)
update_url: https://plugins.dprint.dev/<USER>/<short>/latest.json. Do not forget this one — it's
what powers dprint config update notifications. Set it on every plugin, regardless of architecture.
help_url: the GitHub repo URL by default, but it can point anywhere useful — a docs site, a hosted
config reference, the upstream formatter's homepage — if the plugin has one.
The generated schema.json$id matches config_schema_url.
Derive these at compile time from env!("CARGO_PKG_VERSION") and env!("CARGO_PKG_REPOSITORY") (Rust)
or -ldflags injection (Go) so the runtime can never drift from the published artifact.
Config resolution
Keys are camelCase; unknown keys emit diagnostics (get_unknown_property_diagnostics).
Inherit from dprint's global config where it makes sense: lineWidth → the formatter's wrap width,
indentWidth → tab/indent size, useTabs → tab char, newLineKind → EOL. Plugin-specific keys
override global ones.
Track upstream defaults, don't hardcode them. When the wrapped crate exposes a default options
struct, source each default from it via unmap_* helpers (see the svg reference) so the plugin's
defaults can never silently diverge from the library's. Only invent a default for options the library
doesn't model.
Plugin associations are additive to default file names/extensions; a negated glob removes a
default match. All dprint globs are case-sensitive (#1172, #1089).
Users may place plugin options in per-file overrides. dprint resolves and passes the resulting config,
so the plugin needs no parallel override mechanism; cover it in the CLI end-to-end test (#1136).
Schema generation
Generate schema.json from the Rust config type (schemars) — never hand-write it. Either inline in
build.rs (simplest, see tex-fmt) or a feature-gated generate-schema bin (when you also generate docs,
see svg). Go uses a gen-json-schema codegen tool from struct tags.
Commit the generated schema and add a CI drift check (<generate> && git diff --exit-code). To keep
that check reliable, sort the schema into a stable, canonical key order before writing it — the
json-schema-sort crate does this (and is also available
as a dprint plugin, dprint add kjanat/json-schema-sort). See references/rust-wasm.md for wiring.
Write useful property descriptions, enum/const descriptions, and defaults. dprint lsp downloads each
resolved plugin's schema to provide config completions and hover information (#1177).
Release flow
Treat released GitHub and npm artifacts as immutable. Never replace a binary for an existing version;
bump and release again.
Tag on bare semver *.*.*, not v*.*.*. The house convention is unprefixed tags
(tags: ["[0-9]+.[0-9]+.[0-9]+"]), e.g. 0.1.0, not v0.1.0. Keep tag, Cargo.toml/go.mod version,
and schema $id in lockstep. The proxy forbids - in tags, so prerelease tags such as 1.0.0-beta.1
do not resolve even though they are valid SemVer.
Tag-triggered CI builds and tests the artifact, regenerates the schema, and publishes the GitHub release.
The proxy selects the newest release that is neither a draft nor marked prerelease, so the proxy-facing
release must be published and non-prerelease.
npm is a first-class CLI source for both Wasm and process plugins. If registry info.json/latest.json
declares an npm package, dprint prefers an npm specifier and resolves its version from npm (#1215).
dprint add npm:<package> auto-detects Wasm versus plugin.json when no path is supplied (#1183).
Only Wasm packages can additionally expose the plugin through @dprint/formatter.
Read distribution.md for registry/npm metadata, package layouts, checksums,
and verification. Read release-notes.md for release-body templates and
optional GitHub hardening.
Step 3 — Verify before declaring done
Walk this checklist regardless of architecture:
Confirmed no existing dprint-plugin-<TOOL> (esp. in dprint's own org) and no lighter Rust-native
equivalent before building (Step 0).
plugin_info URLs all interpolate version + repo path; nothing hardcoded.
update_url is set (not None/empty) — points at
https://plugins.dprint.dev/<USER>/<short>/latest.json.
Config resolution emits a diagnostic for an unknown key (test it).
Formatting an already-formatted file returns "no change" (idempotence test).
Invalid UTF-8 input returns an error, not a panic.
Rust code, tests, bins, examples, and build scripts pass the strict Clippy profile in
rust-wasm-build.md; do not settle for baseline -D warnings.
schema.json is generated, committed, and CI checks it isn't stale.
Schema descriptions/defaults are useful in dprint lsp completions and hover.
The release artifact is named plugin.wasm (Wasm paths) and the README documents
dprint add <USER>/<NAME>.
Registry matching metadata is accurate; defaultConfig/configItems are present when useful.
If publishing to npm, package path and registry npm metadata agree; process packages contain
plugin.json, while Wasm packages intended for JS expose getPath() or getBuffer().
Release workflow triggers on a bare semver tag (*.*.*, not v*.*.*).
Never plan to re-upload a binary to an existing release — a mistake means bump + re-release.
At least one fixture pair plus real dprint fmt, dprint add --checksum, and
dprint config update --dry-run end-to-end checks (#1184, #1156).
Reading order
Task
Read
Rust formatter
rust-wasm.md, then rust-wasm-build.md
Go formatter
go-wasm.md
JavaScript formatter over V8
process-v8.md
Publish/register/install
distribution.md
Compose release notes
release-notes.md
For any path, the user's own repos are the canonical templates to copy from rather than reproduce from
memory:
When in doubt, open the real source — it's authoritative over anything reconstructed here.
1---2name: dprint-plugin-creator3description: Scaffolds a new dprint formatter plugin that wraps an existing formatter library and bridges it to the dprint plugin protocol. Use when building or scaffolding a dprint plugin, wrapping a Rust, Go, or JavaScript formatter, or working on dprint plugin config resolution, schema generation, handler traits, registry metadata, npm distribution, or release flow. Selects Rust/Wasm, Go/TinyGo Wasm, or a V8-backed JavaScript process plugin.4---56# dprint plugin creator78Bridge an existing formatter to dprint; do not reimplement its formatting algorithm. The plugin adapts9the formatter's options, input, and output to the dprint protocol.1011## Core mental model1213A dprint plugin is a small adapter implementing four responsibilities:14151. **Identity** — name, version, config key, schema URL, update URL (`plugin_info`).162. **Config resolution** — read the user's `dprint.json` slice + global config, produce a typed config17 and a list of diagnostics for bad/unknown keys. Never hard-fail; diagnose.183. **Format** — decode bytes (UTF-8), run the wrapped formatter, return `None` when the input is already19 canonical (this is what makes `dprint check` work) or the new bytes otherwise.204. **Schema** — a JSON Schema describing the config, published alongside the artifact.2122- **Idempotence** — formatting already-formatted output must produce no further change. Always add a test23 that formats twice and asserts the second pass is a no-op.24- **Diagnostic-first config** — invalid or unknown config keys produce `ConfigurationDiagnostic`s, not25 panics or errors.2627## Step 0 — Before you build: does it already exist?2829Before writing code:3031- Search GitHub for `dprint-plugin-<TOOL>` across all owners, then check dprint's own organization,32 [dprint.dev/plugins](https://dprint.dev/plugins/), crates.io, and npm.33- Prefer an acceptable Rust- or Go-native formatter over a JavaScript process plugin; for JS/TS, check34 dprint's TypeScript and Biome plugins before reaching for Prettier.35- If a plugin already exists, link it and scaffold another only when it is unsuitable or explicitly36 requested.3738## Step 1 — Pick the architecture3940Ask (or infer) **what language the formatter library is written in**, then route:4142| Wrapped formatter is… | Architecture | Reference | When |43| --------------------- | ---------------------------------- | -------------------------- | --------------------------------------------------------------------------------------- |44| A **Rust crate** | Rust → Wasm (`SyncPluginHandler`) | `references/rust-wasm.md` | **Default.** Smallest artifact, simplest release, sandboxed in dprint's Wasm host. |45| A **Go package** | Go → TinyGo → Wasm | `references/go-wasm.md` | Needs a runtime bridge and codegen, but still ships as one sandboxed `.wasm`. |46| **JS/Node only** | Process plugin over V8 (deno_core) | `references/process-v8.md` | Last resort when no suitable Wasm formatter exists. Ships native per-platform binaries. |4748Decision rule: **Wasm if you possibly can.** Only reach for the V8 process plugin when the formatter is49JavaScript with no Rust/Go equivalent and can't reasonably be ported. If a formatter exists in multiple50languages, prefer the Rust one.5152Process plugins **can't be loaded from a remote `extends` config** and **can't run through53`@dprint/formatter`**. They *can* be packaged on npm for the dprint CLI; do not confuse npm transport with54the Wasm-only programmatic host. Read [distribution.md](references/distribution.md) before publishing.5556State the choice and why, then open only the matching architecture reference. The shared rules below57apply to every path.5859## Step 2 — Shared conventions (bake these in by default)6061These are house defaults. Apply them unless the user says otherwise.6263### Naming & the proxy6465- House repo: `github.com/<USER>/dprint-plugin-<NAME>`. Any public repo works; this prefix enables the66 short **`dprint add <USER>/<NAME>`** form, e.g. `kjanat/dprint-plugin-svg` → `dprint add kjanat/svg`.67- The published wasm asset on each release **must** be named `plugin.wasm` — that's the file the proxy68 serves for `dprint add <USER>/<NAME>`.69- Crate/lib name: `dprint-plugin-<NAME>` / `dprint_plugin_<NAME>`. Config key in `dprint.json`: the short70 camelCase form, e.g. `texFmt`, `svg`, `jsonSchemaSort`.71- Registry metadata should accurately declare `configKey`, `fileExtensions`, and `fileNames`. It may also72 provide `defaultConfig` and file-matched `configItems`, which `dprint init` uses to select plugins and73 scaffold useful configuration ([#1185], [#1186], [#1187]).7475[#1185]: https://github.com/dprint/dprint/pull/118576[#1186]: https://github.com/dprint/dprint/pull/118677[#1187]: https://github.com/dprint/dprint/pull/11877879### URLs (all interpolate the repo path `<USER>/<NAME>` or short `<USER>/<short>`)8081- `config_schema_url`: `https://plugins.dprint.dev/<USER>/<short>/<version>/schema.json`82- `update_url`: `https://plugins.dprint.dev/<USER>/<short>/latest.json`. **Do not forget this one** — it's83 what powers `dprint config update` notifications. Set it on every plugin, regardless of architecture.84- `help_url`: the GitHub repo URL by default, but it can point anywhere useful — a docs site, a hosted85 config reference, the upstream formatter's homepage — if the plugin has one.86- The generated `schema.json` `$id` matches `config_schema_url`.8788Derive these at **compile time** from `env!("CARGO_PKG_VERSION")` and `env!("CARGO_PKG_REPOSITORY")` (Rust)89or `-ldflags` injection (Go) so the runtime can never drift from the published artifact.9091### Config resolution9293- Keys are **camelCase**; unknown keys emit diagnostics (`get_unknown_property_diagnostics`).94- **Inherit from dprint's global config** where it makes sense: `lineWidth` → the formatter's wrap width,95 `indentWidth` → tab/indent size, `useTabs` → tab char, `newLineKind` → EOL. Plugin-specific keys96 override global ones.97- **Track upstream defaults, don't hardcode them.** When the wrapped crate exposes a default options98 struct, source each default from it via `unmap_*` helpers (see the svg reference) so the plugin's99 defaults can never silently diverge from the library's. Only invent a default for options the library100 doesn't model.101- Plugin `associations` are **additive** to default file names/extensions; a negated glob removes a102 default match. All dprint globs are case-sensitive ([#1172], [#1089]).103- Users may place plugin options in per-file `overrides`. dprint resolves and passes the resulting config,104 so the plugin needs no parallel override mechanism; cover it in the CLI end-to-end test ([#1136]).105106[#1089]: https://github.com/dprint/dprint/pull/1089107[#1136]: https://github.com/dprint/dprint/pull/1136108[#1172]: https://github.com/dprint/dprint/pull/1172109110### Schema generation111112- Generate `schema.json` from the Rust config type (`schemars`) — never hand-write it. Either inline in113 `build.rs` (simplest, see tex-fmt) or a feature-gated `generate-schema` bin (when you also generate docs,114 see svg). Go uses a `gen-json-schema` codegen tool from struct tags.115- Commit the generated schema and add a CI drift check (`<generate> && git diff --exit-code`). To keep116 that check reliable, sort the schema into a stable, canonical key order before writing it — the117 [`json-schema-sort`](https://crates.io/crates/json-schema-sort) crate does this (and is also available118 as a dprint plugin, `dprint add kjanat/json-schema-sort`). See `references/rust-wasm.md` for wiring.119- Write useful property descriptions, enum/const descriptions, and defaults. `dprint lsp` downloads each120 resolved plugin's schema to provide config completions and hover information ([#1177]).121122[#1177]: https://github.com/dprint/dprint/pull/1177123124### Release flow125126- Treat released GitHub and npm artifacts as immutable. Never replace a binary for an existing version;127 bump and release again.128- **Tag on bare semver `*.*.*`, not `v*.*.*`.** The house convention is unprefixed tags129 (`tags: ["[0-9]+.[0-9]+.[0-9]+"]`), e.g. `0.1.0`, not `v0.1.0`. Keep tag, `Cargo.toml`/`go.mod` version,130 and schema `$id` in lockstep. The proxy forbids `-` in tags, so prerelease tags such as `1.0.0-beta.1`131 do not resolve even though they are valid SemVer.132- Tag-triggered CI builds and tests the artifact, regenerates the schema, and publishes the GitHub release.133 The proxy selects the newest release that is neither a draft nor marked prerelease, so the proxy-facing134 release must be published and non-prerelease.135- npm is a first-class CLI source for both Wasm and process plugins. If registry `info.json`/`latest.json`136 declares an `npm` package, dprint prefers an npm specifier and resolves its version from npm ([#1215]).137 `dprint add npm:<package>` auto-detects Wasm versus `plugin.json` when no path is supplied ([#1183]).138 Only Wasm packages can additionally expose the plugin through `@dprint/formatter`.139- Read [distribution.md](references/distribution.md) for registry/npm metadata, package layouts, checksums,140 and verification. Read [release-notes.md](references/release-notes.md) for release-body templates and141 optional GitHub hardening.142143[#1183]: https://github.com/dprint/dprint/pull/1183144[#1215]: https://github.com/dprint/dprint/pull/1215145146## Step 3 — Verify before declaring done147148Walk this checklist regardless of architecture:149150- [ ] Confirmed no existing `dprint-plugin-<TOOL>` (esp. in dprint's own org) and no lighter Rust-native151 equivalent before building (Step 0).152- [ ] `plugin_info` URLs all interpolate version + repo path; nothing hardcoded.153- [ ] `update_url` is set (not `None`/empty) — points at154 `https://plugins.dprint.dev/<USER>/<short>/latest.json`.155- [ ] Config resolution emits a diagnostic for an unknown key (test it).156- [ ] Formatting an already-formatted file returns "no change" (idempotence test).157- [ ] Invalid UTF-8 input returns an error, not a panic.158- [ ] Rust code, tests, bins, examples, and build scripts pass the strict Clippy profile in159 `rust-wasm-build.md`; do not settle for baseline `-D warnings`.160- [ ] `schema.json` is generated, committed, and CI checks it isn't stale.161- [ ] Schema descriptions/defaults are useful in `dprint lsp` completions and hover.162- [ ] The release artifact is named `plugin.wasm` (Wasm paths) and the README documents163 `dprint add <USER>/<NAME>`.164- [ ] Registry matching metadata is accurate; `defaultConfig`/`configItems` are present when useful.165- [ ] If publishing to npm, package path and registry `npm` metadata agree; process packages contain166 `plugin.json`, while Wasm packages intended for JS expose `getPath()` or `getBuffer()`.167- [ ] Release workflow triggers on a **bare semver tag** (`*.*.*`, not `v*.*.*`).168- [ ] Never plan to re-upload a binary to an existing release — a mistake means **bump + re-release**.169- [ ] At least one fixture pair plus real `dprint fmt`, `dprint add --checksum`, and170 `dprint config update --dry-run` end-to-end checks ([#1184], [#1156]).171172[#1156]: https://github.com/dprint/dprint/pull/1156173[#1184]: https://github.com/dprint/dprint/pull/1184174175## Reading order176177| Task | Read |178| ---------------------------- | ------------------------------------------------------------------------------------------------- |179| Rust formatter | [rust-wasm.md](references/rust-wasm.md), then [rust-wasm-build.md](references/rust-wasm-build.md) |180| Go formatter | [go-wasm.md](references/go-wasm.md) |181| JavaScript formatter over V8 | [process-v8.md](references/process-v8.md) |182| Publish/register/install | [distribution.md](references/distribution.md) |183| Compose release notes | [release-notes.md](references/release-notes.md) |184185For any path, the user's own repos are the canonical templates to copy from rather than reproduce from186memory:187188- Rust/Wasm: [`dprint-plugin-tex-fmt`](https://github.com/kjanat/dprint-plugin-tex-fmt) ·189 [`dprint-plugin-svg`](https://github.com/kjanat/dprint-plugin-svg) ·190 [`dprint-plugin-json-schema-sort`](https://github.com/kjanat/dprint-plugin-json-schema-sort)191- Go/TinyGo: [`dprint-plugin-shfmt`](https://github.com/kjanat/dprint-plugin-shfmt)192- V8 process plugin: [`dprint-plugin-svgo`](https://github.com/kjanat/dprint-plugin-svgo)193194When in doubt, open the real source — it's authoritative over anything reconstructed here.
Run npx skillmds@latest add kjanat/dprint-plugin-creator in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Scaffolds a new dprint formatter plugin that wraps an existing formatter library and bridges it to the dprint plugin protocol. Use when building or scaffolding a dprint plugin, wrapping a Rust, Go, or JavaScript formatter, or working on dprint plugin config resolution, schema generation, handler traits, registry metadata, npm distribution, or release flow. Selects Rust/Wasm, Go/TinyGo Wasm, or a V8-backed JavaScript process plugin. It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
kjanat (@kjanat) published this skill. Their other Agent Skills are listed on their SkillMD profile.