Write your first DSH plugin: layout, manifest, load
The fastest path to a first DSH plugin is reverse engineering: pick the smallest market plugin, read how it is organized, then write your own minimal version. The spec is the safety net; a working plugin shows the real shape.
Why start from a market plugin
The spec says what is required; a working plugin shows how it is actually written, and market plugins are known-working samples. Directory layout, package.json fields, entry style, and configuration declarations are visible; you do not have to guess which fields are mandatory (source).
The goal is structure, not code, so start small and then read complex samples. Copy the layout and fields, not the business logic; copying logic means breaking everything on the first change. Reading a thousand-line plugin first buries you in details; find one that does a single thing.
Step one: pick the smallest plugin
Not every market plugin is a good template. Three filters remove most noise.
- Single purpose: one command, one panel, or one tool; more features mean a harder layout to unpack.
- Public source: the Hub detail page links to a repository; install-only packages reveal nothing.
- Recent update: it follows the current version, so fields and patterns are not outdated.
Prefer a plugin that registers one command. It usually has one entry file, one package.json, and a README, the clearest structure. Avoid plugins with a build chain; they add bundler, transpiler, and style configuration that mislead a first read.
Step two: read four files
Plugin information is concentrated in four files. Do not read all source yet.
- package.json: package name, type, main or exports, dependencies, and dsh.bundle; this decides how the plugin loads.
- Entry file: find apply and see what it registers: commands, tools, services, or UI.
- Configuration declaration: which settings the plugin accepts and their defaults; plugins without one usually cannot be adjusted in settings.
- README: install command, compatible versions, and known limits; faster than source for understanding the purpose.
After four files, decide whether more source is needed. Most first plugins only require these four layers. dsh.bundle is the most overlooked field: declaring it appends the package to the profile bundles list on local install; without it, the plugin may install but never join the composition (source).
Step three: copy structure, write minimal apply
Make it loadable before adding features. A minimal plugin needs one entry and one registration.
// Minimal plugin: log on load and register one command
export const apply = (ctx) => {
ctx.logger.info('plugin loaded');
ctx.command('hello', 'say hello').action(() => 'Hello, DSH!');
};
This only proves two things: the plugin loaded and the command registered. If both work, the directory, package name, and entry are correct. Add configuration only when users need parameters; piling it on early mixes "load failure" with "configuration error." Package and directory names need a pattern, not equality: a dsh- prefix makes plugins recognizable and avoids core package collisions, which cause resolution conflicts that take time to diagnose.
Step four: load locally
Do not publish to npm for local debugging; link the directory.
- Confirm the directory is the package root with package.json.
- From the repository directory run dsh plugin --profile web add ./your-plugin-directory.
- The first run initializes the profile, and pnpm links the directory.
- Confirm the package appears in dsh plugin --profile web list.
- Restart and open a new session; verify the command or entry appears.
The add argument is a directory, not a file. Pointing at src or dist fails; it must be the level containing package.json. A wrong profile is the most common "no effect": you tested in the desktop app but installed into web.
Re-run add after dependency changes. Source-only changes need a restart; package.json or dependency changes need add again so pnpm relinks.
Step five: debugging three ways
Check logs, then configuration, then code.
- Logs: ctx.logger.info inside apply is the most direct evidence; no log means the plugin did not load.
- Configuration: dsh --dump-config shows whether the plugin section exists; present but wrong behavior means inspect registration.
- Restart: when hot reload fails, exit fully and start again; refreshing the page is not enough.
"No log" and "log present but behavior wrong" are different problems. The first checks loading and package name; the second checks registration and parameters. Change one thing at a time and keep a change record.
Step six: pre-publish checklist
Working locally does not mean ready to publish.
- Unique package name: no collision with core or existing plugins, which causes resolution conflicts.
- Declare dsh.bundle: so the profile recognizes it as a bundle.
- State compatible versions: in README and package.json.
- Provide config defaults: otherwise first run fails.
- Document install and uninstall: two commands reduce repeated questions.
- Test both local and npm installs: behavior must match before submitting.
Test in a clean profile before publishing. Local success may rely on cache or old dependencies; only a clean environment exposes missing dependencies.
Four common pitfalls
Four pitfalls dominate first-time plugin work.
- Wrong directory level. add points at a subdirectory and pnpm cannot find package.json.
- Missing dsh.bundle. The package installs but never joins the composition: listed but no function.
- Package name collision. Loading fails with a resolution error; add a prefix.
- No config defaults. First run fails on missing parameters; give every option a safe default.
All four are structural, not business logic. Get the structure working first, then write features, and rework is minimal.
Next steps
Get the minimal plugin running, then add features and publish. For the full development guide, read How to develop a DSH plugin. To compare local directory and GitHub installs, read How to install a local plugin directory. To submit to the market, read Publishing a plugin to DSH Plugin Hub.
Sources: DeepSeek Harness docs - Packaging and installing plugins, DeepSeek Harness docs - Your first plugin, dsh CLI README, DSH Plugin Hub plugin page
FAQ
When writing a DSH plugin, look at a small market plugin first. The spec tells you what is required, while a working plugin shows what it looks like in practice.
A minimal DSH plugin needs at least package.json, an entry file, and a configuration declaration. package.json declares name, dependencies, and dsh.bundle; the entry exports apply; the config declares accepted settings.
A local DSH plugin install is a link, so a restart usually picks up source changes. If you changed package.json or dependencies, run add again.
Check that the DSH plugin directory is the package root containing package.json. Then confirm the profile and the package name with its dsh.bundle declaration.
Related Terms
- apply
- apply is the entry function of a DSH plugin; the framework calls it with ctx, and the plugin registers commands, tools, UI, or services inside it.— DeepSeek Harness docs - Your first plugin
- dsh.bundle
- dsh.bundle is a package.json declaration marking the package as a plugin bundle; once declared, local installation appends it to the profile bundles list.— DeepSeek Harness docs - Packaging and installing plugins
- local directory install
- A local directory install links plugin source into a profile, suitable for editing code; a restart applies changes.— DeepSeek Harness docs - Packaging and installing plugins
- profile
- A profile is the target environment for plugin installation and execution; a local plugin must go into the profile you actually use.— dsh CLI README
Sources
- DeepSeek Harness docs - Packaging and installing plugins· deepseek-ai
- DeepSeek Harness docs - Your first plugin· deepseek-harness
- dsh CLI README· deepseek-ai
- DSH Plugin Hub plugin page· dsh-plugin.org