Skip to content

Latest commit

 

History

History
296 lines (230 loc) · 11.8 KB

File metadata and controls

296 lines (230 loc) · 11.8 KB

Obsidian community plugin

Project overview

  • Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
  • Entry point: main.ts compiled to main.js and loaded by Obsidian.
  • Required release artifacts: main.js, manifest.json, and optional styles.css.

Environment & tooling

  • Node.js: use current LTS (Node 18+ recommended).
  • Package manager: npm (required for this sample - package.json defines npm scripts and dependencies).
  • 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.
  • Types: obsidian type definitions.

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.

Install

npm install

Dev (watch)

npm run dev

Production build

npm run build

Linting

  • Local quality gate: npm run ci (lint, build, test).
  • Lint config matches the Obsidian community scanner: eslint-plugin-obsidianmd recommended + typescript-eslint recommendedTypeChecked (see eslint.config.js).
  • minAppVersion in manifest.json drives obsidianmd/no-unsupported-api (currently 1.13.0).
  • npm run linteslint . --max-warnings 0 (warnings fail the gate).
  • npm run lint:fix — project fix scripts, then eslint . --fix.
  • Historical community-review baseline: specs/COMMUNITY_REVIEW_ISSUES.md.

File & folder conventions

  • Organize code into multiple files: Split functionality across separate modules rather than putting everything in main.ts.
  • Source lives in src/. Keep main.ts small and focused on plugin lifecycle (loading, unloading, registering commands).
  • Example file structure:
    src/
      main.ts           # Plugin entry point, lifecycle management
      settings.ts       # Settings interface and defaults
      commands/         # Command implementations
        command1.ts
        command2.ts
      ui/              # UI components, modals, views
        modal.ts
        view.ts
      utils/           # Utility functions, helpers
        helpers.ts
        constants.ts
      types.ts         # TypeScript interfaces and types
    
  • Do not commit build artifacts: Never commit node_modules/, main.js, or other generated files to version control.
  • Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages.
  • 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).

Manifest rules (manifest.json)

Testing

  • Manual install for testing: copy main.js, manifest.json, styles.css (if any) to:
    <Vault>/.obsidian/plugins/<plugin-id>/
    
  • Reload Obsidian and enable the plugin in Settings → Community plugins.
  • Or use hot reload plugin for development - https://github.com/pjeby/hot-reload

Commands & settings

  • Any user-facing commands should be added via this.addCommand(...).
  • If the plugin has configuration, provide a settings tab and sensible defaults.
  • Persist settings using this.loadData() / this.saveData().
  • Use stable command IDs; avoid renaming once released.

Versioning & releases

  • Automated release process:
    • Run npm run release:<type> to build the plugin, bump the version, generate the changelog, and create a GitHub release
      • npm run release:patch to create a patch release
      • npm run release:minor to create a minor release
      • npm run release:major to create a major release
      • npm run release:beta to create a beta release
  • Manual release process:
    • Bump version in manifest.json (SemVer) and update versions.json to map plugin version → minimum app version.
    • Create a GitHub release whose tag exactly matches manifest.json's version. Do not use a leading v.
    • Attach manifest.json, main.js, and styles.css (if present) to the release as individual assets.
    • After the initial release, follow the process to add/update your plugin in the community catalog as required.

Security, privacy, and compliance

Follow Obsidian's Developer Policies and Plugin Guidelines. In particular:

  • Default to local/offline operation. Only make network requests when essential to the feature.
  • 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.
  • Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases.
  • Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault.
  • Clearly disclose any external services used, data sent, and risks.
  • Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented.
  • Avoid deceptive patterns, ads, or spammy notifications.
  • Register and clean up all DOM, app, and interval listeners using the provided register* helpers so the plugin unloads safely.

UX & copy guidelines (for UI text, commands, settings)

  • Prefer sentence case for headings, buttons, and titles.
  • Use clear, action-oriented imperatives in step-by-step copy.
  • Use bold to indicate literal UI labels. Prefer "select" for interactions.
  • Use arrow notation for navigation: Settings → Community plugins.
  • Keep in-app strings short, consistent, and free of jargon.

Performance

  • Keep startup light. Defer heavy work until needed.
  • Avoid long-running tasks during onload; use lazy initialization.
  • Batch disk access and avoid excessive vault scans.
  • Debounce/throttle expensive operations in response to file system events.

Coding conventions

  • TypeScript with "strict": true preferred.
  • Keep main.ts minimal: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
  • Split large files: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
  • Use clear module boundaries: Each file should have a single, well-defined responsibility.
  • Bundle everything into main.js (no unbundled runtime deps).
  • Avoid Node/Electron APIs if you want mobile compatibility; set isDesktopOnly accordingly.
  • Prefer async/await over promise chains; handle errors gracefully.

Mobile

  • Where feasible, test on iOS and Android.
  • Don't assume desktop-only behavior unless isDesktopOnly is true.
  • Avoid large in-memory structures; be mindful of memory and storage constraints.

Agent do/don't

Do

  • Add commands with stable IDs (don't rename once released).
  • Provide defaults and validation in settings.
  • Write idempotent code paths so reload/unload doesn't leak listeners or intervals.
  • Use this.register* helpers for everything that needs cleanup.

Don't

  • Introduce network calls without an obvious user-facing reason and documentation.
  • Ship features that require cloud services without clear disclosure and explicit opt-in.
  • Store or transmit vault contents unless essential and consented.

Common tasks

Organize code across multiple files

main.ts (minimal, lifecycle only):

import { Plugin } from "obsidian";
import { MySettings, DEFAULT_SETTINGS } from "./settings";
import { registerCommands } from "./commands";

export default class MyPlugin extends Plugin {
  settings: MySettings;

  async onload() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    registerCommands(this);
  }
}

settings.ts:

export interface MySettings {
  enabled: boolean;
  apiKey: string;
}

export const DEFAULT_SETTINGS: MySettings = {
  enabled: true,
  apiKey: "",
};

commands/index.ts:

import { Plugin } from "obsidian";
import { doSomething } from "./my-command";

export function registerCommands(plugin: Plugin) {
  plugin.addCommand({
    id: "do-something",
    name: "Do something",
    callback: () => doSomething(plugin),
  });
}

Add a command

this.addCommand({
  id: "your-command-id",
  name: "Do the thing",
  callback: () => this.doTheThing(),
});

Persist settings

interface MySettings { enabled: boolean }
const DEFAULT_SETTINGS: MySettings = { enabled: true };

async onload() {
  this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
  await this.saveData(this.settings);
}

Register listeners safely

this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
this.registerDomEvent(window, "resize", () => { /* ... */ });
this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));

Troubleshooting

  • 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>/.
  • Build issues: if main.js is missing, run npm run build or npm run dev to compile your TypeScript source code.
  • Commands not appearing: verify addCommand runs after onload and IDs are unique.
  • Settings not persisting: ensure loadData/saveData are awaited and you re-render the UI after changes.
  • Mobile-only issues: confirm you're not using desktop-only APIs; check isDesktopOnly and adjust.

References

Beads Issue Tracker

This project uses bd (beads) for issue tracking. Run bd prime to see full workflow context and commands.

Quick Reference

bd ready              # Find available work
bd show <id>          # View issue details
bd update <id> --claim  # Claim work
bd close <id>         # Complete work

Rules

  • Use bd for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
  • Run bd prime for detailed command reference and session close protocol
  • Use bd remember for persistent knowledge — do NOT use MEMORY.md files
  • The local Beads database is ignored. The portable task state is .beads/issues.jsonl.
  • Husky hooks run .husky/beads-sync to export before commits, import after checkout/merge/rebase, and block pushes when tasks were not exported.
  • If hooks are unavailable, manually run bd bootstrap --yes && bd import .beads/issues.jsonl after pulling, and bd export --no-memories -o .beads/issues.jsonl && git add .beads/issues.jsonl before committing.

Session Completion

When ending a work session:

  1. File Beads issues for remaining work.
  2. Run quality gates if code changed.
  3. Update issue status where appropriate.
  4. Trust the Husky pre-commit hook to export .beads/issues.jsonl; if not committing, export manually.
  5. Commit task/code changes when the user requested a commit.
  6. Pull, rebase, or push only when explicitly requested by the user.