The Obsidian sample plugin ships five working behaviours and a two-file version bump
Template for Obsidian community plugins with build configuration and development best practices.
At a glance
- What is it?
- This repository is the starting point for Obsidian community plugins: TypeScript against the packaged API, esbuild in watch mode, a lint gate on every commit, and a release process where manifest.json and versions.json both have to be edited. It is a template to copy, not a plugin to install.
- Who is it for?
- This template fits a developer writing their first Obsidian community plugin who wants the build, the lint rules, and the release steps already arranged instead of assembling them from memory. It does not fit someone who wants a plugin to install, since the repository contains sample behaviours rather than a finished tool, and it does not fit a team that wants reproducible typing, because the plugin API is tracked as latest and updates arrive when you run npm update.
- Can I use it commercially?
- Yes. 0BSD is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 104 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- obsidianmd/obsidian-sample-plugin4,693 ★ · +153
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| obsidianmd/obsidian-sample-plugin | 4.5k | 4.7k |
153 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Five behaviours ship as working references
The template is not an empty project, which is its main teaching trick. It demonstrates five pieces of the plugin API, each a few lines long and each deletable. It adds a ribbon icon which shows a Notice when clicked. It adds a command called Open modal (simple) which opens a Modal. It adds a plugin setting tab to the settings page. It registers a global click event and outputs a Notice on the click. And it registers a global interval which logs setInterval to the console. The set of five covers the areas a first plugin usually needs: a UI entry point, a command, settings, event handling, and a timer. The consequence for you is that you start by deleting things, and each deletion leaves behind a working reference for one corner of the API instead of an example you had to reconstruct from documentation.
npm run dev skips the type check that npm run build performs
The two build paths are deliberately different, and the difference matters while you work. The package is an ES module whose entry point is main.js, and the dev script is just the bundler:
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"lint": "eslint ."
}npm run dev runs esbuild.config.mjs and leaves it watching, compiling src/main.ts into main.js and picking up new .ts files as you add them. npm run build puts a type pass in front of the same bundler, tsc with -noEmit and -skipLibCheck, and adds the production argument. So your day-to-day loop has no type checking in it. A type error will not stop the watcher, and the place you find out about it is the build or the editor, which is why the build script exists even though the watcher already produced a usable bundle.
The version script edits two files and stages them
Version bumping is scripted, but the script only handles what is mechanical. Running npm version patch, npm version minor, or npm version major, after you have updated minAppVersion by hand in manifest.json, runs node version-bump.mjs and then stages the result with git add manifest.json versions.json. The stated effect of the command is that it bumps the version in manifest.json and package.json, and adds the entry for the new version to versions.json. Two things are worth internalising. First, the minimum Obsidian version is yours to update before the command runs, so a forgotten step ships a release that claims compatibility you have not tested. Second, the script touches two files in the repository while package.json gets the third, so a release commit always spans three files and reviewing it means checking all of them.
manifest.json has to be uploaded twice
The release checklist is specific enough to follow without guessing. Update manifest.json with the new version number, such as 1.0.1, and the minimum Obsidian version your release requires. Update versions.json with the new plugin version mapped to that minimum Obsidian version. Create a GitHub release using the exact version number as the tag version, with no prefix, and upload manifest.json, main.js, and styles.css as binary attachments. Publish. The manifest detail that catches people is stated explicitly: the manifest.json file must be in two places, the root path of the repository and the release itself. The tag detail matters too, since the exact version number is required without a v prefix. The manifest is also where optional metadata lives, for example a funding URL:
{
"fundingUrl": "https://buymeacoffee.com"
}versions.json is the compatibility map for old installs
One file in the repository exists for users who are not running the latest Obsidian, and its purpose is stated without ambiguity. You update versions.json with an entry of the form:
"new-plugin-version": "minimum-obsidian-version"The reason given is that older versions of Obsidian can then download an older version of your plugin that is compatible with them. That makes the file a lookup table keyed by plugin version, read by the app when the user is on an older build than your newest release. The consequence of skipping it is quiet rather than loud. Your newest release installs fine on the newest Obsidian, while anyone on an older build either sees no compatible version or is handed one your current code no longer supports.
The plugin folder inside .obsidian is convenience, not a requirement
The development loop assumes you keep the checkout where Obsidian can see it. You clone your repository to a local development folder, and for convenience you can place that folder in your .obsidian/plugins/your-plugin-name folder, then install NodeJS and run npm i under the repo folder, then run npm run dev to compile your plugin from src/main.ts into main.js. Reload Obsidian to load the new version and enable the plugin in the settings window. NodeJS has to be at least v18, which you can check with node --version. Installing a finished build by hand is the same idea reduced to three files: copy main.js, styles.css, and manifest.json to your vault at VaultFolder/.obsidian/plugins/your-plugin-id/. So the folder layout is the contract, and a plugin that is not in it is invisible until it is loaded.
Lint runs on every commit, with Obsidian-specific rules
Quality gates are preconfigured, which is the part of a template that saves the most time. ESLint is already set up, the project says you can run it against your plugin to find common bugs and ways to improve your code, and you invoke the check with npm run lint, which runs eslint against the project root. Alongside the stock rules sits a custom eslint plugin for Obsidian specific code guidelines, and the configuration lives in eslint.config.mts at the root rather than in a dotfile. The toolchain is pinned in devDependencies, including @eslint/js, eslint, typescript-eslint, the Obsidian plugin, globals, and jiti, with TypeScript itself at a version 5 release. A GitHub action is preconfigured to lint every commit on all branches, so a rule you did not write can fail your first push, before any of your own code is judged.
The API dependency tracks latest, not a fixed version
The typing story is the least pinned part of the setup. The project uses TypeScript to provide type checking and documentation, and depends on the latest plugin API, obsidian.d.ts, in TypeScript Definition format, which contains TSDoc comments describing what it does. In the package manifest that dependency is simply obsidian at latest, and to update it you run npm update under your repo folder. Two consequences follow, pulling in opposite directions. Your build is never stale against the API definition, which is convenient while the API is moving. And your build is not reproducible, because the same commit can type-check against a different definition after an update, so a change that compiled yesterday may fail today for reasons that have nothing to do with your code. Pinning obsidian to a version is the usual escape hatch. Note the template itself has one release, 1.0.0 from 2020, while pushes have continued to 2026-08-02, so the maintenance is in the template and the versioning is left to you.
Editorial conclusion
This template fits a developer writing their first Obsidian community plugin who wants the build, the lint rules, and the release steps already arranged instead of assembling them from memory. It does not fit someone who wants a plugin to install, since the repository contains sample behaviours rather than a finished tool, and it does not fit a team that wants reproducible typing, because the plugin API is tracked as latest and updates arrive when you run npm update. Before you start, check three things: that a plugin for your idea does not already exist and could be partnered on instead; that you have NodeJS v18 or newer; and that you accept a workflow where the version, the manifest, and the compatibility map are three files you keep in step by hand.
Frequently asked questions
How do I develop an Obsidian plugin from this template?
Make a copy of the repository with the Use this template button, clone it into your .obsidian/plugins/your-plugin-name folder, install NodeJS, and run npm i under the repo folder. Then run npm run dev to compile from src/main.ts into main.js, reload Obsidian, and enable the plugin in the settings window. NodeJS v18 or newer is required.
What does npm run dev do in the Obsidian sample plugin?
It runs node esbuild.config.mjs, which compiles src/main.ts into main.js in watch mode and picks up new .ts files. The production path is different: npm run build runs tsc -noEmit -skipLibCheck first and then node esbuild.config.mjs production, so the watch loop itself does no type checking.
How do I release a new version of an Obsidian plugin?
Update manifest.json with the new version and the minimum Obsidian version, add an entry to versions.json mapping the new plugin version to that minimum, then create a GitHub release tagged with the exact version number and no v prefix, uploading manifest.json, main.js, and styles.css as attachments. manifest.json must be present both in the repository root and in the release. Running npm version patch, minor, or major does the mechanical bumps once minAppVersion is set by hand.
How do I install the built Obsidian plugin manually?
Copy main.js, styles.css, and manifest.json into your vault at VaultFolder/.obsidian/plugins/your-plugin-id/. Those three files are the whole distributable, which is also what gets attached to a GitHub release.
How do I add my plugin to the Obsidian community plugin list?
Check the plugin guidelines, publish an initial version, make sure a README.md file is in the root of your repo, and make a pull request at the obsidianmd/obsidian-releases repository to add the plugin.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/obsidianmd-obsidian-sample-plugin)Star history chart for your README
[](https://hysenlabs.com/projects/obsidianmd-obsidian-sample-plugin)Was this useful?