Warning: Winter is in early alpha and so is this documentation. You may find inconsistencies or pieces of code present in twos.dev that have not yet been migrated to Winter.


Go Reference GitHub logo

Winter is the bespoke static website generator that powers twos.dev. It can power your static website as well, either as a CLI tool or Go library. Winter is strongly opinionated to serve my pecularities around building twos.dev.

On the surface, Winter is similar to other static website generators. It can take a directory of files of varying content typesMarkdown, Org, or HTMLapply some templating, and render them to HTML files ready for deployment to a static web server.

Winter is different because of its unique design goals.

Winter's design goals are to allow content to be both easy to edit and hard to break. These goals work against each other by default, so Winter splits content into two modes: warm and cold.

Warm content should be synchronized into the project directory from an outside tool of your choice. Generally this is a writing program such as iA Writer hooked up to a shared or cloud directory; simplistically, it can be a cron job that runs rsync.

Cold content should not be touched by automated tools. This content must be preseved for years or decades, so fewer moving parts is better. When a piece of warm content is stable, it can be "frozen" into cold content using winter freeze.

  1. Installation
  2. Documentation
    1. Directory Structure
    2. Commands
      1. winter init
      2. winter build
      3. winter freeze
    3. Documents
    4. Frontmatter
      1. filename
      2. date
      3. updated
      4. toc
      5. type
      6. category
    5. Templates
      1. Document Fields
        1. {{ .Category }}
        2. {{ .Dest }}
        3. {{ .IsDraft }}
        4. {{ .IsPost }}
        5. {{ .Shortname }}
        6. {{ .Title }}
        7. {{ .CreatedAt }}
        8. {{ .UpdatedAt }}
      2. Functions
        1. posts
        2. archives
        3. img
        4. video


1# CLI
2go install twos.dev/winter@latest
3winter --help
5# Go library
6go get -u twos.dev/winter@latest


This section details the winter CLI documentation. For Go library documentation, see pkg.go.dev/twos.dev/winter.

Directory Structure


Pass --help to any command to see detailed usage and behavior.

winter init

Usage: winter init

Initialize the current directory for use with Winter. The Winter directory structure detailed above will be created, and default starting templates will be populated so that you have a working index.html listing posts.

winter build

Usage: winter build [--serve] [--source directory]

Build all source content into dist. When --serve is passed, a fileserver is stood up afterwards pointing to dist, content is continually rebuilt as it changes, and the browser automatically refreshes.

Winter always builds text content from src/cold and src/warm, gallery content from src/img, and templates from src/template. If --source is specified, Winter will also build text content from that file or directory. --source can be specified any number of times.

winter freeze

Usage: winter freeze shortname...

Freeze all arguments, specified by shortname. This moves the files from src/warm to src/cold and reflects the change in Git.


A document is an HTML, Markdown, or Org file with optional frontmatter. The first level 1 heading (<h1> in HTML, # in Markdown, * in Org) will be used as the document title.

This is an example document called example.md:

2date: 2022-07-07
3filename: example.html
4type: post
7# My Example Document
9This is an example document.


Frontmatter for HTML and Markdown documents is specified in YAML. Frontmatter for Org files is specified using Org keywords of equivalent names (in whatever case you choose). All fields are optional.

The available frontmatter fields for HTML and Markdown are:

1filename: example.html
2date: 2022-07-07
3updated: 2022-11-10
4category: arbitrary string
5toc: true|false
6type: post|page|draft

The same fields are available in Org files:

1#+FILENAME: example.html
2#+DATE: 2022-07-07
3#+UPDATED: 2022-11-10
4#+CATEGORY: arbitrary string
5#+TOC: true|false
6#+TYPE: post|page|draft

See below for details of each.


Filename specifies the desired final location of the built file in dist. This must end in .html no matter the source document type and must not be in a subdirectory. Winter enforces this because if you later move off Winter, web paths that end in .html and do not use subdirectories will be the easiest to migrate.

When not set the filename of the source document is used, with any extensions replaced with .html. For example, envy.html.tmpl and envy.md would both become envy.html (though if two source files would produce the same destination file, Winter will error).

A document's shortname is defined as its extensionless filename. The shortname is accessible to templates using the {{ .Shortname }} template variable. The filename is not accessible to templates, but can be calculated by appending .html to the shortname.


Date is the publish date of the document written as YYYY-MM-DD. It is available to templates as a Go time.Time using {{ .CreatedAt }}.

When not set the document will not have a publish date attached to it.


Updated is the date the document was last meaningfully updated, if any, written as YYYY-MM-DD. It is available to templates as a Go time.Time using {{ .UpdatedAt }}.

When not set the document will not have an update date attached to it.


Whether to render a table of contents (default false). If true, the table of contents will be rendered just before the first level 2 header (<h2> in HTML, ## in Markdown, ** in Org) and will list all level 2, 3, and 4 headers. See the top of this page for an example.


The kind of document. Possible values are post, page, draft.

post documents are programmatically included in template functions. page and draft documents have no programmatic action taken on them and will not be discoverable unless linked to.

The default template set provided by winter init gives each a slightly different visual treatment:

Beyond this, the types are only different in semantics.


The category of the document. Accepts any string. This is exposed to templates via the {{ .Category }} field.

The default template set provided by winter init gives a minor visual treatment to the listing and display of documents with categories.

Otherwise, the category is semantic. There is no programmatic access to documents by category.


Any file in ./src can be templated using the format expressed in the text/template Go library.

Document Fields

The following fields are available to templates rendering documents.

{{ .Category }}

Type: string

The value specified by the frontmatter category field. This is an arbitrary string.

{{ .Dest }}

Type: string

The filesystem path of the document after having been rendered, relative to the web root.

{{ .IsDraft }}

Type: bool

Whether the document is a draft (i.e. has frontmatter specifying type: draft).

{{ .IsPost }}

Type: bool

Whether the document is a post (i.e. has frontmatter specifying type: post).

{{ .Shortname }}

Type: string

The shortname of the document. This is the extensionless filename frontmatter attribute if set, or the source document's extensionless filename otherwise.

{{ .Title }}

Type: string

The value of the document's first level 1 heading (<h1> in HTML, # in Markdown, * in Org).

{{ .CreatedAt }}

Type: time.Time

The publish date of the document as specified by the frontmatter date attribute.

This value can be formatted using Go's func (time.Time) Format function:

1{{ .CreatedAt.Format "2006 January" }} <!-- Renders 2022 July  -->
2{{ .CreatedAt.Format "2006-01-02" }}   <!-- Renders 2022-07-08 -->

Use {{ .CreatedAt.IsZero }} to see if the date was not set. You can use this to hide unset dates:

1{{ if not .CreatedAt.IsZero }}
2  published {{ .CreatedAt.Format "2006 January" }}
3{{ end }}
{{ .UpdatedAt }}

Type: time.Time

The date the document was last meaningfully updated, as specified by the frontmatter updated attribute.

This value behaves identically to {{ .CreatedAt }}. For details on dealing with it, see that documentation.



Usage: {{ range posts }} ... {{ end }}

Returns a list of all documents with type post, from most to least recent.

See Document Fields for a list of fields available to documents.


Usage: {{ range archives }}{{ .Year }}: {{ range .Documents }} ... {{ end }}{{ end }}

Returns a list of archive types ordered from most to least recent. An archive has two fields, .Year (integer) and .Documents (array of documents). This allows you to display posts sectioned by year.

See Document Fields for a list of fields available to documents.


Alias: imgs

Usage: {{ img[s] <caption> <imageshortname alttext>... }}

Render an image or images with a single caption. Image files must be present in this format:


For example, to render one image with a caption and alt text:

1<!-- mypage.md -->
3{{ img
4   "A caption."
5   "test1"
6   "Descriptive alt text of what the image is of, for assistive tech"


Descriptive alt text of what the image is of, for assistive tech
A caption.

In this example, the image file must hold one or more of these forms:

If -light and/or -dark variants exist, they will be used when the user is in the respective dark mode setting.

Any number of images can be rendered together with one caption beneath the group by passing multiple images and alt texts. They will appear next to each other when the page width allows it, or stacked vertically otherwise.

1{{ imgs
2   "A pair of images."
3   "test1"
4   "Descriptive text of test1"
5   "test1"
6   "Descriptive text of test2"


Descriptive text of imagename1 Descriptive text of imagename2
A pair of images.


Alias: videos

Usage: {{ video[s] <caption> <videoshortname alttext>... }}

Behaves exactly as img but searches for mp4/mov files instead and renders them in <video> tags.

Note that most browsers do not currently support varying a video source by light/dark mode setting, so the wrong variant may be displayed.