Meridian handbook
ReferenceConfiguration

engine.yaml: content, media and search

Every key of engine.yaml's content, media, events, tags, approvals, endpoints, forms, search, maps, admin and notifications sections, with type, default and meaning.

These sections of config/engine.yaml tune how content, media, search and the admin behave for the whole install. Per-site and per-type settings live in the site and schema files. Value formats (durations, retention periods, sizes) are explained on engine.yaml.

content

KeyTypeDefaultMeaning
content.trash_retentionretention30dHow long a trashed document waits before the nightly purge deletes it. A document on hold is kept until released
content.revisions.prunebooleantruePrune old revisions daily. false keeps every revision and does not schedule the task
content.revisions.keep_allretention90dEvery revision younger than this is kept. At least 1d
content.revisions.keep_lastinteger20And the newest N of each document’s revision chain. At least 1
content.revisions.keep_monthlybooleantrueAnd the newest revision of each calendar month (UTC) of each chain
content.health.stale_afterretention12mA live page older than this fails the content health staleness signal. At least 1d

A type’s own revisions: block in its schema file overrides content.revisions key by key. A document under legal hold keeps every revision. engine-dmo content prune-revisions --dry-run shows what the sweep would remove.

media

KeyTypeDefaultMeaning
media.max_upload_bytessize50MBThe largest file an upload may carry
media.max_pixelsinteger50000000The largest image, in pixels, the pipeline decodes. At least 1,000,000
media.acceptlist of strings[image/jpeg, image/png, image/webp, image/avif, application/pdf, application/gpx+xml]The media types an upload may have. An asset field’s own accept narrows it further
media.strip_exif_gpsbooleantrueRemove GPS coordinates from uploaded images
media.require_alt_textbooleantrueAn image used on a page needs alt text before the page publishes
media.signing_keysecretDerivedThe key derivative addresses are signed with. Unset, a key derived from server.security.signing_key is used. Changing it changes every derivative address
media.derivatives.keep_unusedretention90dA derivative nothing has served or warmed for this long is removed by the nightly prune. At least 1d
media.rights.termsmapThe four built-in termsThe licence vocabulary staff choose from, by key. Setting it replaces the built-in terms
media.rights.terms.<key>.labelmap of stringsThe term’s name per locale, such as {en-GB: "Stock, website only, 2 years"}
media.rights.terms.<key>.useslist of stringsWhere an asset under the term may be used: website, social, newsletter, print, editorial, press, partner, handover
media.rights.terms.<key>.territorylist of stringsWhere it may be used: WORLD or country codes
media.rights.terms.<key>.durationstringEmptyHow long the licence runs from its start, such as 2y or 18m; empty is perpetual
media.rights.terms.<key>.credit_requiredbooleanfalseThe credit must be shown wherever the asset is
media.rights.expiry_warningslist of strings[180d, 90d, 30d, 0d]When, before a licence ends, the people who manage media rights are told
media.rights.block_publish_when_expiredbooleantrueAn asset whose licence has ended blocks publishing a page that uses it. false makes it a warning
media.rights.warn_publish_when_expiringbooleantrueWarn at publish when a licence ends while the page stays live
media.rights.on_expirystringwarnWhat a live page does once an asset’s licence has ended: warn (nothing changes on the page) or placeholder (the image renders as absent)
media.orphans.scan_schedulestring0 3 * * *When the full reference scan, which finds assets nothing uses, runs. Cron, UTC
media.duplicates.thresholdinteger10How many of a perceptual hash’s 64 bits may differ for two images to be offered as near duplicates

The built-in licence terms are commissioned-perpetual (every use, worldwide, perpetual), stock-2yr-web (website and social, two years), a member-supplied term (website, social and newsletter, credit required) and press-only (press and editorial, credit required).

events

KeyTypeDefaultMeaning
events.occurrence_window.behindretention90dHow far into the past a recurring event’s occurrences are kept
events.occurrence_window.aheadretention540dHow far ahead they are generated. At most five years
events.roll_attime of day03:30When the window rolls forward each day, in the default site’s reporting time zone

tags

KeyTypeDefaultMeaning
tags.promotion.thresholdinteger0 (meaning 5)How many distinct subjects a freeform tag must be on before it is offered for promotion into the vocabulary and the people who manage tags are told

approvals

How the moderation queue treats changes members submit through the portal (engine-dmo). Which fields a member may edit, and whether a tier’s changes need review, are set in the schema.

KeyTypeDefaultMeaning
approvals.review_window_pollduration15mHow often review windows are closed and returned drafts expired
approvals.returned_expiryretention30dA draft returned to its member and left untouched this long is discarded as if rejected, and the member is told
approvals.expected_turnaroundduration24hThe review time members are told to expect; a change waiting longer is overdue
approvals.bulk_approve.enabledbooleantrueReviewers may approve several low-risk changes at once
approvals.bulk_approve.low_risk_typeslist of strings[opening_hours, phone, link, boolean, number, date, datetime, select, address, geo]The field types whose changes count as low risk

endpoints

Install-wide defaults for declared endpoints (forms, enquiries, newsletter sign-up and the rest in each site’s endpoints.yaml). A site’s endpoint may tighten these and may not loosen a security-relevant one.

KeyTypeDefaultMeaning
endpoints.defaults.rate_limit.public_getrate limit60/minute/ipPublic GET endpoints, per client address
endpoints.defaults.rate_limit.public_postrate limit10/hour/ipPublic POST endpoints, per client address
endpoints.defaults.rate_limit.public_mailrate limit500/day/endpointMail sent by public endpoints, per endpoint
endpoints.defaults.rate_limit.memberrate limit60/hour/sessionMember endpoints, per session
endpoints.defaults.rate_limit.staffrate limit600/minute/sessionStaff endpoints, per session
endpoints.defaults.rate_limit.embedrate limit600/minute/originEmbeddable widgets, per embedding origin. Must be <count>/<window>/origin
endpoints.defaults.spam.proof_of_workbooleantruePublic forms carry a small proof-of-work challenge the browser solves. There is never a CAPTCHA
endpoints.defaults.spam.honeypotbooleantruePublic forms carry a hidden field a person leaves empty
endpoints.defaults.spam.min_fill_timeduration2sA submission faster than this after the form loaded is treated as automated
endpoints.max_input_bytessize256KBThe largest non-upload body an endpoint accepts

forms

The Forms module’s retention of stored submissions.

KeyTypeDefaultMeaning
forms.max_retentionretention2yThe longest any endpoint’s store step may keep a submission
forms.orphan_retentionretention30dHow long submissions outlive their endpoint leaving the configuration
forms.purge_schedulestring0 2 * * *When the purge of expired submissions runs. Cron, UTC

The search backend is always an external service. driver: none is a valid, explicit choice: the search route then serves browse mode.

search:
  driver: meilisearch
  meilisearch:
    url: ${MEILISEARCH_URL}
    api_key: ${MEILISEARCH_API_KEY}
KeyTypeDefaultMeaning
search.driverstringnonemeilisearch, elasticsearch, opensearch or none
search.log_queriesbooleantrueRecord queries for the zero-result and search reports. false is refused in production
search.min_query_lengthinteger2Shorter queries are not sent to the backend
search.degraded_modestringbrowseWhat the search route does while the backend is failing: browse (serve browse results) or error
search.runs_keptinteger20Finished rebuild, purge and sweep runs kept per site and kind, 1 to 1000
search.circuit_breaker.failuresinteger5Failures within window that stop the engine asking the backend
search.circuit_breaker.windowduration30sThe window failures are counted in
search.circuit_breaker.cooldownduration1mHow long the breaker stays open before trying again
search.meilisearch.urlstringEmptyThe Meilisearch base URL. Required with that driver
search.meilisearch.api_keysecretEmptyIts API or master key. Set the variable empty for a server with no key
search.meilisearch.index_prefixstringmeridianPrefix of every index name, so several installs can share one server
search.meilisearch.timeoutduration2sEach request’s timeout
search.elasticsearch.addresseslist of strings[]The cluster’s URLs. Required with that driver; the first is used
search.elasticsearch.usernamestringEmptyBasic authentication user
search.elasticsearch.passwordsecretEmptyBasic authentication password
search.elasticsearch.index_prefixstringmeridianIndex name prefix
search.elasticsearch.timeoutduration3sEach request’s timeout
search.opensearch.*The same keys as elasticsearch, with the same defaults. With driver: opensearch and no opensearch.addresses, the elasticsearch block is used

Also accepted and not used in this release: search.meilisearch.addresses, .username and .password; search.elasticsearch.url and .api_key (and the same under opensearch). Their secret forms are still held to the secret rule.

maps

Static maps are drawn on the server from a tile provider and cached as an image, so a visitor’s browser never contacts the provider. With no tiles, a static map renders nothing.

maps:
  tiles: https://api.maptiler.com/maps/streets-v2/{z}/{x}/{y}.png?key={key}
  key: ${MAP_TILES_KEY}
  attribution: "© MapTiler © OpenStreetMap contributors"
KeyTypeDefaultMeaning
maps.tilesstringEmptyThe tile URL template, with {z}, {x}, {y}, and {key} where the provider takes the key in the address. Any service on the standard tile scheme works, including your own
maps.keysecretEmptyThe provider’s key, written into {key}
maps.attributionstringEmptyThe credit the provider’s licence requires, shown beneath every map
maps.max_zoominteger0 (meaning 18)The highest zoom a template may ask for
maps.tile_sizeinteger0 (meaning 256)The provider’s tile edge in pixels
maps.timeoutduration0s (meaning 10s)The time limit for fetching one map’s tiles

admin

KeyTypeDefaultMeaning
admin.rich_text_editorstringEmpty (Editor.js)The rich text editing surface in the admin and the portal: editorjs (the default) or native
admin.rich_text_input_rulesbooleanfalseMarkdown-style shortcuts in the editor: ## starts a heading, - a list, **bold** bolds, where the field allows the result
admin.theme.brand_namestringEmptyThe name the admin shows in place of the product’s
admin.theme.logostringEmptyThe logo: a path under the project’s public/ starting with / (/branding/logo.svg for public/branding/logo.svg), SVG or PNG
admin.theme.logo_darkstringEmptyThe logo for the dark colour scheme, as logo
admin.theme.faviconstringEmptyThe admin’s favicon, as logo; ICO is also accepted
admin.theme.css_variablesmap of strings{}Brand tokens as CSS custom properties; engine-dmo check lists the themeable names when one is not. Every colour pair is checked against WCAG 2.2 AA contrast when the configuration loads; tokens that carry target size, focus, spacing, type or motion cannot be set
admin.theme.custom_cssstringEmptyA .css file under public/, as logo, loaded after the admin’s own stylesheet
admin.theme.contrast_checkstringEmpty (enforce)enforce makes a failing pair an error; warn a warning (itself a warning in production)

notifications

When staff are emailed about what the admin already shows them.

KeyTypeDefaultMeaning
notifications.mention_email_delayduration15mA mention still unread after this long is emailed
notifications.digest_hourtime of day08:00When the daily digest of everything else goes, in each user’s own time zone
notifications.email_includes_bodybooleantrueInclude the comment or change in the email; false sends a summary and a link only
notifications.retentionretention90dRead notifications are deleted after this