Claude-code-plugins-plus obsidian-upgrade-migration
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/obsidian-pack/skills/obsidian-upgrade-migration" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-obsidian-upgrade-migration && rm -rf "$T"
plugins/saas-packs/obsidian-pack/skills/obsidian-upgrade-migration/SKILL.mdObsidian Upgrade Migration
Current State
!
npm list 2>/dev/null | head -20
!cat manifest.json 2>/dev/null || echo 'No manifest.json in cwd'
Overview
Upgrade an Obsidian plugin between versions: migrate persisted settings with version checks, replace deprecated API calls, update
manifest.json minAppVersion, and test across Obsidian releases.
Prerequisites
- Existing Obsidian plugin with source code
- Current
andmanifest.jsonversions.json - Access to Obsidian changelog and breaking changes docs
- Node.js 18+ with npm or pnpm
Instructions
Step 1: Audit Current Version Compatibility
Check what your plugin currently targets and what the user's Obsidian version requires:
# Current plugin target echo "=== manifest.json ===" cat manifest.json | python3 -c " import json, sys m = json.load(sys.stdin) print(f\"Plugin: {m['id']} v{m['version']}\") print(f\"minAppVersion: {m['minAppVersion']}\") " # Current obsidian type definitions echo "=== obsidian package version ===" npm ls obsidian 2>/dev/null || echo "Not found in node_modules" # Check versions.json for version history echo "=== versions.json ===" cat versions.json 2>/dev/null | python3 -m json.tool || echo "No versions.json"
Step 2: Update the Obsidian Type Definitions
# Update to latest obsidian types npm install obsidian@latest --save-dev # Check what changed npm diff obsidian 2>/dev/null | head -100
Then check for TypeScript errors against the new types:
npx tsc --noEmit 2>&1 | head -50
Every error here is a breaking change you need to address.
Step 3: Settings Migration with Version Tracking
Implement a version-aware
loadData() pattern so existing users' settings survive upgrades:
interface PluginSettings { _version: number; // Internal schema version // v1 fields enabled: boolean; // v2 fields (added in plugin v2.0.0) syncInterval: number; // v3 fields (added in plugin v3.0.0) theme: 'light' | 'dark' | 'system'; } const CURRENT_SETTINGS_VERSION = 3; const DEFAULT_SETTINGS: PluginSettings = { _version: CURRENT_SETTINGS_VERSION, enabled: true, syncInterval: 300, theme: 'system', }; async loadSettings(): Promise<PluginSettings> { const raw = await this.loadData(); if (!raw) return { ...DEFAULT_SETTINGS }; const version = raw._version ?? 1; let settings = { ...raw }; // v1 -> v2: add syncInterval if (version < 2) { settings.syncInterval = DEFAULT_SETTINGS.syncInterval; console.log('[your-plugin] Migrated settings v1 -> v2'); } // v2 -> v3: add theme, rename old field if (version < 3) { settings.theme = DEFAULT_SETTINGS.theme; // Rename deprecated field if ('darkMode' in settings) { settings.theme = settings.darkMode ? 'dark' : 'light'; delete settings.darkMode; } console.log('[your-plugin] Migrated settings v2 -> v3'); } settings._version = CURRENT_SETTINGS_VERSION; await this.saveData(settings); // Persist the migration return settings as PluginSettings; }
Step 4: Replace Deprecated API Calls
Common deprecations and their replacements:
Vault API changes:
// DEPRECATED: vault.modify with string path await this.app.vault.modify(filePath, content); // REPLACEMENT: use TFile object const file = this.app.vault.getAbstractFileByPath(filePath); if (file instanceof TFile) { await this.app.vault.modify(file, content); } // DEPRECATED: vault.create returns void in older versions this.app.vault.create(path, content); // REPLACEMENT: returns TFile, handle it const newFile = await this.app.vault.create(path, content);
Event registration changes:
// DEPRECATED: workspace.on('file-open') with old signature this.app.workspace.on('file-open', (file) => { ... }); // REPLACEMENT: use registerEvent for proper cleanup this.registerEvent( this.app.workspace.on('file-open', (file) => { ... }) );
Editor API (CodeMirror 5 to 6 migration):
// DEPRECATED: accessing CM5 editor instance const cm = (editor as any).cm; cm.getValue(); // CM5 // REPLACEMENT: use Obsidian's Editor interface const content = editor.getValue(); const cursor = editor.getCursor(); editor.replaceRange(text, cursor); // For CM6-specific features, use EditorView extension: import { EditorView, ViewPlugin } from '@codemirror/view'; this.registerEditorExtension( ViewPlugin.fromClass(class { constructor(view: EditorView) { // CM6 view access } }) );
FileManager changes:
// DEPRECATED: processFrontMatter sync signature this.app.fileManager.processFrontMatter(file, (fm) => { fm.tags = ['updated']; }); // REPLACEMENT: async signature (Obsidian 1.4+) await this.app.fileManager.processFrontMatter(file, (fm) => { fm.tags = ['updated']; });
Step 5: Update manifest.json
Bump
minAppVersion to the lowest Obsidian version that supports all APIs you use:
{ "id": "your-plugin", "name": "Your Plugin", "version": "3.0.0", "minAppVersion": "1.5.0", "description": "...", "author": "...", "isDesktopOnly": false }
Update
versions.json to map your plugin version to the minimum Obsidian version:
{ "1.0.0": "0.15.0", "2.0.0": "1.0.0", "3.0.0": "1.5.0" }
Step 6: Test Across Obsidian Versions
Build and verify:
# Clean build rm -rf dist node_modules/.cache npm install npm run build # Check for type errors npx tsc --noEmit # Check bundle for leftover deprecated calls grep -rn 'cm\.getValue\|processFrontMatter.*sync\|vault\.modify.*string' src/ || echo "No deprecated patterns found"
Manual testing checklist:
- Install plugin on the
you declared -- confirm it loads without errorsminAppVersion - Install on latest Obsidian -- confirm full functionality
- Test settings migration: copy a
from an older version into the plugin directory, reload, verify settings are preserved and upgradeddata.json - Open Developer Console (Ctrl+Shift+I) and check for deprecation warnings
Step 7: Handle the Release
# Update version in package.json and manifest.json npm version major # or minor/patch # Ensure versions.json includes the new mapping python3 -c " import json v = json.load(open('versions.json')) m = json.load(open('manifest.json')) v[m['version']] = m['minAppVersion'] json.dump(v, open('versions.json', 'w'), indent=2) print(f\"Added {m['version']} -> {m['minAppVersion']}\") " # Build the release artifacts npm run build
Output
- Updated
with correctmanifest.jsonminAppVersion - Updated
with new version mappingversions.json - Settings migration code that handles all previous schema versions
- All deprecated API calls replaced with current equivalents
- Clean
with no type errorstsc --noEmit - Tested on minimum and latest Obsidian versions
Error Handling
| Error | Cause | Solution |
|---|---|---|
| API removed in newer types | Check changelog for replacement API |
| Types not installed | |
| Settings lost after upgrade | No migration logic for jump | Add migration step for each version gap |
at runtime | API exists in types but not in user's Obsidian | Lower or add runtime version check |
| Plugin loads but features missing | Feature flag not migrated | Check settings migration covers all paths |
Examples
Simple version bump: Plugin works fine on new Obsidian, just need to update
minAppVersion. Run Step 1 to audit, Step 5 to update manifest, Step 6 to verify.
CodeMirror 5 to 6 migration: Plugin uses
editor.cm for custom decorations. Replace CM5 Decoration with CM6 EditorView extensions per Step 4. This is the most common large migration.
Settings schema change: Plugin v2 renamed
darkMode: boolean to theme: 'light' | 'dark' | 'system'. Add migration in Step 3 that maps the old boolean to the new enum, preserving user preference.
Resources
- Obsidian Changelog
- Obsidian API Breaking Changes
- Obsidian Developer Docs
- Obsidian Plugin API Types
- CodeMirror 6 Migration
Next Steps
For CI/CD to automate release testing, see
obsidian-ci-integration. For multi-environment testing, see obsidian-multi-env-setup.