A single-binary static site generator for technical documentation, written in Go. Tryck is Swedish for "print".
Documentation: https://tryck.pinta.land/.
- Plain Markdown plus
:::directives: admonitions, collapsible details and tabs that work without JavaScript. - Clear errors: every problem is reported as
file:line:col: severity[code]: message, and the build reports all of them at once. - Correct docs output: link and anchor checking,
.mdlinks rewritten to page URLs, clean URLs, sitemap, feeds,llms.txt, search, redirects, and subpath deploys such as GitHub Pages project sites. - Fast: parallel parsing and rendering; about 80 ms for 1,000 pages on a laptop.
- Themes: configure the built-in theme (accent, logo, light/dark, header links, footer, extra CSS) or package your own in a theme folder.
- Extensible without a build step: custom
:::directives from templates and plugins in JavaScript, run by the stock binary. Go plugins for everything else. - Math, diagrams and images: TeX formulas rendered to MathML at build time, mermaid diagrams, and images that get their size, lazy loading and smaller copies for a
srcset. - Sharing and AI tools: Open Graph and Twitter card tags, Atom/RSS feeds, and an
llms.txtwith Markdown copies of every page. - Versioned docs: older releases under
/v1/,/v2/next to the current docs, with a version picker;tryck snapshotfreezes a release. - Reader extras: copy buttons on code blocks, heading anchors, "Edit this page" links and "Last updated" dates from git.
- Live reload:
tryck devrebuilds in memory, reloads the browser and shows build errors as an overlay. - No runtime dependencies: one static binary, no Node or Python in CI.
tryck init my-docs
cd my-docs
tryck dev # http://127.0.0.1:8080/
tryck build # writes dist/Install with Homebrew on macOS (brew install pinta365/tap/tryck) or Scoop on Windows (scoop bucket add pinta365 https://github.com/Pinta365/scoop-bucket, then scoop install tryck), download a binary for Linux, macOS or Windows from the releases page, or build from source with Go 1.26 or later:
go install github.com/Pinta365/tryck/cmd/tryck@latesttryck build [--config tryck.config.yaml] [--output ./dist] [--drafts] [--base-url URL] [--workers N] [--strict] [--verbose]
tryck dev [--config tryck.config.yaml] [--port 8080] [--host 127.0.0.1] [--open] [--verbose]
tryck init [dir]
tryck version
Exit codes: 0 success, 1 build errors (or warnings with --strict), 2 usage or config errors. CLI flags override the config file, which overrides built-in defaults.
content/
├── index.md → /
└── 01-guide/
├── _index.md → /guide/ (section title and weight)
├── 01-install.md → /guide/install/
└── diagram.png → /guide/diagram.png
- Numeric prefixes order pages and are dropped from URLs.
weightin front matter wins over them. - Link to other pages by their Markdown file, relative to the current file:
[Install](01-install.md#download). - Pages with
draft: trueappear intryck devwith a banner and are left out oftryck buildunless you pass--drafts.
Directives:
:::note[Optional title]
Any **Markdown**.
:::
:::tabs{group=lang}
@tab Go
```go
x := 1
```
@tab TypeScript
```ts
const x = 1;
```
:::The built-in theme is configured in tryck.config.yaml; most sites need nothing else:
theme:
accent: "#0b7a5a" # links, active nav, tabs
logo: /img/logo.svg # from static/
colorScheme: auto # auto (follows the system, with a toggle), light or dark
nav: # header links
- title: GitHub
url: https://github.com/you/project
footer: "© 2026 You. Built with [tryck](https://github.com/Pinta365/tryck)."
css: [/css/custom.css] # extra stylesheets, loaded lastIts styles live in /css/tryck.css and use CSS variables (--fg, --bg, --font, --accent, ...), so a stylesheet in theme.css can restyle the site without touching templates.
Theme folders package layouts, static files and defaults for reuse. Point theme.path at one:
themes/acme/
├── theme.yaml # defaults for the theme section (accent, logo, footer, params, ...)
├── layouts/ # replaces built-in layouts with the same name
└── static/ # copied to the output root
Files are looked up in the site first, then the theme, then the built-in theme: layouts/partials/footer.html in your site beats the theme's, which beats tryck's. Settings in tryck.config.yaml win over theme.yaml key by key, except css: the site's stylesheets are added after the theme's. Custom values for a theme's templates go in theme.params and are available as .Theme.Params.
Layouts are Go html/template files. To add analytics or other tags to every page, create layouts/partials/head-end.html or body-end.html; both are empty by default. See the built-in set in internal/render/layouts. Templates get .Site, .Page, .Content, .Nav, .TOC, .Prev, .Next and .Theme, and the functions relURL, absURL, dateFormat, markdownify, slugify, partial, default, dict and jsonify.
A template in layouts/directives/ adds a directive: layouts/directives/card.html renders :::card[Title]{href="guide/setup.md"} blocks, with the title, attributes and rendered body as template data; href and src are rewritten and checked like Markdown links. No code or build step. See Custom directives.
A JavaScript file in plugins/ is a plugin, run by the stock binary with nothing to install. Scripts have the same hooks as Go plugins: add data to pages, change the rendered HTML, write extra files and report diagnostics. See Script plugins.
// plugins/todo.js: warn about unfinished pages
function onPageParsed(page) {
page.source.split("\n").forEach((text, i) => {
if (text.includes("TODO")) page.report("warning", "unfinished section", i + 1);
});
}Plugins are compiled in and hook into the build phases through the public API in pkg/plugin. The built-ins use the same API:
| Plugin | Config key | Output |
|---|---|---|
| toc | defaults.toc, defaults.tocDepth |
Table of contents per page |
| readingTime | plugins.readingTime |
.Page.Data.readingTime in minutes |
| syntaxHighlighting | plugins.syntaxHighlighting |
chroma highlighting with CSS classes; theme for light and darkTheme for dark mode, in /css/chroma.css |
| linkCheck | plugins.linkCheck |
Broken link and anchor diagnostics |
| sitemap | plugins.sitemap |
/sitemap.xml |
| search | plugins.search |
A search box in the header, and /search-index.json |
| redirects | A redirect page for each aliases entry |
|
| feed | plugins.feed |
Atom or RSS feeds of dated pages, /feed.xml by default |
| llmsTxt | plugins.llmsTxt |
/llms.txt and a Markdown copy of each page for AI tools; /llms-full.txt with full: true |
| math | plugins.math |
$...$, $$...$$ and ```math formulas rendered to MathML at build time with KaTeX, built in |
| social | plugins.social |
Open Graph and Twitter card tags for link previews, with a site or per-page image |
| mermaid | plugins.mermaid |
```mermaid blocks drawn as diagrams; the script loads from url (jsDelivr by default) only on pages that use it |
A plugin is a Go type implementing plugin.Plugin plus any of the phase interfaces in pkg/plugin. Custom binaries compile plugins in and hand them to tryck.Main:
package main
import (
"github.com/Pinta365/tryck/pkg/plugin"
"github.com/Pinta365/tryck/pkg/tryck"
)
type Hello struct{ greeting string }
func (*Hello) Name() string { return "hello" }
// Declare the keys of the "plugins.hello" section, so a typo warns.
func (*Hello) ConfigKeys() []string { return []string{"greeting"} }
func (h *Hello) Init(cfg map[string]any, _ plugin.SiteInfo) error {
h.greeting = "Hello"
if v, ok := cfg["greeting"].(string); ok {
h.greeting = v
}
return nil
}
func (h *Hello) OnPageParsed(p *plugin.Page) error {
p.Data["greeting"] = h.greeting
return nil
}
func main() { tryck.Main(&Hello{}) }Build it with go build, for example go build ./examples/plugin-hello. See examples/plugin-hello for a complete, tested plugin, and Using plugins for setting up a site with a custom binary. ConfigKeys is optional; declaring it turns typos in the plugin's section into config.unknownKey warnings.
tryck init includes .github/workflows/pages.yml, which downloads a release binary and deploys to GitHub Pages with the right --base-url. For Cloudflare Pages, use this build command with the output directory dist:
curl -sSL https://github.com/Pinta365/tryck/releases/latest/download/tryck_linux_amd64.tar.gz | tar xz tryck && ./tryck buildgo test -race ./... # all tests
TRYCK_UPDATE=1 go test ./internal/build # accept new golden output in testdata/sites/*/expected
go test -run '^$' -bench . ./internal/buildMIT. See LICENSE.