Write a plugin¶
A plugin support module is a subclass of ObsidianPlugin that overrides the hooks it needs. It can live in this repository or in a package of its own. Support for core Obsidian syntax is a step, not a plugin, see Contributing.
Example: strip Tasks metadata¶
The Tasks plugin adds emoji metadata such as π
2026-05-10 to task lines. This plugin removes it:
from __future__ import annotations
import re
from obsidian_pdf_exporter.plugins.base import ObsidianPlugin
from obsidian_pdf_exporter.plugins.base import PluginContext
_METADATA = re.compile(r"\s+[β«πΌπ½π
β³π«β
βπ][^\n]*")
class TasksPlugin(ObsidianPlugin):
"""Remove the emoji metadata of the Tasks plugin."""
name = "tasks"
description = "Remove Tasks plugin metadata from task lines"
priority = 60 # after the built-in plugins (5 to 50)
def process_markdown(self, content: str, context: PluginContext) -> str:
return _METADATA.sub("", content)
Register it under the entry point group in the package's pyproject.toml:
[project.entry-points."obsidian_pdf_exporter.obsidian_plugins"]
tasks = "my_ope_plugins.tasks:TasksPlugin"
Install it next to ope and check that it is found:
Hooks¶
All hooks have no-op defaults.
| Hook | Runs | Used by |
|---|---|---|
find_folder_note(folder) |
While the page tree is built. Returns the note of a folder or None. The first plugin with a result wins. |
folder_notes |
process_markdown(content, context) |
On each page and on embedded notes, after the frontmatter is parsed and before embeds, callouts and wiki links. | dataview, meta_bind, excalidraw |
process_document(markdown, context) |
On the assembled Markdown of the whole document, after process_markdown ran on every page. |
citations |
reader_extensions(context), pandoc_args(context) |
When the pandoc call is built, after process_document. Return extra reader extensions and arguments, such as --citeproc. |
citations |
extract_block(body, block_id) |
For ![[Note#^id]]. Returns the selected text or None, before core looks for ^id. |
block_link_plus |
process_svg(svg, context) |
On each SVG image while it is inlined. | drawio |
process_html(html, context) |
On the HTML of the whole document, after the core HTML steps and before the template. | meta_bind |
PluginContext (markdown hooks) has vault_root, page_folder, frontmatter, options and vault_index. vault_root is None when the export runs outside a vault, so a plugin that needs vault-wide data should do nothing then.
DocumentContext (SVG and HTML hooks) has build_dir, options, root, export (title, version, date), template, plugins, redline and log.
Priority¶
priority is an integer, lower runs earlier. The built-ins use 5 (folder_notes), 10 (dataview), 15 (excalidraw), 20 (meta_bind), 30 (block_link_plus), 40 (drawio) and 50 (citations). Pick a value after the plugins whose output you need: a plugin that reads tables made by Dataview runs after 10.
Guarantees¶
- The frontmatter is parsed and removed before
process_markdownruns. - Plugins run before embeds, callouts, wiki links and image captions, so their output passes the rest of the pipeline like written Markdown.
- Plugins also run on embedded notes, with the context of the embedded note.
- Keep code as written. Wrap a rewrite in
outside_codefromobsidian_pdf_exporter.core.code, as Obsidian does not process code.
In this repository¶
A plugin for a broadly used Obsidian plugin can go into obsidian_pdf_exporter/plugins/<name>/ with an __init__.py that exports the class and a thin plugin.py. Put larger parsing code in a module next to it, as Dataview does with engine.py. Add the entry point to the project's pyproject.toml, unit tests in tests/unit/, a page in these docs and an example in examples/.