obsidian-best-practices — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited obsidian-best-practices (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill outlines crucial patterns, common pitfalls, and effective debugging strategies for developing within the Obsidian ecosystem.
When interacting with Obsidian's APIs—especially internal or undocumented ones—defensive programming is mandatory.
app.internalPlugins or sub-plugins are fully loaded or exist, as this will crash the plugin load sequence. // Bad: will crash if workspaces plugin is disabled or loading
const ws = app.internalPlugins.plugins.workspaces.instance.activeWorkspace;
// Good: safely traverse the object
const ws =
app.internalPlugins?.plugins?.workspaces?.instance?.activeWorkspace || "";app.workspace.getActiveFile().document.title for version strings), always verify .match() returns an array before accessing properties like .length.In Obsidian's "Live Preview" mode, standard HTML elements (like <a> tags with href attributes) are often replaced by CodeMirror 6 widgets. For example, a Markdown link [text](url) might render as a series of spans (.cm-link, .cm-url) in the DOM, but the .cm-url span may be completely empty of text and lack an href attribute.
element.href or element.innerText) when hovering over editor content. Instead, convert the mouse coordinates to a CodeMirror document position, and read the raw Markdown text from the editor state: // 1. Get the CodeMirror EditorView instance
const leaf = this.app.workspace.getMostRecentLeaf();
const cmView = leaf?.view?.editor?.cm;
if (!cmView) return;
// 2. Convert mouse event coordinates to a document position
const pos = cmView.posAtCoords({ x: event.clientX, y: event.clientY });
if (pos === null) return;
// 3. Extract the surrounding line text from the document state
const lineStart = cmView.state.doc.lineAt(pos).from;
const lineEnd = cmView.state.doc.lineAt(pos).to;
const lineText = cmView.state.doc.sliceString(lineStart, lineEnd);
// 4. Parse the lineText (e.g., with regex) to find the actual URL or markdown tokenminAppVersion in their manifest.json. Without this, modern versions of Obsidian will reject the plugin and it will fail to enable. {
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"minAppVersion": "0.15.0"
}.obsidian/plugins/, the directory name exactly must match the id string inside its manifest.json. If they do not match, Obsidian will silently fail to recognize the plugin. (e.g., if id: "bases-kanban", the folder cannot be named obsidian-bases-kanban).obsidian plugin:reload id=<plugin-id> will return a "Plugin not found" error because the plugin is not yet indexed by the app. You must first ensure it is added to community-plugins.json, and then run a full obsidian reload to restart the vault environment." ") is rarely sufficient. Obsidian's core engine often strips pure whitespace and reverts to its default backup text (like the standard Vault + Filename title).\u200B). This safely tricks the application into rendering a purely blank element that Obsidian respiects as "contentful text".If developing plugins, you do not need to rely on the physical inspector window inside Obsidian. Leverage the obsidian-cli (installed globally):
npm run build && obsidian plugin:reload id=<your-plugin-id> obsidian dev:errorsconsole.log trace logs. Execute code directly: obsidian eval code="app.plugins.enabledPlugins.has('my-plugin')"obsidian eval code="..." to traverse the live component UI tree. For example, looking into app.workspace.getLeavesOfType('view-type')[0].view... allows you to inspect deep configuration settings and runtime variables powering active workspace views in real-time.When creating DOM elements for a plugin (like file trees or recent file views), adhering to the following ensures it feels like native Obsidian UI:
display: none to display: flex (which shifts other elements and causes visual jumpiness), use visibility: hidden and visibility: visible to preserve layout size. If the element should overlap or appear precisely in a corner, leverage position: absolute so it falls out of the document flow and prevents reflowing the flex/grid parents.display: grid is often superior to flexbox. By defining grid-template-columns and grid-template-rows (with the bottom text set to grid-column: 1 / -1; grid-row: 2;), you guarantee exact alignment between the parent and children without messy left margins.var(--text-normal), var(--text-muted), var(--text-faint)var(--font-smallest), var(--font-smaller)var(--nav-item-color-hover), var(--cursor)em units instead of absolute pixels (e.g., width: 0.85em; height: 0.85em) combined with vertical-align: middle or a clean align-items: center flex wrapper to keep the icon exactly vertically aligned with the adjacent text..tree-item-spacer { flex-grow: 1; }).~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.