---
title: site
description: Build and validate a linked static site from Markdown sources or a Margo config.
language: en
margo:
  actions:
    markdown: true
    pdf: true
---

# `margo site`

## Purpose

`site` builds linked HTML pages from a Markdown directory or a `.yaml` or
`.yml` site config. Directory mode recursively discovers regular `.md` and
`.markdown` files, rewrites valid Markdown links, and validates anchors and
local images. It does not read stdin.

## Input and output

Directory mode requires `--output-dir`. The destination must not already
exist. `--assets local` copies validated assets; `inline` embeds supported
images. On success, the new directory contains page artifacts, required runtime
assets, and `margo-manifest.json`.

The report on stdout contains `margo-site-report/v1`, artifact count, manifest
digest, page records, and an optional policy digest. Build and publication
failures go to stderr.

Config mode takes source, output, assets, site identity, base URL or path,
layout, navigation, locale, theme, and publication settings from the config.
Without `--output-dir`, configured output is resolved beside that file. An
explicit `--output-dir` changes only the publication destination.

### Minimal configured site

`margo schema` currently emits only the `policy` and `document` JSON Schemas;
there is no `site` schema command. A site config is a versioned, closed YAML
contract with `version: 1`. The authoritative v1 field names and validation
are the [`site.Config` definition](https://github.com/araihu/margo/blob/main/site/config.go);
the repository's [`showcase.yaml`](https://github.com/araihu/margo/blob/main/showcase.yaml)
is a complete, larger example.

The following is a complete minimal documentation site. It expects one
Markdown source at `docs/index.md`, a local SVG at `assets/logo.svg` (the same
file can be used for `icon`), and a local JPEG or PNG at
`assets/social.jpg`. The social image must be exactly 1280×640; Margo validates
that dimension, format, and the non-empty `alt` text before publication.

```yaml
version: 1
source: docs
output: dist
assets: local
offline: true
base_path: /docs
site:
  name: Example Docs
  description: A small configured documentation site.
  base_url: https://docs.example.com
  home: index.md
  logo: assets/logo.svg
  icon: assets/logo.svg
  social_image:
    path: assets/social.jpg
    alt: Example Docs preview
layout:
  kind: docs
  default:
    families: [default]
    sidebar: true
    toc: true
    content:
      layout: article
  values:
    family: default
locales:
  default: en
  supported: [en]
navigation:
  mode: file-tree
theme:
  builtin: true
  name: modern
  allow_switch_theme: false
  color_mode: light
```

Create `docs/index.md`, put the local assets in the paths above, and build a
fresh destination:

```sh
mkdir -p docs assets build
cat > docs/index.md <<'MARKDOWN'
---
title: Example Docs
description: A small configured documentation site.
language: en
---

# Example Docs

This page is generated by the configured site contract.
MARKDOWN

margo site ./site.yaml --diagnostics json > build/site-report.json
```

The command writes `dist/index.html`, `dist/sitemap.xml`,
`dist/llms.txt`, and `dist/margo-manifest.json`; the report is
`margo-site-report/v1`. `base_path: /docs` affects public URLs and canonical
metadata, while `output: dist` remains a filesystem path beside `site.yaml`.
Set `base_path: /` for a root deployment. `theme.name: modern` selects the
built-in `modern` theme; `allow_switch_theme` and `color_mode` control its
runtime controls and color family. The accepted `theme.builtin` boolean is
shown in the example for the v1 config shape but is not a theme selector by
itself. A custom theme instead adds a `themes` entry with a local `css_url`
and `token_catalog`, then selects that name. `navigation.mode` is currently
`file-tree`; `locales` must include the default locale in its supported list.
`layout.kind: docs` owns search, sidebar, table of contents, pagination, and
family navigation. Archive, tag, RSS, and Atom generation remain
consumer-owned.

### Page publication actions

Pages can retain their Markdown source and publish a pre-rendered PDF through
frontmatter:

```yaml
margo:
  actions:
    markdown: true
    pdf: true
```

The PDF action also accepts an object when a branded PDF must include the exact
chart-data tables used by `margo pdf --print-chart-data`:

```yaml
margo:
  actions:
    pdf:
      printChartData: true
```

The object form defaults to `pre-rendered`; existing boolean and string forms
remain compatible. Use `pdf: client` for browser printing without publishing a
PDF artifact. See the [`margo pdf` branding guide](../pdf/index.md#corporate-branding)
for the complete branded publication workflow.

### Article publication metadata

Site builds project the document metadata fields `authors`, `publishedAt`,
`modifiedAt`, and `tags` into three stable surfaces: the generated article has
an accessible publication-details header (`address`, labeled `time` elements,
and a tag list). Published and modified timestamps are shown as separate
`Published`/`Updated` values with a visible separator when both are present;
the labels wrap with their values on narrow screens and localize with the
page locale. The initial HTML head contains `article:*` metadata, and each
page record in the site report and `margo-manifest.json` carries the same fields.
This lets a blog
or news consumer build archive and tag indexes from the route records without
duplicating the values in Markdown prose.

For sources that use the common singular names, directory and configured site
builds also accept `author: Name` and `date: YYYY-MM-DD` (or an RFC 3339 value)
as aliases. The canonical names remain preferred when multiple authors or
timestamps are needed:

```markdown
---
title: A calm release
language: en
authors: [Ana Silva]
publishedAt: "2026-08-25T12:00:00Z"
tags: [operations, release]
---
```

Margo does not generate archive, tag, RSS, or Atom pages automatically. Route
records are deterministic and expose enough metadata for a consumer-owned
indexer; ordering, URLs, and publication policy remain the site's
responsibility.

## Examples

```sh
margo site ./docs \
  --output-dir ./build/site-2026-08-20 \
  --assets local \
  --diagnostics json > build/site-report.json
```

```sh
margo site ./site.yaml --diagnostics text > build/site-report.txt
```

## Failures and diagnostics

| Code | Meaning |
| --- | --- |
| `site.output_required` | Directory mode has no `--output-dir` |
| `site.output_exists` | Destination already exists |
| `site.sources_empty` | No public Markdown source was found |
| `site.config_invalid` | YAML or closed config fields are invalid |
| `site.identity_required` | Configured public identity is incomplete |
| `site.link_missing` | A Markdown or generated link target is absent |
| `site.anchor_missing` | A target fragment has no matching heading |
| `site.asset_external` | A site image is not a local site asset |
| `site.asset_outside_root` | An asset escapes the source root |
| `site.output_collision` | Sources map to the same output path |
| `site.artifact_collision` | Generated artifact paths conflict |

Margo builds and validates the complete result in a sibling staging directory,
then publishes by a no-replace rename. A failure exits `1` and leaves an
existing destination untouched.

## Limitations and care

Use a new versioned destination for every build; there is no `--force` for a
site tree. A site build is filesystem publication only. It does not deploy,
upload, tag, or release the generated directory.
