Most features are opt-in. Enable only what you need in your blog's YAML configuration.
Posts are written in Markdown with optional YAML front matter.
GoBlog-specific Markdown features:
- Syntax highlighting in code blocks
- Automatic link detection
- Highlight text using
==marked text==(renders as<mark>) - Image optimization (if enabled) generates optimized variants automatically
Front matter uses YAML syntax and can be delimited by ---, +++, or any repeated character (e.g., xxx).
Example:
---
title: My First Post
section: posts
tags:
- introduction
status: published
visibility: public
published: 2025-01-15T10:00:00Z
---
This is my first post on GoBlog!| Parameter | Description |
|---|---|
title |
Optional post title |
section |
Which section (posts, notes, etc.) |
status |
published, draft, scheduled |
visibility |
public, unlisted, private |
slug |
Custom URL slug |
path |
Full custom path |
priority |
Higher value = appears first in listings |
published |
Publication date (ISO 8601) |
updated |
Last modification date |
tags |
List of tags |
location |
Geo coordinates (geo:lat,lon) |
gpx |
GPX track content (paste or upload): statistics (distance, time, elevation) displayed on post |
showroute |
Set to false to hide GPX track from the map (statistics remain visible) |
videoplaylist |
HLS .m3u8 stream URL for embedded video player |
images |
Image URLs for the post |
imagealts |
Alt text for images |
comments |
Set to false to disable per post |
reactions |
Set to false to disable per post |
webmention |
Set to false to disable outgoing webmentions for this post |
aliases |
List of old URLs to redirect |
replylink |
URL this post replies to |
likelink |
URL this post likes |
link |
URL this post bookmarks |
translationkey |
Links translated versions of the same post across blogs |
original |
Overrides the canonical URL for a post |
summary |
Custom post summary text (overrides auto-generated) |
audio |
Embeds an HTML audio player with the specified URL |
+<param> |
Prefix with + to append values instead of replacing (e.g., +tags: newtag) |
| [any key] | Custom parameters are preserved and accessible |
published: Visible according to visibility settingdraft: Only visible when logged inscheduled: Auto-publishes at thepublisheddate/time
When you delete a post, GoBlog appends the -deleted suffix to its status (e.g., published-deleted). You don't need to set this manually; use the undelete button in the editor to restore. A deleted timestamp is set; posts with a deleted date older than 7 days are automatically permanently purged.
public: Visible to everyone, in feeds, indexedunlisted: Visible with link, not in feeds or indexesprivate: Only visible when logged in
---
status: scheduled
published: 2025-12-25T09:00:00Z
---GoBlog checks every 30 seconds and publishes scheduled posts when their time comes.
Sections can have custom path templates configured in the Settings UI. Available variables: .Section, .Slug, .Year, .Month, .Day, .BlogPath.
Set postAsHome: true in a blog's YAML config to use a specific post as the homepage instead of the post index. The post at the blog's base path (e.g., / for the default blog) is rendered as a simplified static page (no post parameters, taxonomies, or sharing buttons). Useful for a static "about" page as the landing page. See example-config.yml.
Filter any index page by post parameters using the ?p: query prefix:
/posts?p:tags=golang # posts with tag "golang"
/posts?p:tags=golang&p:tags=indieweb # posts with either tag (OR)
/posts?p:tags # posts that have any tags at all
Multiple values for the same key are combined with OR; different keys with AND. An empty value (?p:key or ?p:key=) matches posts where the parameter exists with a non-empty value.
Append ?cache=0 or ?cache=false to any URL to skip the response cache.
Access at /editor (requires login).
Features:
- Formatting toolbar with buttons for bold, italic, strikethrough, and links
- Undo/Redo buttons
- Live preview via WebSocket: auto-updates as you type, renders HTML in real time
- Auto-save and multi-tab sync via WebSocket: editor state is synchronized across all open browser tabs and persists across server restarts
- Markdown syntax highlighting
- Media upload via file picker
- Template button to pre-fill a configured template
- Geo-location button: query the browser location API to get the current coordinates in the right format (
geo:lat,lon) - GPX helper: merge multiple GPX files, returns minified YAML to paste into post front matter
- File uses tracking: shows which posts reference each uploaded file
- File optimization: manually trigger imgproxy optimization on a file
- View file variants: see all optimized variants generated from an uploaded file
Accessible from the editor page (requires login):
| Path | Shows |
|---|---|
/editor/drafts |
All draft posts |
/editor/private |
All private posts |
/editor/unlisted |
All unlisted posts |
/editor/scheduled |
All scheduled posts |
/editor/deleted |
All deleted posts (with undelete option) |
/editor/links |
All external links across the blog, with usage counts and drill-down per domain |
/editor/files |
All uploaded media files, with options to view their usage or optimized variants, delete them and optimize images using imgproxy |
Each post page includes interactive buttons (individually hideable via Settings UI):
- Share: Modal with multiple share targets (currently Email, Mastodon, Bluesky, LinkedIn, Micro.blog, Reddit, Hacker News, SMS), copy-to-clipboard, and native Web Share API. Text auto-truncated per platform.
- Read Aloud: Browser speech synthesis (Web Speech API) or pre-generated cloud TTS audio (using Google Cloud or Mistral).
- Translate: Links to Google Translate for the post.
Create/update/delete posts via the Micropub protocol.
Endpoints: /micropub (posts), /micropub/media (media uploads)
Authentication: IndieAuth token or app password.
Example (create post):
curl -X POST https://yourblog.com/micropub \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": ["h-entry"],
"properties": {
"content": ["Hello from Micropub!"],
"category": ["test"]
}
}'Compatible with Indigenous, Quill, Micropublish, and other Micropub clients.
Comments are received via Webmention or ActivityPub replies. Enable per blog in YAML (see example-config.yml).
- Admin UI:
/commentto list or delete comments - Disable per post: Add
comments: falseto front matter - Comment form fields:
target,comment,name,website
Comments trigger webmentions to the post's URL, notifying linked pages.
Anonymous visitors submitting comments must solve a numeric image CAPTCHA challenge. This is always active and not configurable. Logged-in users bypass the CAPTCHA. The CAPTCHA uses digit-based images (500x250 pixels) and expires after 10 minutes. Once solved, the session remembers the solution for 24 hours.
Emoji reactions on posts. Enable and configure via the Settings UI. The available emoji reactions are customizable (comma-separated list of actual emoji characters). Default reactions: ❤️, 👍, 🎉, 😂, 😱.
- Disable per post: Add
reactions: falseto front matter
Full-text search using SQLite FTS5. Enable per blog in YAML (see example-config.yml).
When search is enabled, an OpenSearch descriptor is served at /search/opensearch.xml, allowing browsers to add the blog as a search engine.
Gallery of all posts with images. Posts with the images front matter parameter are automatically included. Enable per blog in YAML (see example-config.yml).
Set and update your profile image in the Settings UI. It is automatically used for favicons, the Fediverse actor image, and feed icons.
Interactive map showing posts with locations and GPX tracks. Enable per blog in YAML (see example-config.yml).
- Add location to posts:
location: geo:51.5074,-0.1278 - Add GPX track: Upload via the editor's file picker, or paste GPX content into the
gpxfront matter parameter. Use the GPX helper to merge multiple files.
For privacy, map tiles are proxied through GoBlog. OpenStreetMap tiles are used by default; configure custom tile providers via mapTiles in YAML.
Display blogs you follow via an OPML file. Enable per blog in YAML (see example-config.yml). The OPML file can be hosted externally (e.g., from an RSS reader export). Buttons on the blogroll page allow downloading the OPML and refreshing the cache.
Display a Markdown banner on all pages of a blog. Useful for temporary notices, scheduled maintenance, or important updates. Configure via YAML.
Generate audio versions of posts using Google Cloud TTS or Mistral TTS. Audio files are auto-generated after publishing public posts with a section. Users can play them via the Read Aloud button on post pages.
Configure globally in YAML. Supports Google Cloud TTS (API key) or Mistral TTS (API key + voice ID). Mistral takes precedence when both are configured.
Blog statistics at a configurable path. Displays total posts (with and without dates), yearly breakdowns, and monthly aggregates with word/character counts and words-per-post averages.
Redirects to a random post at a configured path.
Redirects to the date archive page showing posts from this day across all years.
SMTP-based contact form with optional privacy policy checkbox. Accepted fields: name, email, website, message (all optional except message). Configure per blog in YAML.
Date archives are auto-generated at these paths:
| Pattern | Example | Shows |
|---|---|---|
/{year}/ |
/2025/ |
Posts from that year |
/{year}/{month}/ |
/2025/06/ |
Posts from that month |
/{year}/{month}/{day}/ |
/2025/06/08/ |
Posts from that day |
/x/{month}/ |
/x/06/ |
Posts in June across years |
/x/{month}/{day}/ |
/x/06/08/ |
Posts on June 8 across years |
/x/x/{day}/ |
/x/x/08/ |
Posts on the 8th across months and years |
Send notifications via Ntfy, Telegram, or Matrix. Configure globally in YAML. Notifications are sent for new posts, updates, and other events.
Automatically publish new posts to a Telegram channel. Sends a formatted message with the post title and link after publishing a public post. Updates, deletions, and undeletions are also synced.
Per-post control: After a post is sent, the Telegram chat and message IDs are stored in the post parameters (telegramchat, telegrammsg). Editing and republishing updates the existing message instead of sending a new one.
This is separate from notification-based Telegram, which sends push notifications to you; this cross-posts to channels for your audience.
RSS, Atom, and JSON feeds are always available at any index URL by appending the feed extension (e.g., /posts.rss, /posts.atom, /posts.json). Min variants (/posts.min.rss, /posts.min.atom, /posts.min.json) render the same content but omit TTS audio and the comments/interactions link for cleaner output. All feeds send Access-Control-Allow-Origin: *.
Add ?fallbacktitle to feed URLs to generate computed titles for untitled posts.
Custom short domain for compact post URLs. Every post also gets an auto-generated short URL at /s/{hex-id} which 301-redirects to the full post path.
Posts can embed HLS video streams using the videoplaylist front matter parameter. An hls.js video player is rendered on the post page.
By default, media is stored locally in data/media/ and served at /m/.
External storage: You can use BunnyCDN or FTP for media storage instead. See example-config.yml for configuration details.
Custom media domain: See example-config.yml for configuration.
GoBlog can automatically generate optimized image variants (AVIF, JPEG/PNG) using imgproxy, a standalone image processing service.
- On upload, GoBlog saves the original and sends it to imgproxy to generate variants (AVIF, JPEG/PNG at configured sizes)
- Variants are saved alongside the original in media storage
- When rendering a page, GoBlog emits a
<picture>element (AVIF preferred, JPEG/PNG as fallback) with responsivesrcsetwdescriptors - Non-image uploads (audio, video, etc.) are never sent to imgproxy
- Automatically serves next-gen format (AVIF) without manual conversion
<picture>markup is fully cacheable: same HTML for all browsers, noVaryheader- Originals are preserved unchanged
All combinations of formats x widths are generated. Quality and encoding options are set via imgproxy environment variables (see imgproxy configuration).
If you have existing media files that are duplicated (e.g., original uploads alongside compressed copies), the media migration tool can consolidate them. It detects perceptually identical images using perceptual hashing (dhash), groups them together, and replaces all references to compressed copies with the original; then deletes the redundant files.
- Discovery: Scans all media files, computes perceptual hashes (with EXIF-aware and raw variants), and groups files with similar aspect ratios and low hash distance
- Original identification: Picks the original based on EXIF presence, then image dimensions, then file size
- Verification: Displays groups and asks for confirmation (or use
--yesto auto-confirm) - Optimization: Optionally triggers imgproxy optimization on the originals
- Replacement: Updates all post content and parameters to reference the original file instead of compressed copies
- Cleanup: Deletes the redundant compressed files
| Flag | Description |
|---|---|
--yes |
Auto-confirm all groups (skip interactive prompts) |
--dry-run |
Show what would be changed without making any changes |
--discover-only |
Only discover and group files, don't process them |
--threshold N |
Maximum perceptual hash distance to consider files similar (default: 10) |
--limit N |
Process at most N groups |
--preview |
Show image previews using timg (requires timg in PATH) |
- A state cache (
data/media-migrate.json) stores computed hashes to avoid re-processing unchanged files - Only JPEG and PNG files are considered
- Cross-extension duplicates (e.g., original JPG + compressed PNG) are handled correctly
- Files with optimized variants (via imgproxy) are skipped
- The tool is idempotent: safe to run multiple times
Tor Hidden Service
GoBlog can serve as a Tor .onion hidden service when Tor is installed. See advanced.md for configuration and details.
Notify search engines (Bing, Yandex) of new and updated content. GoBlog auto-generates a random 128-character key persisted in the database. The key is served at /<key>.txt for search engine verification. Pings are sent on both post creation and updates.
Restrict all public access: users must be logged in to view any content. Enable via YAML (see example-config.yml).
GoBlog implements the NodeInfo 2.1 protocol for federation discovery. Two endpoints are always served:
/.well-known/nodeinfo-- Discovery endpoint returning a JSON link document/nodeinfo-- NodeInfo 2.1 document reporting software name, version, user/post counts, and supported protocols (ActivityPub, Micropub, Webmention)
GoBlog serves a dynamic robots.txt at /robots.txt. It automatically includes Sitemap directives and blocks all crawlers when Private Mode is enabled. Use the robotstxt YAML config to block specific bots (see example-config.yml). The aibotblock plugin can also contribute blocked bot names dynamically.