Emet speaks to four kinds of provider: OpenAI, Anthropic, Google Gemini and anything with an OpenAI-compatible API (Ollama, LM Studio, Groq, vLLM and so on). You configure them in admin/cms.yaml, and each person using the CMS either uses a key you’ve put in that file or adds their own in the AI Settings modal.
Turning AI on#
AI is off until cms.yaml has an ai: block. The shortest one that works is:
ai:
enabled: trueThat gets you the three hosted providers with their default models and Gemini as the default. Nothing will respond until somebody adds a key, but the AI tab, toolbar button and shortcuts all appear.
A fuller example, with every key Emet actually reads:
ai:
enabled: true
providers:
- type: gemini
model: gemini-3.5-flash
apiKey: "AIza..." # optional site-wide key
- type: anthropic
model: claude-sonnet-4-6
- type: custom
endpoint: "http://localhost:11434/v1"
model: llama3
defaultProvider: gemini
features:
chat: true
inline: true
seoOptimize: true
imageGeneration: true
translation: true
pageBuilder: true
brandVoice: |
Plain English, second person, no marketing adjectives.
British spelling. Short paragraphs.
actions:
- id: tldr
label: Add a TL;DR
prompt: Add a one-paragraph TL;DR to the top of this text, then return the whole text.
scope: body
shortcuts:
inlineAction: Mod-J
openChat: Mod-Shift-AIf you leave providers out you get all three hosted providers; if you list some, you get only those. defaultProvider falls back to the first in the list. The setup wizard writes this block for you if you tick Enable AI features on its Integrations step, with the default model filled in for each provider you pick.
Providers and models#
| Type | Default model | Built-in list | Image models |
|---|---|---|---|
gemini |
gemini-3.5-flash |
gemini-3.1-pro-preview, gemini-3.5-flash |
gemini-3-pro-image-preview (default), gemini-2.5-flash-image |
openai |
gpt-5.5 |
gpt-5.5, gpt-5.5-pro, gpt-5.2, gpt-5 |
gpt-image-2 |
anthropic |
claude-sonnet-4-6 |
claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5 |
none |
custom |
default |
whatever the endpoint reports | none |
The built-in list is only a starting point. Once a key is saved (or you click Refresh next to the model dropdown), Emet asks the provider for its current models and caches the answer in your browser, so the dropdown shows what your account can actually use. OpenAI’s list is filtered to chat models; Gemini’s to models that support generateContent. There’s also a + Custom model… option at the bottom of the dropdown for typing an ID that isn’t listed.
Which model gets used, in order of precedence: the one you picked in AI Settings, then the model in cms.yaml, then the default above. Your pick is remembered per provider in your browser.
API keys#
Keys can come from two places. A site-wide key is the apiKey on a provider in cms.yaml: everyone who opens the CMS can use AI without setting anything up, and the provider shows a Site key badge in settings. A personal key is one you paste into AI Settings (the cog in the AI panel header); it’s stored in your browser’s localStorage and overrides the site key for you only.
The settings modal has a Test button that validates a key without saving it and a Save button that validates and keeps it, then fetches the model list. There’s a Get key link next to each hosted provider pointing at the right console.
WarningA site-wide key is a key in a Git repository. If the repository is public, so is the key. The wizard warns you about this, but it’s worth repeating: for a public site, leaveapiKeyout and let people add their own.
Requests go directly from the browser to the provider’s API. For Anthropic that means Emet sends the anthropic-dangerous-direct-browser-access header, which is their opt-in for exactly this. There’s no server-side proxy, and the endpoint key is ignored for the three hosted providers, so a cms.yaml can’t quietly redirect your key somewhere else.
Emet also rate-limits itself on the client side, at 60 requests a minute for OpenAI and Gemini, 50 for Anthropic and 100 for custom endpoints. If you hit it you’ll get a “Rate limited” error with a countdown rather than a bill.
Gemini key types#
Google issues two kinds of key, and Emet tells them apart by prefix:
AIza…keys come from Google AI Studio. Requests go togenerativelanguage.googleapis.com.AQ.…keys are Vertex AI Express Mode keys from Google Cloud. Requests go toaiplatform.googleapis.comand bill your Cloud account, which is the way to spend Google One or Developer Program credits on this.
The settings modal shows an AI Studio or Vertex Express badge next to the key. Note that Vertex Express keys can’t list models, so for those Emet substitutes a hard-coded list of older models (gemini-2.5-flash, gemini-2.5-pro, gemini-2.0-flash, gemini-1.5-pro, gemini-1.5-flash). Use + Custom model… if you want something newer.
Custom providers#
The custom type works with any endpoint that accepts OpenAI-style /chat/completions requests. The only thing it needs is endpoint; a key is optional, which suits a local Ollama or LM Studio server:
ai:
providers:
- type: custom
endpoint: "http://localhost:11434/v1"
model: llama3
defaultProvider: customFor the model list Emet tries GET {endpoint}/models first and, if that fails, Ollama’s /api/tags. Custom providers never do image generation, whatever sits behind them.
Brand voice#
ai.brandVoice is a block of free text that’s added to the system prompt of every AI call: chat, inline actions, page builder, translation, the lot. It’s the one place to say “we don’t use exclamation marks” and have it stick. Keep it short; it goes out with every request.
Custom actions#
ai.actions adds your own entries to the toolbar AI menu, next to Improve Text. Each has an id, a label, a prompt and an optional scope:
| Key | What it does |
|---|---|
id |
A unique key. Anything you like. |
label |
The text in the menu. |
prompt |
Your instruction. Emet appends the text to work on and asks for only the result back. |
scope |
selection (the default) works on selected text, or the whole body if nothing’s selected. body always works on the whole body. |
icon |
Accepted but not used for anything yet. |
The result replaces the selection, or the whole body, with no confirmation step. Undo works as usual.
Feature toggles#
Everything under features defaults to on. Setting one to false hides the corresponding UI:
| Key | Hides |
|---|---|
inline |
The toolbar AI menu and the AI button on the selection toolbar. Since the toolbar menu is also where image generation, translation, SEO and the page builder live, this takes those with it. |
imageGeneration |
Generate Image and the Edit with AI buttons on images. |
translation |
Translate in both the toolbar menu and the selection menu. |
seoOptimize |
The SEO quick actions in chat and the toolbar menu. |
pageBuilder |
Build Page in chat and Page Builder in the toolbar menu. |
chat |
Nothing, currently. The chat tab is tied to ai.enabled. |
Shortcuts#
ai.shortcuts.inlineAction (default Mod-J) opens the inline actions menu on a selection, and ai.shortcuts.openChat (default Mod-Shift-A) switches the right panel to the AI tab. Both use Tiptap’s key notation (Mod is Ctrl on Windows and Linux, Cmd on a Mac) and both only work while the visual editor has focus. See Keyboard shortcuts for the rest.