Export your Hashnode blog posts and drafts to markdown and JSON files with local image storage.
Hashnode-Blog-Exporter.mp4
- π Export published posts and drafts (including unpublished drafts)
- π Export static pages (e.g.,
/privacy-policy) - π Export series/collection metadata
- πΌοΈ Download and store images locally with smart URL handling
- π Output in Markdown and/or JSON formats
- π¨ Beautiful terminal UI with accurate progress tracking
- π§ Customizable output directory
β οΈ Comprehensive error logging for failed operations- π Built with Typer for modern CLI experience
- β 35 unit tests with full mock coverage
python -m venv hn-export-env
source hn-export-env/bin/activate
pip install git+https://github.com/sureshdsk/hn-export.gitgit clone https://github.com/sureshdsk/hn-export.git
cd hn-export
uv sync-
Get your Hashnode API key from https://hashnode.com/settings/developer
-
Set the environment variable:
export HASHNODE_API_KEY=your_api_key_hereOr create a .env file in the directory where you run the command:
HASHNODE_API_KEY=your_api_key_hereExport everything (posts, drafts, static pages, series) from your primary publication:
If installed via pip:
hn-exportIf using the cloned repo with uv:
uv run hn-export# Specify a publication
hn-export --publication blog.example.com
# Custom output directory
hn-export --output-dir my-blog-backup
# Export only posts
hn-export --posts-only
# Export only drafts
hn-export --drafts-only
# Export only series metadata
hn-export --series-only
# Export only markdown (skip JSON)
hn-export --format markdown
# Export only JSON (skip markdown)
hn-export --format json
# Skip image downloads
hn-export --no-images
# Combine options
hn-export --posts-only --format markdown --no-images{domain-name}/
βββ posts/
β βββ markdown/ # Post markdown files
β βββ json/ # Post JSON files
β βββ images/ # Downloaded images
βββ drafts/
β βββ markdown/ # Draft markdown files
β βββ json/ # Draft JSON files
β βββ images/ # Downloaded images
βββ pages/
β βββ markdown/ # Static page markdown files
β βββ json/ # Static page JSON files
β βββ images/ # Downloaded images from static page content
βββ series/
β βββ {series-slug}.md # Series metadata (markdown)
β βββ {series-slug}.json # Series metadata (JSON)
βββ export_errors.log # Error log (if errors occurred)
βββ export_errors.json # Error log in JSON format
- Install dependencies with dev tools:
uv sync --extra dev- Install pre-commit hooks:
uv run pre-commit install- Run pre-commit manually (optional):
uv run pre-commit run --all-filesRuff - Fast Python linter and formatter:
# Lint code
uv run ruff check .
# Fix linting issues
uv run ruff check --fix .
# Format code
uv run ruff format .
# Check formatting without changes
uv run ruff format --check .ty - Fast static type checker from Astral:
uv run ty check src/hn_blog_exporterPre-commit - Runs automatically on git commit:
- Trailing whitespace removal
- End-of-file fixer
- YAML/JSON/TOML validation
- Ruff linting and formatting
- ty type checking
src/hn_blog_exporter/
βββ __init__.py
βββ main.py # CLI interface (Typer)
βββ config.py # Configuration
βββ hashnode_client.py # GraphQL API client
βββ exporter.py # Export logic
βββ image_downloader.py # Image downloading
βββ error_logger.py # Error logging
tests/
βββ conftest.py # Pytest fixtures
βββ test_config.py # Config tests
βββ test_hashnode_client.py # API client tests
βββ test_exporter.py # Export logic tests
βββ test_image_downloader.py # Image download tests
# Install dev dependencies
uv sync --extra dev
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=src/hn_blog_exporter
# Run specific test file
uv run pytest tests/test_config.py
# Run with verbose output
uv run pytest -vIf you get authentication errors:
- Verify your API key is correct in
.env - Ensure the
.envfile is in the project root - Check that your API key has the necessary permissions
The Hashnode API has generous rate limits (20k requests/min for queries). If you hit rate limits, wait a moment and try again.
If images fail to download:
- Errors are logged to
export_errors.login the output directory - Export continues without interruption
- Original URLs are preserved in the markdown
- Check the error log for details (URL, error type, timestamp)
- Use
--no-imagesto skip image downloads entirely
When errors occur during export, two log files are created:
export_errors.log- Human-readable formatexport_errors.json- Machine-readable format
The export summary will show the error count and log file location.
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
