Site config

admin/site.yaml describes your site to Emet: where the content is, what fields each kind of entry has, which shortcodes exist, where uploaded images go and which branches matter. It’s the file the setup wizard writes, and the one you’ll edit most as the site changes shape.

Everything in here is about the site rather than about how you like to work; preferences live in cms.yaml. Note that Emet only reads particular keys from each file, so editor settings in site.yaml do nothing, and neither do preview.styles in cms.yaml.

A complete example#

site:
  name: My Site
  url: https://example.com
  links:
    - label: Production
      url: https://example.com
      branch: main
    - label: Staging
      url: https://staging.example.com
      branch: staging

frontmatter_format: yaml
typographer: true

production_branch: main
working_branch: staging
allowed_branches: [staging, main]
publish:
  merge_method: squash
deploy:
  skip_ci: "[skip ci]"

media:
  folder: static/images
  public_path: /images

languages_layout: directory
languages:
  - code: en
    name: English
    contentDir: content
    default: true

sections:
  blog:
    label: Blog posts
    folder: content/blog
    sort:
      field: date
      direction: desc
    list:
      subtitle: author
    fields:
      - name: title
        type: string
        required: true
      - name: date
        type: datetime
    body:
      shortcodes: true

shortcodes:
  youtube:
    label: YouTube video
    icon: "▶"
    fields:
      - name: id
        type: string
        required: true
    preview: '<iframe src="https://www.youtube.com/embed/{{id}}"></iframe>'
    hugo: '{{< youtube id="{{id}}" >}}'

preview:
  styles:
    - assets/css/main.css
  wrapper: '<article class="post"><h1>{{title}}</h1>{{content}}</article>'
  allowed_origins:
    - https://fonts.googleapis.com
    - https://fonts.gstatic.com

gsc:
  site_url: https://example.com/

socialPreview:
  enabled: true
  platforms: [linkedin, twitter, facebook]

sections, media and languages are big enough to get their own pages: Sections, Media and Languages. The rest is below.

Site#

Key What it does Default
site.name Shown in the header and on the welcome screen. CMS
site.url Your site’s public URL. Used to build the page URL for SEO analysis and social previews, and as the base when the preview resolves ref and relref links. unset
site.links “View on site” buttons in the preview toolbar. See below. one link built from site.url, if set

Each entry in site.links becomes a button in the preview toolbar that opens the current page on that deployment. Emet works out the page’s path from your Hugo config, as Hugo would (see Permalinks), and appends it to the link’s url. Up to three links show as separate buttons; four or more collapse into a dropdown. The same page link is on each entry card’s menu as View on site.

If you don’t list any links but site.url is set, Emet makes one up: a Live button pointing at site.url, checked against production_branch (or the branch you’re editing when there’s no production branch). List your own links to change the label or add staging deployments.

Add a branch to a link and the button starts checking before it opens anything. A draft gets told that Hugo will skip it. An entry that isn’t on that branch yet gets told to publish first. Otherwise Emet looks up the GitHub Actions runs for the branch’s latest commit and shows the result as a coloured dot and a one-word label on the button: building (re-checked every ten seconds), build failed, or live. In the draft, unpublished, building and failed cases the click opens a dialog explaining the situation, with the option to go ahead anyway.

This only works if your site builds with GitHub Actions. Other CI systems don’t report runs to GitHub in a way Emet can see, so leave branch off and the button is just a link.

Content settings#

Key What it does Default
frontmatter_format yaml or toml. The format Emet uses for new entries and for the _index.md it writes when you create a subfolder. Existing files are read in either format regardless. yaml
typographer Set to true if your Hugo config has Goldmark’s typographer extension on (it’s on by default in Hugo), so the preview turns straight quotes and -- into the same curly quotes and dashes the built site will. Emet leaves it off unless you say so. The dev server’s generated config reads your Hugo config and sets it; the web wizard doesn’t. off

Branches and publishing#

These keys turn on the publication workflow, where edits land on a working branch and reach production through a pull request.

Key What it does Default
production_branch The branch your site deploys from. Setting this turns the workflow on. unset
working_branch The branch editors land on after signing in. the repository’s default branch
allowed_branches Branches offered in the branch switcher. The switcher only appears when there are at least two. none
publish.merge_method squash, merge or rebase. squash
deploy.skip_ci Text appended to commit messages for draft saves, image uploads, review-only saves and bulk-created draft translations, so your host doesn’t rebuild for content nobody can see. Change it to match your CI ("[skip netlify]", say) or set it to "" to turn it off. "[skip ci]"

See Sync for exactly when the skip-CI suffix is added.

Google Search Console#

gsc:
  site_url: https://example.com/
  cache_ttl: 3600

site_url has to match your Search Console property exactly, trailing slash included. cache_ttl is how long (in seconds) fetched data is reused before Emet asks Google again. Add enabled: false to switch the integration off without deleting the block.

The config alone isn’t enough: the deployment also needs a Google OAuth client, which is set through environment variables. See Google Search Console.

Social previews#

socialPreview:
  enabled: true
  platforms: [linkedin, twitter, facebook]

Controls the social media cards in the SEO panel. Both keys are optional; by default all three platforms are shown. See Social previews.

Preview#

The preview pane renders your Markdown with your site’s own CSS, so what you see is roughly what you’ll get. The preview block says which CSS and what HTML to wrap the body in.

preview:
  styles:
    - assets/css/main.css
    - static/css/syntax.css
  wrapper: |
    <article class="post">
      <h1>{{title}}</h1>
      {{#date}}<time>{{date}}</time>{{/date}}
      {{#cover}}<img src="{{image:cover}}" alt="">{{/cover}}
      {{content}}
    </article>
  sections:
    docs:
      styles:
        - assets/css/docs.css
      wrapper: '<main class="docs">{{content}}</main>'
  hugo_environment: production
Key What it does Default
styles Repository paths to CSS files. They’re fetched from the repository and injected into the preview, with relative url() references rewritten so fonts and background images still resolve. none
wrapper An HTML template around the rendered body, so your CSS has the structure it expects. none
sections.<key>.styles Extra stylesheets for one section, added to the top-level list. none
sections.<key>.wrapper A wrapper for one section, replacing the top-level one. none
hugo_environment Which config/<environment>/ Hugo overrides to apply when the preview resolves ref and relref links. production
allowed_origins Origins the preview may fetch fonts and @imported stylesheets from, as bare https:// origins. See fonts from other origins. none

The wrapper template is a small Mustache-like language: {{content}} is the rendered body (raw HTML), {{title}} or any other frontmatter key is that value HTML-escaped (dates come out as yyyy-MM-dd), {{image:cover}} is an image field with the same URL handling the preview uses for images in the body, and {{#field}}...{{/field}} renders its contents only when the field is set, or once per item (with {{.}} as the item) when it’s a list.

With no styles and no wrapper, Emet falls back to a plain built-in style. See CSS preview for how to pick the right CSS files.

Note that base_path is prefixed onto styles paths only: a wrapper is an HTML template rather than a path, so it’s used exactly as written even when base_path is set.

Shortcodes#

The shortcodes block tells the visual editor which Hugo shortcodes your site has, what parameters they take and how to show them in the preview. The insert menu also has to be switched on per section with body.shortcodes: true; shortcodes already in a file get their cards either way.

shortcodes:
  figure:
    label: Figure
    icon: "🖼"
    category: Media
    fields:
      - name: src
        type: image
        required: true
      - name: caption
        type: string
    preview: '<figure><img src="{{src}}"><figcaption>{{caption}}</figcaption></figure>'
    hugo: '{{< figure src="{{src}}" caption="{{caption}}" >}}'

  tabs:
    label: Tabs
    children: [tab]
    fields: []
    preview: '<div class="tabs">{{children}}</div>'
    hugo: '{{< tabs >}}{{children}}{{< /tabs >}}'

  tab:
    label: Tab
    parent: tabs
    fields:
      - name: name
        type: string
        required: true
    preview: '<section><h4>{{name}}</h4>{{content}}</section>'
    hugo: '{{< tab name="{{name}}" >}}{{content}}{{< /tab >}}'
Key What it does
label Name in the insert menu and the preview header.
icon An emoji or short string shown next to the label.
category Groups shortcodes under a heading in the insert menu.
fields The shortcode’s parameters, using the same field types as frontmatter. A field’s default is applied when the shortcode is inserted.
preview HTML template rendered in the preview pane.
hugo An example of the shortcode’s syntax. Emet reads it to decide whether the shortcode is paired (needs a closing tag) and whether it takes positional or named parameters, and shows it to the AI as the canonical form.
children Names of shortcodes allowed inside this one. Setting it makes this a container with an Add button for each child.
parent Marks a shortcode as child-only: it’s hidden from the insert menu and can only be added inside its parent.
min_children, max_children A container warns while it has fewer children than the minimum, and disables Add at the maximum.
inline Insert the shortcode as a chip inside the current paragraph rather than as a block card.
allow_text_content Accepted but not read. Text before a container’s first child is preserved regardless.

The preview template is simple substitution: {{name}} for each field, {{content}} for the inner content of a paired shortcode, {{children}} for the rendered children of a container, and {{#if name}}...{{else}}...{{/if}} or {{#unless name}}...{{/unless}} around the optional bits. Positional parameters in your Markdown are matched to fields in the order they’re declared, and placeholders that aren’t filled in are removed. The preview appears in the preview pane and on the chips of shortcodes sitting inside a sentence, never on a block card.

Emet writes a closing tag only for shortcodes it knows to be paired: ones read from the file with a closing tag, ones with a content field, and ones whose hugo example shows a closing tag or {{content}}. Everything else is written as a single tag, which is what Hugo insists on for templates that don’t read .Inner. Both the < and % delimiters are preserved. Block shortcodes has the details.

The wizard generates all of this by reading layouts/shortcodes/, so you’ll rarely write it from scratch. See Shortcodes for how they behave in the editor.

Where next?#

  • Sections for the folder and field definitions that make up most of this file.
  • cms.yaml for the editor, SEO and AI settings that don’t belong here.