Skip to main content

Publish Obsidian notes to static site generators with configurable transforms

Project description

Obsidian Publisher

License: MIT Python 3.10+ GitHub last commit GitHub repo size Code style: black

A modular Python library for publishing notes from an Obsidian vault to static site generators like Hugo.

Features

  • Wikilink Conversion: Converts Obsidian [[wikilinks]] to standard markdown links
  • Tag Filtering: Publish only notes with specific tags (e.g., status/evergreen)
  • Tag Transformation: Filter, rename, and reformat tags for your site
  • Image Optimization: Converts images to WebP with PNG fallback
  • Frontmatter Processing: Transform frontmatter for Hugo or other SSGs
  • Orphan Cleanup: Automatically removes unused images

Installation

pip install obsidian-publisher

Or install from source:

git clone https://github.com/akcube/obsidian-publisher.git
cd obsidian-publisher
pip install -e .

Quick Start

  1. Initialize a config file:
obsidian-publish init config.yaml
  1. Edit config.yaml with your vault and site paths:
vault_path: ~/Obsidian/MyVault
output_path: ~/Sites/my-blog
source_dir: Zettelkasten
required_tags:
  - status/evergreen
  1. Publish all eligible notes:
obsidian-publish republish -c config.yaml

CLI Commands

# Republish all eligible notes
obsidian-publish republish -c config.yaml

# Preview without making changes
obsidian-publish republish -c config.yaml --dry-run

# Publish a specific note
obsidian-publish add "My Note Title" -c config.yaml

# Remove a published note
obsidian-publish delete "My Note Title" -c config.yaml

# List all publishable notes
obsidian-publish list-notes -c config.yaml

Configuration

Basic Configuration

# Paths
vault_path: ~/Obsidian/MyVault
output_path: ~/Sites/my-blog
source_dir: Zettelkasten  # Subdirectory within vault

# Output directories
content_dir: content/posts
image_dir: static/images

# Image sources within vault
image_sources:
  - assets
  - attachments

Tag Filtering

# Required tags (note must have at least one)
required_tags:
  - status/evergreen

# Excluded tags (note with any of these is skipped)
excluded_tags:
  - status/draft
  - status/private

Link Transforms

# Relative links: [Title](slug.md)
link_transform:
  type: relative

# Absolute links: [Title](/blog/slug)
link_transform:
  type: absolute
  prefix: /blog

# Hugo ref shortcode: [Title]({{< ref "slug" >}})
link_transform:
  type: hugo_ref

Tag Transforms

# Filter tags by prefix and change separator
tag_transform:
  prefixes:
    - domain
    - type
  replace_separator:
    - "/"
    - "-"
# Result: domain/cs/algo → domain-cs-algo

Frontmatter Settings

frontmatter:
  hugo: true
  author: Your Name

Image Optimization

optimize_images: true
max_image_width: 1920
webp_quality: 85
image_path_prefix: /images

Python API

Basic Usage

from pathlib import Path
from obsidian_publisher.core.publisher import Publisher, PublisherConfig

config = PublisherConfig(
    vault_path=Path("~/Obsidian/MyVault"),
    output_path=Path("~/Sites/my-blog"),
    source_dir="Zettelkasten",
    required_tags=["status/evergreen"],
)

publisher = Publisher(config)
result = publisher.republish()

print(f"Published: {result.published}")
print(f"Failed: {result.failed}")

Custom Transforms

from obsidian_publisher.transforms.links import absolute_link
from obsidian_publisher.transforms.tags import filter_by_prefix, replace_separator, sort_tags, compose
from obsidian_publisher.transforms.frontmatter import hugo_frontmatter

# Create custom transforms
link_transform = absolute_link("/blog")
tag_transform = compose(
    filter_by_prefix("domain", "type"),
    replace_separator("/", "-"),
    sort_tags(),  # Sort tags alphabetically
)
frontmatter_transform = hugo_frontmatter("Author Name")

publisher = Publisher(
    config,
    link_transform=link_transform,
    tag_transform=tag_transform,
    frontmatter_transform=frontmatter_transform,
)

Architecture

The library is designed with modularity in mind:

obsidian_publisher/
├── core/
│   ├── discovery.py    # VaultDiscovery - finds publishable notes
│   ├── processor.py    # ContentProcessor - transforms content
│   ├── publisher.py    # Publisher - orchestrates everything
│   └── models.py       # Data models
├── transforms/
│   ├── links.py        # Link transform factories
│   ├── tags.py         # Tag transform factories
│   └── frontmatter.py  # Frontmatter transform factories
├── images/
│   └── optimizer.py    # ImageOptimizer - WebP conversion
└── cli/
    └── main.py         # Click CLI

Transform Pattern

All transforms are factory functions that return callables:

# Link transform: (title, slug) -> markdown_link
def absolute_link(prefix: str = "") -> LinkTransform:
    def transform(title: str, slug: str) -> str:
        return f"[{title}]({prefix}/{slug})"
    return transform

# Tag transform: (tags) -> tags
def filter_by_prefix(*prefixes: str) -> TagTransform:
    def transform(tags: List[str]) -> List[str]:
        return [t for t in tags if any(t.startswith(p) for p in prefixes)]
    return transform

def sort_tags() -> TagTransform:
    return lambda tags: sorted(tags)

Development

# Clone and install dev dependencies
git clone https://github.com/akcube/obsidian-publisher.git
cd obsidian-publisher
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"

# Run tests
pytest

# Run with coverage
pytest --cov=obsidian_publisher

License

MIT License

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_publisher-0.1.0.tar.gz (31.5 kB view details)

Uploaded Source

Built Distribution

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

obsidian_publisher-0.1.0-py3-none-any.whl (24.8 kB view details)

Uploaded Python 3

File details

Details for the file obsidian_publisher-0.1.0.tar.gz.

File metadata

  • Download URL: obsidian_publisher-0.1.0.tar.gz
  • Upload date:
  • Size: 31.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for obsidian_publisher-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1d06075227420cfd3c9574fbe029130b873f3bb67da08a38809b535ae9fa43f5
MD5 63ab5f361e68e62ef9f3f89063d49477
BLAKE2b-256 c781a8fb16d3edbe039aeeab913f4b964a87755ca31240627ae6cdfe30d595d4

See more details on using hashes here.

File details

Details for the file obsidian_publisher-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for obsidian_publisher-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 20d7ee022cf87d9d4bae92175f9e9058ee43c626bb336d4fa7119dbb68f08a2a
MD5 e1e582b62742daf7a71b5719adbc59d2
BLAKE2b-256 fbbd8d433ca56bb8bbc6f9364fe5822a3e2848d10d7df51cdb7ee67f2c1c6fcc

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