Emet is configured by two YAML files that live in your site’s repository, in an admin/ folder alongside content/ and layouts/:
your-hugo-site/
└── admin/
├── site.yaml # the shape of your site: sections, fields, shortcodes, media, languages, branches
└── cms.yaml # how Emet behaves: editor, AI, SEO, local preview serversite.yaml is required and describes your content. cms.yaml is optional and holds preferences; leave it out and you get the defaults. Both are ordinary files in Git, so changes to them get the same history and review as everything else, and there’s no admin database to back up.
Why two files?#
The split is between things that are true of the site and things that are true of how you want to edit it. site.yaml changes when the site changes shape: a new section, a renamed folder, a second language. cms.yaml changes when you want Emet to behave differently: a different default editor, an AI provider, a quieter SEO panel.
Emet reads both and merges them into one config, but it only reads certain keys from each file, so a key in the wrong file is silently ignored. The two reference pages say which is which: site.yaml and cms.yaml.
Where the files come from#
On a GitHub-hosted site, Emet reads them from the branch you picked after signing in. If there’s no admin/site.yaml on that branch, the setup wizard scans the repository and writes both files for you.
Running the dev server against a local site (HUGO_ROOT=/path/to/site npm run dev) does the same thing without the wizard: if admin/site.yaml is missing it’s generated on first start and written to disk, and the files are read from disk from then on.
The desktop app reads admin/ inside whichever folder you open. It doesn’t generate anything, so a site that’s never been opened in Emet needs its config written first, either by hand or by the dev server.
Sites in a subdirectory#
If the Hugo site isn’t at the repository root (a docs/ site inside a code repository, say), Emet needs to know. There are three ways to tell it:
- In the repository picker, open Advanced options and fill in Site subdirectory before choosing the repository.
- Let the setup wizard find it. If it detects more than one Hugo site in the repository, it asks which one you mean and writes the answer to
site.yamlasbase_path. - Put
base_path: docsin anadmin/site.yamlat the repository root.
Once Emet knows the subdirectory it looks for config at docs/admin/site.yaml, and prefixes every Hugo-relative path in your config with docs/: section folders, the media folder, language content directories, preview stylesheets and the data/ directory. Public URL paths such as media.public_path are left alone, since those are URLs on the built site rather than paths in the repository.
If you open a repository without saying where the site is and there’s no admin/site.yaml at the root, Emet checks each top-level folder for a Hugo site with its own admin/site.yaml. If there’s exactly one, it uses that.
What lives in your browser instead#
Not everything is in the repository. Settings that are personal rather than shared stay in your browser’s local storage, under a key per GitHub user (emet-cms-<username>, and emet-gsc-auth-<username> for Search Console tokens):
- API keys you enter yourself in the AI settings, as opposed to site-wide keys in
cms.yaml. - Google Search Console tokens.
- UI preferences: sidebar state, editor mode, panel sizes, the last repository you opened.
- Unsaved edits, so a closed tab doesn’t lose them.
Two people sharing a browser each get their own set, and signing out and back in restores the right one. None of it is synced anywhere, so a new machine starts from scratch.
Authentication#
Sign-in isn’t configured in either file. The GitHub App’s client ID is baked in at build time with VITE_GITHUB_CLIENT_ID, and the token exchange is done by a small Cloudflare Pages Function that holds the client secret. See Environment variables for the variables and GitHub App for creating the app.