Angle Guard
approvedby Yuriy Vereshchagin
Captures and wraps HTML tags in pasted text so they don't break the note's formatting. - This plugin has not been manually reviewed by Obsidian staff.
Angle Guard
Captures and wraps HTML tags in pasted text so they don't break the note's formatting.
Paste a paragraph that contains <name> somewhere in the middle, and everything below it stops rendering. Headings go flat, lists lose their bullets, callouts unroll. The source still holds every character, but the note no longer displays it.
Why it happens
Markdown allows raw HTML, and a tag name can be any identifier — it does not have to be a real element. So <name> is read as an opening HTML tag. The renderer passes it to the browser, the browser opens an element that is never closed, and each sibling after it is pulled into that element.
This is not a fault in Obsidian. It is Markdown following its specification, applied to text that was never meant to be markup.
What it does
On paste, it finds tag-shaped text and moves it where the renderer will not read it as markup. The characters are left unchanged.
| Situation | Result |
|---|---|
| A tag inside a sentence | Wrapped in a code span: `<name>` |
| A tag on its own line | Placed in a code block |
| Several lines of HTML | The whole run is placed in one code block |
| A full HTML document | One code block, not one edit per tag |
It leaves the following alone:
- A bare
<in prose, as ina < b. - Anything already inside a code span, code fence, or YAML frontmatter.
- Autolinks such as
<https://example.com>and<me@example.com>. - Inline and block math, and wikilinks.
- Text already escaped by hand as
\<name>. - Tags on the ignore list —
br,img,hr,sup,sub,u,mark,kbdby default.
Where text is copied from
Where the text is copied from changes what lands on the clipboard.
Copying from a Markdown code block inside Obsidian puts the literal <name> on the clipboard as plain text, so it is caught before it is inserted. Copying from a rendered HTML preview — a web page, the reading view, a browser — does not. There, <name> renders as an empty element with no visible text, so the plain-text copy drops it and only the clipboard's HTML flavor still carries it. Obsidian then pastes its own conversion of that HTML, which puts <name> back into the note after the plugin has already looked at the paste.
The plugin handles this by recording the note at paste time and comparing against it once the paste settles, then protecting whatever was inserted. The check retries across a few frames, so it does not depend on the paste being synchronous.
Commands
| Command | What it does |
|---|---|
| Protect raw HTML in current note | Repairs a note that is already broken |
| Protect raw HTML in selection | The same, for a selection. Also in the right-click menu |
| Toggle protection on paste | Turns protection on or off. Also the status bar icon |
Settings
Protect on paste is the master switch, on after install. Ask first turns silent protection into a confirmation prompt. Tell me what was fixed shows a short note after each change. Status bar shortcut adds the </> icon, which is struck through when protection is off.
How much to catch sets the scope. Anything that looks like a tag is the default. Only unknown or unclosed tags leaves correctly paired HTML such as <b>bold</b> as written. Tags to leave alone is your own ignore list. Check pastes from the web covers the HTML-preview case above.
Tags in a sentence chooses between a code span and a backslash escape. Where a line already contains an unpaired backtick, the plugin escapes regardless, because a code span would not survive there. Blocks of HTML puts standalone or multi-line HTML in a code block. Code block label is the language written after the opening fence.
Building from source
npm install
npm run build # type-checks, then writes main.js
npm run lint # the official Obsidian plugin review rules
npm test # 44 unit tests, plus a runtime smoke test
npm run dev # rebuild on change
src/protect.ts holds the protection logic and has no Obsidian dependency, so it is tested on its own. The plugin bundles no third-party code at runtime; it uses only the Obsidian API.
Acknowledgements
- Build setup, release workflow, and
manifest.json/versions.jsonconventions follow the Obsidian sample plugin (MIT). - Code is checked against eslint-plugin-obsidianmd, the official plugin review rules.
- The tag grammar in
src/protect.tsimplements the raw-HTML rules from the CommonMark specification. - This plugin was built using Claude Opus 5.0 and Opus 4.8. The problem was first worked through with the help of DeepSeek and Poolside, whose design discussions are recorded in the project notes. The angle-bracket rendering behaviour it addresses was identified in day-to-day use.
License
MIT.
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.