|
| 1 | +# Obsidian community plugin |
| 2 | + |
| 3 | +## Project overview |
| 4 | + |
| 5 | +- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript). |
| 6 | +- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian. |
| 7 | +- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`. |
| 8 | + |
| 9 | +## Environment & tooling |
| 10 | + |
| 11 | +- Node.js: use current LTS (Node 18+ recommended). |
| 12 | +- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies). |
| 13 | +- **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`. |
| 14 | +- Types: `obsidian` type definitions. |
| 15 | + |
| 16 | +**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly. |
| 17 | + |
| 18 | +### Install |
| 19 | + |
| 20 | +```bash |
| 21 | +npm install |
| 22 | +``` |
| 23 | + |
| 24 | +### Dev (watch) |
| 25 | + |
| 26 | +```bash |
| 27 | +npm run dev |
| 28 | +``` |
| 29 | + |
| 30 | +### Production build |
| 31 | + |
| 32 | +```bash |
| 33 | +npm run build |
| 34 | +``` |
| 35 | + |
| 36 | +## Linting |
| 37 | + |
| 38 | +- To use eslint install eslint from terminal: `npm install -g eslint` |
| 39 | +- To use eslint to analyze this project use this command: `eslint main.ts` |
| 40 | +- eslint will then create a report with suggestions for code improvement by file and line number. |
| 41 | +- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/` |
| 42 | + |
| 43 | +## File & folder conventions |
| 44 | + |
| 45 | +- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`. |
| 46 | +- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands). |
| 47 | +- **Example file structure**: |
| 48 | + ``` |
| 49 | + src/ |
| 50 | + main.ts # Plugin entry point, lifecycle management |
| 51 | + settings.ts # Settings interface and defaults |
| 52 | + commands/ # Command implementations |
| 53 | + command1.ts |
| 54 | + command2.ts |
| 55 | + ui/ # UI components, modals, views |
| 56 | + modal.ts |
| 57 | + view.ts |
| 58 | + utils/ # Utility functions, helpers |
| 59 | + helpers.ts |
| 60 | + constants.ts |
| 61 | + types.ts # TypeScript interfaces and types |
| 62 | + ``` |
| 63 | +- **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control. |
| 64 | +- Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages. |
| 65 | +- Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`). |
| 66 | + |
| 67 | +## Manifest rules (`manifest.json`) |
| 68 | + |
| 69 | +- Must include (non-exhaustive): |
| 70 | + - `id` (plugin ID; for local dev it should match the folder name) |
| 71 | + - `name` |
| 72 | + - `version` (Semantic Versioning `x.y.z`) |
| 73 | + - `minAppVersion` |
| 74 | + - `description` |
| 75 | + - `isDesktopOnly` (boolean) |
| 76 | + - Optional: `author`, `authorUrl`, `fundingUrl` (string or map) |
| 77 | +- Never change `id` after release. Treat it as stable API. |
| 78 | +- Keep `minAppVersion` accurate when using newer APIs. |
| 79 | +- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml |
| 80 | + |
| 81 | +## Testing |
| 82 | + |
| 83 | +- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to: |
| 84 | + ``` |
| 85 | + <Vault>/.obsidian/plugins/<plugin-id>/ |
| 86 | + ``` |
| 87 | +- Reload Obsidian and enable the plugin in **Settings → Community plugins**. |
| 88 | + |
| 89 | +## Commands & settings |
| 90 | + |
| 91 | +- Any user-facing commands should be added via `this.addCommand(...)`. |
| 92 | +- If the plugin has configuration, provide a settings tab and sensible defaults. |
| 93 | +- Persist settings using `this.loadData()` / `this.saveData()`. |
| 94 | +- Use stable command IDs; avoid renaming once released. |
| 95 | + |
| 96 | +## Versioning & releases |
| 97 | + |
| 98 | +- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version. |
| 99 | +- Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`. |
| 100 | +- Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets. |
| 101 | +- After the initial release, follow the process to add/update your plugin in the community catalog as required. |
| 102 | + |
| 103 | +## Security, privacy, and compliance |
| 104 | + |
| 105 | +Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular: |
| 106 | + |
| 107 | +- Default to local/offline operation. Only make network requests when essential to the feature. |
| 108 | +- No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings. |
| 109 | +- Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases. |
| 110 | +- Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault. |
| 111 | +- Clearly disclose any external services used, data sent, and risks. |
| 112 | +- Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented. |
| 113 | +- Avoid deceptive patterns, ads, or spammy notifications. |
| 114 | +- Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely. |
| 115 | + |
| 116 | +## UX & copy guidelines (for UI text, commands, settings) |
| 117 | + |
| 118 | +- Prefer sentence case for headings, buttons, and titles. |
| 119 | +- Use clear, action-oriented imperatives in step-by-step copy. |
| 120 | +- Use **bold** to indicate literal UI labels. Prefer "select" for interactions. |
| 121 | +- Use arrow notation for navigation: **Settings → Community plugins**. |
| 122 | +- Keep in-app strings short, consistent, and free of jargon. |
| 123 | + |
| 124 | +## Performance |
| 125 | + |
| 126 | +- Keep startup light. Defer heavy work until needed. |
| 127 | +- Avoid long-running tasks during `onload`; use lazy initialization. |
| 128 | +- Batch disk access and avoid excessive vault scans. |
| 129 | +- Debounce/throttle expensive operations in response to file system events. |
| 130 | + |
| 131 | +## Coding conventions |
| 132 | + |
| 133 | +- TypeScript with `"strict": true` preferred. |
| 134 | +- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules. |
| 135 | +- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules. |
| 136 | +- **Use clear module boundaries**: Each file should have a single, well-defined responsibility. |
| 137 | +- Bundle everything into `main.js` (no unbundled runtime deps). |
| 138 | +- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly. |
| 139 | +- Prefer `async/await` over promise chains; handle errors gracefully. |
| 140 | + |
| 141 | +## Mobile |
| 142 | + |
| 143 | +- Where feasible, test on iOS and Android. |
| 144 | +- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`. |
| 145 | +- Avoid large in-memory structures; be mindful of memory and storage constraints. |
| 146 | + |
| 147 | +## Agent do/don't |
| 148 | + |
| 149 | +**Do** |
| 150 | +- Add commands with stable IDs (don't rename once released). |
| 151 | +- Provide defaults and validation in settings. |
| 152 | +- Write idempotent code paths so reload/unload doesn't leak listeners or intervals. |
| 153 | +- Use `this.register*` helpers for everything that needs cleanup. |
| 154 | + |
| 155 | +**Don't** |
| 156 | +- Introduce network calls without an obvious user-facing reason and documentation. |
| 157 | +- Ship features that require cloud services without clear disclosure and explicit opt-in. |
| 158 | +- Store or transmit vault contents unless essential and consented. |
| 159 | + |
| 160 | +## Common tasks |
| 161 | + |
| 162 | +### Organize code across multiple files |
| 163 | + |
| 164 | +**main.ts** (minimal, lifecycle only): |
| 165 | +```ts |
| 166 | +import { Plugin } from "obsidian"; |
| 167 | +import { MySettings, DEFAULT_SETTINGS } from "./settings"; |
| 168 | +import { registerCommands } from "./commands"; |
| 169 | + |
| 170 | +export default class MyPlugin extends Plugin { |
| 171 | + settings: MySettings; |
| 172 | + |
| 173 | + async onload() { |
| 174 | + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); |
| 175 | + registerCommands(this); |
| 176 | + } |
| 177 | +} |
| 178 | +``` |
| 179 | + |
| 180 | +**settings.ts**: |
| 181 | +```ts |
| 182 | +export interface MySettings { |
| 183 | + enabled: boolean; |
| 184 | + apiKey: string; |
| 185 | +} |
| 186 | + |
| 187 | +export const DEFAULT_SETTINGS: MySettings = { |
| 188 | + enabled: true, |
| 189 | + apiKey: "", |
| 190 | +}; |
| 191 | +``` |
| 192 | + |
| 193 | +**commands/index.ts**: |
| 194 | +```ts |
| 195 | +import { Plugin } from "obsidian"; |
| 196 | +import { doSomething } from "./my-command"; |
| 197 | + |
| 198 | +export function registerCommands(plugin: Plugin) { |
| 199 | + plugin.addCommand({ |
| 200 | + id: "do-something", |
| 201 | + name: "Do something", |
| 202 | + callback: () => doSomething(plugin), |
| 203 | + }); |
| 204 | +} |
| 205 | +``` |
| 206 | + |
| 207 | +### Add a command |
| 208 | + |
| 209 | +```ts |
| 210 | +this.addCommand({ |
| 211 | + id: "your-command-id", |
| 212 | + name: "Do the thing", |
| 213 | + callback: () => this.doTheThing(), |
| 214 | +}); |
| 215 | +``` |
| 216 | + |
| 217 | +### Persist settings |
| 218 | + |
| 219 | +```ts |
| 220 | +interface MySettings { enabled: boolean } |
| 221 | +const DEFAULT_SETTINGS: MySettings = { enabled: true }; |
| 222 | + |
| 223 | +async onload() { |
| 224 | + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); |
| 225 | + await this.saveData(this.settings); |
| 226 | +} |
| 227 | +``` |
| 228 | + |
| 229 | +### Register listeners safely |
| 230 | + |
| 231 | +```ts |
| 232 | +this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ })); |
| 233 | +this.registerDomEvent(window, "resize", () => { /* ... */ }); |
| 234 | +this.registerInterval(window.setInterval(() => { /* ... */ }, 1000)); |
| 235 | +``` |
| 236 | + |
| 237 | +## Troubleshooting |
| 238 | + |
| 239 | +- Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`. |
| 240 | +- Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code. |
| 241 | +- Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique. |
| 242 | +- Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes. |
| 243 | +- Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust. |
| 244 | + |
| 245 | +## References |
| 246 | + |
| 247 | +- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin |
| 248 | +- API documentation: https://docs.obsidian.md |
| 249 | +- Developer policies: https://docs.obsidian.md/Developer+policies |
| 250 | +- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines |
| 251 | +- Style guide: https://help.obsidian.md/style-guide |
0 commit comments