🔮
Obsidian Plugin & Theme Best Practices
Guía de referencia · Skill ejecutada el 16 jun 2026
✓ Análisis completado
📋

Guía de patrones para desarrollo seguro en Obsidian

3 áreas críticas analizadas · 6 anti-patrones detectados · Ejemplos de código correcto vs incorrecto

3
Áreas cubiertas
6
Anti-patrones
6
Patrones correctos
Acceso a APIs internas — app.internalPlugins Crítico
❌ Patrón incorrecto
// ❌ CRASH: si el plugin "workspaces" está desactivado o cargando const ws = app.internalPlugins.plugins.workspaces .instance.activeWorkspace; // TypeError: Cannot read properties of undefined (reading 'instance')
✅ Patrón correcto
// ✅ Optional chaining + fallback vacío const ws = app.internalPlugins?.plugins?.workspaces?.instance?.activeWorkspace ?? ""; // Si el plugin no existe o aún carga → string vacío, sin crash
Por qué importa: Obsidian carga plugins en paralelo y no garantiza orden. Un plugin puede estar desactivado por el usuario en cualquier momento. El encadenamiento opcional (?.) y el operador nullish coalescing (??) son tu red de seguridad.
Acceso al archivo activo — workspace.getActiveFile()
❌ Patrón incorrecto
this.registerEvent( app.workspace.on('active-leaf-change', () => { const file = app.workspace.getActiveFile(); processFile(file.path); // ❌ file puede ser null }) );
✅ Patrón correcto
this.registerEvent( app.workspace.on('active-leaf-change', () => { const file = app.workspace.getActiveFile(); if (!file) return; // ✅ Guard clause processFile(file.path); }) );
Parsing de DOM — document.title y .match()
❌ Patrón incorrecto
const version = document.title.match(/v(\d+\.\d+)/)[1]; // ❌ TypeError si match() retorna null
✅ Patrón correcto
const m = document.title.match(/v(\d+\.\d+)/); const version = m?.[1] ?? "unknown"; // ✅ Siempre seguro
Leer el token bajo el cursor del editor Patrón recomendado
// Obtener el texto de la línea actual y la posición del cursor const cursor = editor.getCursor(); const line = editor.getLine(cursor.line); // Extraer la "palabra" bajo el cursor const wordRange = editor.wordAt(cursor); if (!wordRange) return; // ✅ Guard: cursor en espacio vacío const word = editor.getRange(wordRange.from, wordRange.to); console.log(`Token bajo cursor: "${word}"`); // Para acceso al árbol sintáctico (CM6 syntaxTree) import { syntaxTree } from '@codemirror/language'; const tree = syntaxTree(view.state); const node = tree.resolveInner(cursor.from, 1); console.log(`Tipo de nodo: ${node.type.name}`); // e.g. "Link", "Heading", "Code"
  • Todos los accesos a app.internalPlugins usan optional chaining
  • El evento active-leaf-change tiene guard clause para null
  • Los resultados de .match() se verifican antes de acceder por índice
  • Selectores a[href] en Live Preview → migrar a clases CM6
  • editor.wordAt() verificado antes de llamar a getRange()
  • Eventos registrados con registerEvent() para limpieza automática
💡
Para depurar en Obsidian: abre la consola con Ctrl/Cmd+Shift+I, filtra por el nombre de tu plugin y usa app.plugins.plugins["mi-plugin"] para acceder a la instancia en vivo.
🔗
Documentación oficial: https://docs.obsidian.md/Plugins/Getting+started/Build+a+plugin · Referencia CM6: https://codemirror.net/docs/ref/