Skip to main content

Export Obsidian vaults to PDF with plugin support. Runs locally or in CI.

Project description

logo

Export Obsidian vaults to PDF with plugin support. Runs locally or in CI.

Test, Lint and Build Build and publish to PyPI Package version

Features

  • Folder-tree → single PDF, preserving Obsidian's folder-note convention.
  • Wiki links ([[Page]], [[Page#section|alias]]) resolved to in-document anchors.
  • Embedded images (![[img.png]]) resolved by name against a one-pass vault index, picking the file closest to the referencing page.
  • Obsidian callouts (> [!note], > [!warning], …) styled per type.
  • YAML frontmatter parsed and stripped before rendering.
  • Redline PDFs: tracked-changes diff between two git commits.
  • Pluggable PDF templates (CSS + running header/footer + paged-media @page rules). Two ship in the box: default and redline. See TEMPLATING.md for the full spec.
  • Wide tables auto-rotated to landscape pages.

Plugin support modules translate non-core Obsidian-plugin syntax (Folder Notes, Dataview, Meta Bind, Tasks, …) into plain markdown so pandoc can render it. Third-party plugin support is installable as a separate Python package — see Working with plugins for disabling, options and authoring. Built-in modules:

Name Priority Obsidian plugin What it does
folder_notes 5 LostPaul/obsidian-folder-notes Maps the Folder/Folder.md convention onto the folder during tree building, so a folder note becomes its section's body.
dataview 10 blacksmithgu/obsidian-dataview Renders DQL queries (​```dataview blocks, =this.field inline) as static markdown tables/lists at export time. Skipped when not running inside a git repo (because it needs vault-wide indexing).
meta_bind 20 mProjectsCode/obsidian-meta-bind-plugin Resolves VIEW[…] widgets against the page's frontmatter ({key} substitution, + concatenation, date(YYYY-MM-DD) / datetime(…) formatting). Strips INPUT[…] and BUTTON[…].

Installation

1. Install the CLI

macOS / Linux

curl -LsSf uvx.sh/obsidian-pdf-exporter/install.sh | sh

Windows

powershell -ExecutionPolicy ByPass -c "irm https://uvx.sh/obsidian-pdf-exporter/install.ps1 | iex"

A specific version:

curl -LsSf uvx.sh/obsidian-pdf-exporter/0.1.0/install.sh | sh

2. System dependencies

PDF rendering uses WeasyPrint, which links against GTK/Pango/Cairo at runtime. Pandoc is downloaded automatically on first run.

Easiest path — let the CLI diagnose and install for you:

obsidian-pdf-exporter doctor          # report what is missing
obsidian-pdf-exporter doctor --fix    # run the install command (asks for confirmation)
obsidian-pdf-exporter doctor --fix -y # ditto, no prompt — for CI

doctor only ever runs your platform's own package manager; it never downloads or hosts binaries itself.

OS What --fix runs Notes
Debian / Ubuntu sudo apt-get install -y libpango-1.0-0 libpangoft2-1.0-0 … Other detected managers: dnf, pacman, zypper, apk.
macOS brew install pango Requires Homebrew.
Windows winget install --id tschoonj.GTK3 --accept-source-agreements --accept-package-agreements Installs the upstream GTK3 runtime via winget. Or install Inkscape (bundles GTK).

If your environment is unusual and WeasyPrint cannot find the libs, set OBSIDIAN_PDF_GTK_PATH to an os.pathsep-separated list of directories containing the GTK DLLs/dylibs.

Quick start

# A standard export of a vault folder
obsidian-pdf-exporter export ./MyVault/SpaceA

# Custom title, version, output path
obsidian-pdf-exporter export ./MyVault/SpaceA \
  --title "Quality Manual" --version 3.1.0 \
  --output ./out/quality-manual.pdf

# Redline between two commits (must run inside a git repo)
obsidian-pdf-exporter redline ./MyVault/SpaceA \
  --from-commit v3.0.0 --to-commit HEAD

# Discovery
obsidian-pdf-exporter list-templates
obsidian-pdf-exporter list-plugins
obsidian-pdf-exporter --help

The shorter alias ope is identical to obsidian-pdf-exporter.

Vault layout

The exporter walks a space folder (the ROOT argument) and turns every subfolder into a section. A folder note named after its folder is the section's body:

SpaceA/
├── SpaceA.md            ← root note (becomes the document's intro page)
├── images/              ← attachments folder name is irrelevant
│   └── logo.png
├── Chapter A/
│   ├── Chapter A.md     ← folder note for "Chapter A"
│   ├── img/             ← scoped attachments — picked over top-level on ties
│   │   └── logo.png
│   └── Section 1/
│       └── Section 1.md
└── Chapter B/
    └── Chapter B.md

Three roots are recognised:

  1. Space/Space.md exists → used as the root note.
  2. Space/Space/Space.md → the inner folder is the root, its siblings become children.
  3. Neither → an anonymous root, immediate child folders are sections.

The whole vault is indexed once at the start of an export. Folders that contain no markdown (typical attachment folders, regardless of name) are pruned from the page tree automatically. .git, .obsidian, .vscode and any dotfile folder are always skipped.

Commands

export ROOT

Build a single PDF from a vault folder.

Flag Description Default
ROOT (positional) Vault folder to export. required
-T, --template NAME|PATH Template name (see list-templates) or path to a custom CSS file / template directory (see Templates). default
-o, --output PATH Output PDF path. ./.export/<title>.pdf
-t, --title TEXT Document title. Used in <h1> and the running header. folder name
--subtitle TEXT Document subtitle. ""
-v, --version TEXT Version label (e.g. 3.1.0). Shown in metadata + footer. empty
-d, --date YYYY-MM-DD Document date. today
--include NAME Only include these top-level subfolders. Repeatable. all
--exclude NAME Skip these top-level subfolders. Repeatable. none
-O, --option key=value Free-form option forwarded to plugins / template. Repeatable.
--disable-plugin NAME Skip a plugin (see list-plugins). Repeatable. none
--debug Keep the intermediate build directory next to the PDF. off

Example:

obsidian-pdf-exporter export ./MyVault/SpaceA \
  -T default \
  --title "Quality Manual" --subtitle "Process documentation" \
  --version 3.1.0 --date 2026-05-07 \
  --include "Chapter A" --include "Chapter B" \
  --disable-plugin meta_bind \
  -O brand=acme -O confidentiality=internal

redline ROOT

Generate a tracked-changes PDF between two git commits. Must run inside the git repo that contains ROOT.

Flag Description Default
ROOT (positional) Vault folder inside the repo. required
--from-commit REF Older commit-ish (baseline). required
--to-commit REF Newer commit-ish. HEAD
-T, --template NAME|PATH Template name or path (same as export). redline
-o, --output PATH Output PDF path. ./.export/<title>_redline.pdf
-t, --title TEXT Document title. folder name
--subtitle TEXT Document subtitle. computed from hashes
-O, --option key=value Forwarded to plugins / template. Repeatable.
--disable-plugin NAME Skip a plugin. Repeatable. none
--debug Keep intermediates. off

Markup conventions in the rendered PDF:

  • Word-level changes inside a single block: inline <ins> / <del> styling.
  • Whole-block insertions: green ::: {.redline-ins} block.
  • Whole-block deletions: red ::: {.redline-del} block (headings demoted to bold so deleted sections do not pollute the TOC).

list-templates

Print every known template (built-in, packaged entry-point, user-config directory, runtime-registered) with its source.

list-plugins

Print every installed Obsidian-plugin support module, sorted by priority.

doctor

Diagnose missing native dependencies (GTK/Pango/Cairo, pandoc) and optionally install them via your platform's package manager. See System dependencies for the per-OS commands.

Flag Description Default
--fix Run the recommended install command instead of just reporting it. off
-y, --yes Skip the confirmation prompt (intended for CI). off

Exit codes: 0 if everything is in order, 1 if something is missing (or the user declined the fix), 130 if the user aborted at the confirm prompt.

version

Print the installed version.

Markdown features the exporter understands

Anything pandoc's GFM dialect understands works as-is. Obsidian-specific features are handled too:

Feature What happens
[[Page]], [[Page#section|alias]] Resolved to an in-document anchor if the page exists in the tree, otherwise rendered as italic text.
![[image.png]], ![[image.png|alt text]] Resolved against the vault index by filename. When several files share a name, the one with the deepest common ancestor with the referencing page wins. SVGs are inlined.
> [!note] Title … callouts Converted to fenced divs with a class per type (note, tip, warning, danger, info, important, example, abstract, todo, success/check/done, question/help, failure/bug, quote).
Inline > [!note] text (single line) Converted to an inline <span class="callout …">.
YAML frontmatter Parsed (used by Meta Bind) and stripped before rendering.
HTML comments <!-- … --> Stripped.
Wide tables Auto-detected; rendered on landscape pages.
Image followed by a single italic line The italic line is promoted to the image's caption.

Plugins

The built-in plugin support modules are listed in Features. Operational details below.

Disabling a plugin

obsidian-pdf-exporter export ./vault --disable-plugin dataview

--disable-plugin is repeatable.

Passing options to plugins

obsidian-pdf-exporter export ./vault -O dataview.limit=200 -O brand=acme

-O key=value flags reach every plugin's process_markdown via context.options, and the template via ExportContext.options. Naming convention for plugin-scoped options: <plugin>.<key>.

Installing third-party plugin support

Anyone can publish a Python package that registers an ObsidianPlugin subclass under the obsidian_pdf_exporter.obsidian_plugins entry-point group. After pip install <pkg>, the plugin is available without any change to this project.

To author one, see CONTRIBUTING.md.

Templates

A template decides how the PDF looks: CSS, running header/footer, page size, margins, paged-media @page rules. Templates are independent of the markdown pipeline.

Built-in templates

Name Purpose
default Brand-neutral A4 portrait with title-block heading, running document title in the top-left, version + page counter in the bottom-right, date in the bottom-left.
redline Same chrome as default, plus styled <ins> / <del> and .redline-ins / .redline-del block markup. Used implicitly by the redline command.

Selecting a template

obsidian-pdf-exporter export ./vault --template default
obsidian-pdf-exporter list-templates    # what's installed right now

The value of --template is resolved in this order:

  1. If it is an existing path on disk → loaded as a custom template.
  2. Otherwise → looked up by name in the registry (built-in → packaged entry-point → user-config directory → runtime).

Custom templates without writing Python

Most users only need to swap CSS or add a logo and footer. Three no-Python paths cover that:

A. Drop-in CSS file

obsidian-pdf-exporter export ./vault --template ./brand.css

The filename stem (brand) becomes the template name. CSS only — no header/footer HTML, no extra assets.

B. Template directory with manifest

my-template/
├── template.yaml     # optional; sensible defaults if absent
├── main.css
├── header.html       # optional: appended after <body>
├── page.css          # optional: @page rules + running() positions
└── logo.svg          # optional: copied next to the CSS at render time
# template.yaml — every key is optional
name: legal-pack
description: Legal cover sheet style
css: main.css # default: template.css or sole *.css
assets: [logo.svg] # default: every non-css/yaml/html sibling
running_html: header.html # appended after <body>
page_css: page.css # @page rules etc.
obsidian-pdf-exporter export ./vault --template ./my-template/

C. Named user-config template

Drop the directory into your user-config folder so it appears in list-templates and works by short name from any vault:

~/.config/obsidian-pdf-exporter/templates/
└── legal-pack/
    ├── template.yaml
    └── main.css
obsidian-pdf-exporter export ./vault --template legal-pack

The location can be overridden with OBSIDIAN_PDF_EXPORTER_TEMPLATES_DIR, or moved by setting XDG_CONFIG_HOME.

Custom templates that need Python (markdown / HTML hooks)

If your template must mutate the assembled markdown or pandoc HTML (process_markdown, process_html), or compute decorations from the export context, write a Template subclass and register it under the obsidian_pdf_exporter.templates entry-point group:

# my_brand_pdf/template.py
from importlib import resources
from obsidian_pdf_exporter.templates import Template

class MyBrandTemplate(Template):
    name = "mybrand"
    description = "ACME corporate template"

    def get_css(self) -> str:
        return resources.files(__package__).joinpath("mybrand.css").read_text("utf-8")
# pyproject.toml of my-brand-pdf
[project.entry-points."obsidian_pdf_exporter.templates"]
mybrand = "my_brand_pdf.template:MyBrandTemplate"

Install it (pip install my-brand-pdf or uv pip install -e .) and it appears in list-templates immediately.

The full spec — every hook, the ExportContext object, header/footer injection, asset shipping, runtime registration — is in TEMPLATING.md.

Environment variables

Name Purpose
OBSIDIAN_PDF_GTK_PATH os.pathsep-separated extra directories searched for GTK/Pango/Cairo libs (Windows + macOS).
OBSIDIAN_PDF_EXPORTER_TEMPLATES_DIR Directory scanned for named user templates (default: $XDG_CONFIG_HOME/obsidian-pdf-exporter/templates).

Exit codes

Code Meaning
0 Success.
1 Runtime error during export (see traceback).
2 Bad CLI arguments (e.g. unknown template).

Contributing

See CONTRIBUTING.md for development setup, testing, and PR guidelines.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

obsidian_pdf_exporter-0.0.1.tar.gz (49.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

obsidian_pdf_exporter-0.0.1-py3-none-any.whl (65.7 kB view details)

Uploaded Python 3

File details

Details for the file obsidian_pdf_exporter-0.0.1.tar.gz.

File metadata

  • Download URL: obsidian_pdf_exporter-0.0.1.tar.gz
  • Upload date:
  • Size: 49.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for obsidian_pdf_exporter-0.0.1.tar.gz
Algorithm Hash digest
SHA256 fa989a0d013cf3379ebace26ada1ddeca4fccdbc138f3c66a1b9bc12ad925505
MD5 823126b6ae9b6ef86b33c42c989c4db8
BLAKE2b-256 42f0e3d4d768242422072124466377852fbd4415558a54a2d021e45f7a5dd22e

See more details on using hashes here.

File details

Details for the file obsidian_pdf_exporter-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: obsidian_pdf_exporter-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 65.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for obsidian_pdf_exporter-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 23b8f90c044bba09e6f3d2e95c9aad5cb1d167153c037b46289bdc9cf6f2b75d
MD5 c38617c1e178284226665a9bd9e7e38d
BLAKE2b-256 08d9482851766bbefbb96f5363ad90833914523d6839cde7b6a46941970f2303

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page