Publish Obsidian notes to static site generators with configurable transforms
Project description
Obsidian Publisher
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
- Initialize a config file:
obsidian-publish init config.yaml
- Edit
config.yamlwith your vault and site paths:
vault_path: ~/Obsidian/MyVault
output_path: ~/Sites/my-blog
source_dir: Zettelkasten
required_tags:
- status/evergreen
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d06075227420cfd3c9574fbe029130b873f3bb67da08a38809b535ae9fa43f5
|
|
| MD5 |
63ab5f361e68e62ef9f3f89063d49477
|
|
| BLAKE2b-256 |
c781a8fb16d3edbe039aeeab913f4b964a87755ca31240627ae6cdfe30d595d4
|
File details
Details for the file obsidian_publisher-0.1.0-py3-none-any.whl.
File metadata
- Download URL: obsidian_publisher-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20d7ee022cf87d9d4bae92175f9e9058ee43c626bb336d4fa7119dbb68f08a2a
|
|
| MD5 |
e1e582b62742daf7a71b5719adbc59d2
|
|
| BLAKE2b-256 |
fbbd8d433ca56bb8bbc6f9364fe5822a3e2848d10d7df51cdb7ee67f2c1c6fcc
|