OB

obsidian-hello-world

Create basic Obsidian plugin functional components like settings, modals, and ribbon icons.

Install

mkdir -p .claude/skills/obsidian-hello-world && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/1137" && unzip -o skill.zip -d .claude/skills/obsidian-hello-world && rm skill.zip

Installs to .claude/skills/obsidian-hello-world

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Create a minimal working Obsidian plugin with commands, settings, modals,
73 charsno explicit “when” trigger
Beginner

Key capabilities

  • Define typed settings for an Obsidian plugin
  • Register commands for the command palette, editor, and conditional execution
  • Add ribbon icons to the Obsidian interface
  • Create modal dialogs for user interaction
  • Display information in the status bar

How it works

The skill demonstrates how to implement core Obsidian plugin features by extending the `Plugin` class, defining settings, registering commands, and interacting with the Obsidian API for UI elements.

Inputs & outputs

You give it
TypeScript code defining plugin features
You get back
A functional Obsidian plugin with commands, settings, and UI elements

When to use obsidian-hello-world

  • Creating a first Obsidian plugin command
  • Building a settings UI panel
  • Adding a ribbon icon to the application interface

About this skill

Obsidian Hello World

Overview

Build a minimal working Obsidian plugin demonstrating the five core building blocks: commands (palette + editor + checkCallback), settings tab with typed config, ribbon icons, modals, and status bar. Every snippet uses real Obsidian API.

Prerequisites

  • Completed obsidian-install-auth setup (symlinked dev vault, npm run dev working)
  • Build pipeline producing main.js from src/main.ts

Instructions

Step 1: Define Typed Settings

// src/main.ts — top of file
import {
  App, Editor, MarkdownView, Modal, Notice,
  Plugin, PluginSettingTab, Setting, TFile
} from 'obsidian';

interface MyPluginSettings {
  greeting: string;
  showRibbon: boolean;
  dateFormat: string;
}

const DEFAULT_SETTINGS: MyPluginSettings = {
  greeting: 'Hello, Obsidian!',
  showRibbon: true,
  dateFormat: 'YYYY-MM-DD',
};

Step 2: Create the Plugin Class with Commands

export default class MyPlugin extends Plugin {
  settings: MyPluginSettings;

  async onload() {
    await this.loadSettings();

    // Ribbon icon — shows greeting as Notice
    if (this.settings.showRibbon) {
      this.addRibbonIcon('sparkles', 'My Plugin: Greet', () => {
        new Notice(this.settings.greeting);
      });
    }

    // Command: show greeting (available everywhere)
    this.addCommand({
      id: 'show-greeting',
      name: 'Show greeting',
      callback: () => new Notice(this.settings.greeting),
    });

    // Command: insert greeting at cursor (editor-only — greyed out when no editor is active)
    this.addCommand({
      id: 'insert-greeting',
      name: 'Insert greeting at cursor',
      editorCallback: (editor: Editor, view: MarkdownView) => {
        editor.replaceSelection(this.settings.greeting);
      },
    });

    // Command: word count with checkCallback (conditionally available)
    this.addCommand({
      id: 'count-words',
      name: 'Count words in current note',
      checkCallback: (checking: boolean) => {
        const view = this.app.workspace.getActiveViewOfType(MarkdownView);
        if (view) {
          if (!checking) {
            const text = view.editor.getValue();
            const count = text.split(/\s+/).filter(Boolean).length;
            new Notice(`Word count: ${count}`);
          }
          return true; // command is available
        }
        return false; // hide from palette when no editor
      },
    });

    // Command: open modal dialog
    this.addCommand({
      id: 'show-greeting-modal',
      name: 'Show greeting modal',
      callback: () => new GreetingModal(this.app, this.settings.greeting).open(),
    });

    // Command: insert today's date
    this.addCommand({
      id: 'insert-date',
      name: 'Insert today\'s date',
      editorCallback: (editor: Editor) => {
        const today = new Date().toISOString().slice(0, 10);
        editor.replaceSelection(today);
      },
    });

    // Status bar — persistent widget at bottom
    const statusEl = this.addStatusBarItem();
    statusEl.setText('Plugin loaded');

    // Update status bar when active file changes
    this.registerEvent(
      this.app.workspace.on('active-leaf-change', () => {
        const view = this.app.workspace.getActiveViewOfType(MarkdownView);
        if (view) {
          const count = view.editor.getValue().split(/\s+/).filter(Boolean).length;
          statusEl.setText(`Words: ${count}`);
        } else {
          statusEl.setText('No editor');
        }
      })
    );

    // Settings tab
    this.addSettingTab(new MySettingTab(this.app, this));
    console.log(`[${this.manifest.id}] loaded`);
  }

  onunload() {
    console.log(`[${this.manifest.id}] unloaded`);
  }

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

  async saveSettings() {
    await this.saveData(this.settings);
  }
}

Step 3: Create Settings Tab

class MySettingTab extends PluginSettingTab {
  plugin: MyPlugin;

  constructor(app: App, plugin: MyPlugin) {
    super(app, plugin);
    this.plugin = plugin;
  }

  display(): void {
    const { containerEl } = this;
    containerEl.empty();

    new Setting(containerEl)
      .setName('Greeting message')
      .setDesc('Text shown by the greet command and ribbon icon.')
      .addText(text => text
        .setPlaceholder('Hello, Obsidian!')
        .setValue(this.plugin.settings.greeting)
        .onChange(async (value) => {
          this.plugin.settings.greeting = value;
          await this.plugin.saveSettings();
        }));

    new Setting(containerEl)
      .setName('Show ribbon icon')
      .setDesc('Toggle the sparkles icon in the left ribbon. Reload plugin to apply.')
      .addToggle(toggle => toggle
        .setValue(this.plugin.settings.showRibbon)
        .onChange(async (value) => {
          this.plugin.settings.showRibbon = value;
          await this.plugin.saveSettings();
        }));

    new Setting(containerEl)
      .setName('Date format')
      .setDesc('Format for the Insert Date command.')
      .addDropdown(dropdown => dropdown
        .addOption('YYYY-MM-DD', '2026-03-22')
        .addOption('MM/DD/YYYY', '03/22/2026')
        .addOption('DD.MM.YYYY', '22.03.2026')
        .setValue(this.plugin.settings.dateFormat)
        .onChange(async (value) => {
          this.plugin.settings.dateFormat = value;
          await this.plugin.saveSettings();
        }));
  }
}

Step 4: Create a Modal

class GreetingModal extends Modal {
  message: string;

  constructor(app: App, message: string) {
    super(app);
    this.message = message;
  }

  onOpen() {
    const { contentEl } = this;
    contentEl.createEl('h2', { text: this.message });
    contentEl.createEl('p', { text: 'This is a modal dialog from your plugin.' });

    // Add a button that does something
    const btn = contentEl.createEl('button', { text: 'Count vault files' });
    btn.addEventListener('click', () => {
      const count = this.app.vault.getMarkdownFiles().length;
      contentEl.createEl('p', { text: `Your vault has ${count} markdown files.` });
    });
  }

  onClose() {
    this.contentEl.empty();
  }
}

Step 5: Build and Test

set -euo pipefail
npm run build

# In Obsidian:
# 1. Settings > Community plugins > Enable your plugin
# 2. Click the sparkles icon in the ribbon
# 3. Ctrl+P > "Show greeting"
# 4. Ctrl+P > "Count words in current note" (open a .md file first)
# 5. Ctrl+P > "Show greeting modal"
# 6. Settings > My Plugin > change the greeting
# 7. Check the status bar at bottom for word count

Step 6: Listen to Vault Events

// Add to onload() — react to file changes
this.registerEvent(
  this.app.workspace.on('file-open', (file: TFile | null) => {
    if (file) {
      console.log(`[${this.manifest.id}] Opened: ${file.path}`);
    }
  })
);

// Track file modifications (debounce for production — see obsidian-rate-limits)
this.registerEvent(
  this.app.vault.on('create', (file) => {
    if (file instanceof TFile) {
      new Notice(`New file: ${file.basename}`);
    }
  })
);

Output

  • Working plugin with:
    • Three command types: callback, editorCallback, checkCallback
    • Settings tab with text, toggle, and dropdown controls
    • Ribbon icon with click handler
    • Modal dialog with interactive button
    • Status bar widget with live word count
    • Event listeners for file-open and file-create

Error Handling

ErrorCauseSolution
Plugin not loadingBuild errors or bad manifestCheck console (Ctrl+Shift+I) for red errors
Settings not savingMissing await on saveDataAlways await this.saveSettings() in onChange
Command greyed outeditorCallback needs active editorOpen a markdown note, or use callback instead
Ribbon icon missingInvalid icon nameUse Lucide icon names: sparkles, file-text, search
Status bar not updatingEvent not registeredWrap in this.registerEvent() for auto-cleanup
Settings reset on restartForgot saveData callloadData returns null on first run — Object.assign handles this

Examples

Available Lucide Icon Names

Obsidian uses Lucide icons. Common examples:

  • file-text, folder, search, settings, star
  • heart, bookmark, tag, link, external-link
  • edit, trash-2, copy, clipboard, check
  • dice, bot, sparkles, wand, calendar
  • bar-chart-2, globe, download, upload

Command Types Summary

TypeWhen AvailableUse Case
callbackAlwaysNon-editor commands (open modal, toggle feature)
editorCallbackWhen editor is activeInsert text, transform selection
checkCallbackConditionallyShow/hide based on context

Register a Hotkey-Ready Command

// Users assign hotkeys in Settings > Hotkeys
this.addCommand({
  id: 'toggle-feature',
  name: 'Toggle my feature',
  callback: () => this.toggleFeature(),
});

Resources

Next Steps

  • Set up hot-reload development: obsidian-local-dev-loop
  • Build advanced UI (views, fuzzy search, context menus): obsidian-core-workflow-b
  • Apply production patterns: obsidian-sdk-patterns

When not to use it

  • When developing a plugin for an application other than Obsidian
  • When the `obsidian-install-auth` setup is not complete

Prerequisites

Completed `obsidian-install-auth` setupBuild pipeline producing `main.js` from `src/main.ts`

Limitations

  • Commands with `editorCallback` are only available when an editor is active
  • Ribbon icons require valid Lucide icon names
  • Settings changes require saving data with `saveData`

How it compares

This provides a structured, minimal example of a working Obsidian plugin, unlike starting from an empty project or a complex template.

Compared to similar skills

obsidian-hello-world side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
obsidian-hello-world (this skill)427dReviewBeginner
agent-implementer-sparc-coder16moReviewIntermediate
scaffold-feature04moReviewIntermediate
coder02moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

Search skills

Search the agent skills registry