Extension
When an agent should write a Meridian extension (a Go plugin adding template filters, globals, routes or commands), the minimal filter extension, how it must be built to load, how to name it in engine.yaml and how to verify it.
An extension is a Go plugin (.so) the engine opens at start when engine.yaml names it. It can add template filters and globals, HTTP routes, commands, an asset resolver and a development server. Reach for one only when configuration and templates cannot express the need: most sites never have one.
Before you write one
- Check it is needed. A filter for formatting, a component for markup, a declared endpoint for a form, and a webhook for telling another system all exist already. Search the handbook first: https://docs.cherwell.studio/llms-full.txt.
- Check you can build it. A plugin loads only into the exact build it was made for. You need the Go toolchain version the engine names, the same OS and architecture, cgo, and the
github.com/wrux/meridian-extendmodule source at the engine’s version. Read the requirements from the binary:
engine-dmo version extensions: built with go1.27.3 darwin/arm64, GOARM64=v8.0, cgo, -trimpath, meridian-extend v0.0.0-00010101000000-000000000000 If the line says the engine is a pure Go build, or you do not have the toolchain or the module source, stop: tell the person what the extension would do and that it needs a cgo engine (the -vips image) and a build environment. Extensions do not load on Windows.
The module source is the hard part. github.com/wrux/meridian-extend is not published to a Go module proxy: the engine records it at the zero version v0.0.0-00010101000000-000000000000, so go get cannot fetch it, and nothing in the binary or the images carries it. An extension can be built only where the person has the module’s source at the engine’s exact version (from the Meridian source repository) to point a replace at. Without it, stop and say so. Every engine release means rebuilding every extension against the new version; there is no compatibility window between releases.
A filter
// extensions/shout/shout.go
package main
import (
"context"
"strings"
extend "github.com/wrux/meridian-extend"
)
type shout struct{}
func (shout) Manifest() extend.Manifest {
return extend.Manifest{ID: "shout", Name: "Shout", Version: "1.0.0"}
}
// Register declares the filter, so engine-dmo check admits it in templates.
func (shout) Register(r extend.Registrar) error {
return r.Filter("shout", "value|shout: the value in capitals")
}
// Init attaches the filter's function once the engine is running.
func (shout) Init(_ context.Context, h extend.Host) error {
return h.Filter("shout", func(_ extend.Render, in, _ extend.Value) (any, error) {
return strings.ToUpper(in.String()), nil
})
}
// Extension is the symbol the engine looks up.
var Extension extend.Extension = shout{}
func main() {} // extensions/shout/go.mod
module example.com/shout
go 1.26.0
toolchain go1.27.3
require github.com/wrux/meridian-extend v0.0.0-00010101000000-000000000000
replace github.com/wrux/meridian-extend => ../../../meridian-extend Use the toolchain and meridian-extend version your engine-dmo version prints, not the ones above, and point replace at wherever the person keeps the module source. Keep the extension to the standard library: any module it shares with the engine must match the engine’s version exactly.
Build and name it
cd extensions/shout
GOWORK=off CGO_ENABLED=1 go build -trimpath -buildmode=plugin -o ../shout.so . For a Linux container image, build in the official Go image of the engine’s toolchain (see Extensions).
# config/engine.yaml
extensions:
- extensions/shout.so <h2>{{ entry.title|shout }}</h2> Verify
engine-dmo check
engine-dmo version
engine-dmo render / --json check opens the plugin and reports any mismatch at its line in engine.yaml (does not match this engine (...), followed by each difference and what to change), and admits |shout in templates. version lists shout among the modules. Init runs only when the engine serves, so render is the test that the filter works.
Rebuild every extension for every engine release, and never load a .so you did not build from source you control: an extension runs inside the engine with all its privileges. First-party extensions, such as vite, are named by bare name (extensions: [vite]) and ship with the engine; see First-party extensions.
Claims
How an agent lets a business claim its listing on engine-dmo, seeding an organisation for each listing, writing the claim link that has no helper, reading the verification mail, and verifying the form from a script.
Troubleshooting
The errors a coding agent meets in a Meridian project (from check, the dev verbs, schema plan and apply, seed, render, the accessibility audit and server start), what each message means and the exact fix, with the MRD error codes.