we like simple. A minimal static site generator for blogs built with Python 3 and Jinja2 https://picopaper.com
Find a file
2026-07-04 09:07:57 +02:00
images ADD Logo configuration for the header #14 2025-10-23 23:44:24 +02:00
items ADD subpaths for pages and create basic index #19 2025-10-27 22:04:42 +01:00
output init 2025-10-09 15:59:47 +02:00
static CHANGE example for static file and add new post for documentation 2025-10-09 19:50:00 +02:00
theme/default ux: FIX parent menu item - correct size 2026-07-04 09:07:57 +02:00
.gitignore init 2025-10-09 15:59:47 +02:00
config.py feat: ADD dropdown field for the menu #28 2026-07-04 08:27:13 +02:00
Dockerfile feat: ADD 'new' function to generate new instance to avoid git clone #1 2026-07-04 08:54:05 +02:00
LICENSE init 2025-10-09 15:59:47 +02:00
picopaper.py feat: ADD 'new' function to generate new instance to avoid git clone #1 2026-07-04 08:54:05 +02:00
README.md feat: ADD optional exif data removable in files #18 2026-07-04 07:59:35 +02:00
requirements.txt feat: ADD optional exif data removable in files #18 2026-07-04 07:59:35 +02:00

picopaper logo

picopaper

A minimal static site generator for blogs built with Python 3 and Jinja2

Show cases:


Features

Available:

  • simple use, easy to understand and modify
  • config file for settings
  • Themes
  • long- and short form content
  • pages
  • static files
  • separate sections (used for categories, tagging, etc) /section/{tag}
  • exclusion of sections from main index (drafts or system notes)
  • HTML anchors for headers
  • list random posts at the bottom
  • optional RSS feeds
  • automatic EXIF/metadata stripping from images (privacy)

Ideas:

  • custom error pages (404, etc)

Not planned:


Usage

Creating a new page or article

Put markdown file into items dir. Important naming convention:

2025-10-03_long_building-a-static-site-generator.md
2025-10-05_short_quick-update_draft.md

Format: YYYY-MM-DD_type_slug[_section].md

  • 2025-10-03 - date of the article
  • _long_ - type of content: long, short, or page
  • building-a-static-site-generator - slug/path for the URL
  • _draft (optional) - section tag for categorization

The first # header is the title of the article - no frontmatter needed.

Types of content:

  • long - only title with link to articles will be displayed in the main index
  • short - title and all content will be displayed
  • page - won't be displayed in the main index at all

Sections

Posts can be tagged with an optional section (e.g., _python, _webdev). Posts with section tags:

  • Appear on the main page (unless excluded in config)
  • Have their own section page at /section/{tag}/

Configuration in config.py:

# Exclude specific sections from main page (they'll still have /section/name/ pages)
EXCLUDE_SECTIONS_FROM_MAIN = ['draft', 'private']

This is useful for draft posts or topic-specific content you want separated from the main index.

Images

Images placed in images/ are copied to output/images/. By default picopaper strips metadata (EXIF, GPS location, XMP, PNG text chunks) from them during the build so nothing like camera model or GPS coordinates is published. The source files in images/ are never modified — only the copies in output/.

JPEGs are re-encoded with their original quantization tables (quality="keep"), so there is no quality loss. Animated images (GIF/WebP) and unsupported formats (e.g. SVG) are copied as-is.

Configuration in config.py:

STRIP_IMAGE_EXIF = True  # set to False to copy images byte-for-byte

Metadata stripping uses Pillow. If Pillow is not installed, picopaper prints a warning and copies images unchanged.


Installation

  1. Create and activate virtual environment:
python3 -m venv venv
source venv/bin/activate  # On Linux/Mac
# or: venv\Scripts\activate  # On Windows
  1. Install dependencies:
pip install -r requirements.txt
  1. Configure your blog in config.py:
BLOG_TITLE = "My Blog"
BLOG_DESCRIPTION = "A simple blog built with picopaper"
THEME = "default"
  1. Generate the site:
venv/bin/python picopaper.py
  1. Serve locally for testing:
cd output
python3 -m http.server 8000

Visit http://localhost:8000

Docker

Build and run with Docker:

# Build the image
docker build -t picopaper .

# Run the container
docker run --rm -v $(pwd):/app picopaper

For Podman (recommended for rootless):

# Build the image
podman build -t picopaper .

# Run with user namespace mapping
podman run --rm --userns=keep-id -v $(pwd):/app picopaper

The generated site will be available in the output/ directory.


Directory Structure

  • items/ - Markdown content files
  • theme/ - Theme directory containing templates and assets
    • default/ - Default theme
      • templates/ - Jinja2 templates
      • assets/ - CSS and static assets
  • images/ - Image files (copied to output)
  • static/ - Static files copied as-is (GPG keys, .well-known, etc.)
  • output/ - Generated site (do not edit)
  • config.py - Blog configuration

Themes

Themes are organized in the theme/ directory. Each theme has its own subdirectory containing:

  • templates/ - Jinja2 template files
  • assets/ - CSS, JavaScript, and other static assets

To switch themes, change the THEME setting in config.py


Notes


Security

For security concerns or reports, please contact via hello a t uphillsecurity d o t com gpg.


License

Apache License

Version 2.0, January 2004

http://www.apache.org/licenses/

  • Commercial use
  • Modification
  • Distribution
  • Patent use
  • Private use
  • Limitations
  • Trademark use
  • Liability
  • Warranty