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 |
Site links#
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: 3600site_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.