Meridian handbook
Concepts

Modules and bundles

What the core platform provides, what each Meridian module adds and requires, which modules the engine-cms and engine-dmo bundles compile, and what happens when a project uses a module its binary lacks.

Meridian is a core content platform plus modules. A bundle is the core and a fixed list of modules compiled into one binary: engine-cms and engine-dmo. You choose a bundle by choosing the binary; you cannot add or remove a module from a binary, and nothing in a project’s configuration switches one on.

Your own code is not a module. It is an extension, a Go plugin the engine loads at start; see Extending.

The core

Every bundle has the core platform:

  • the five core document types page, article, event, guide and collection, and your custom types;
  • every field type, validation, relations, localisation and entry variants;
  • documents, revisions, the publishing workflow, scheduling and the trash;
  • multiple sites and locales, routes, redirects, feeds, calendars and sitemaps;
  • the media library and image pipeline;
  • search, navigation, tags and smart lists;
  • declared endpoints, sources, importers, outbound webhooks and plugins;
  • analytics, dashboards and content health;
  • the admin, staff users, roles and grants;
  • the content API and the content MCP;
  • the template language and its tooling.

The modules

ModuleAddsRequiresengine-cmsengine-dmo
AI (ai)AI assistance in the admin with your own provider: suggestions, alt text, a review queue, a budget and the activity log; the ai: section of engine.yaml; engine-dmo aiYesYes
Forms (forms)The store step of declared endpoints and the submissions it keeps, their inbox in the admin, the drawn forms (enquiryForm(), subscribeForm()) and their abuse controls; the forms: sectionYesYes
Newsletter (newsletter)Subscribers and their consent, double opt-in, campaigns built from live content, the deliverability wizard, the preference centre; engine-dmo newsletterYesYes
Translation (translation)Translation jobs and the Translations workspace, translation memory, the glossary, adding a locale to existing content; engine-dmo translate; the translation: sectionYesYes
Directory (directory)The listing and area types, landing pages, and the embeddable widgetsYes
CRM (crm)Organisations and people, their custom fields (config/crm/), partner roles (config/portal/roles.yaml), broadcasts to member contacts, the import and the merge; engine-dmo crm, engine-dmo broadcastFormsYes
Memberships (memberships)Membership tiers (config/memberships.yaml), which tiers each site sells, memberships with renewals and reminders; the membership: sectionCRMYes
Member portal (portal)The member realm: invitations and sign-in, the member portal and the member API (config/portal/, each site’s portal.yaml); engine-dmo portalCRM, MembershipsYes
Claims (claims)A person claiming an organisation, the checks and the decision, the staff queue and the portal’s claim flowCRM, Memberships, Member portalYes
Promotions (promotions)Promotion slots (each site’s promotions.yaml), members’ requests for them, the staff queue, and promoted() in templatesCRM, Memberships, Member portalYes
Member report (memberreport)The member report in the portal, the member API, as a PDF and as a digest; config/analytics/reports.yamlCRM, Memberships, Member portalYes
Affiliates (affiliates)Affiliate programmes, tracked links marked sponsored, conversion imports and postbacks; engine-dmo affiliatesCRMYes
Paid listings (paidlistings)Prices for tiers and promotions, orders, payment through a provider’s hosted checkout or a staff invoice; config/paidlistings.yamlCRM, Memberships, Member portal, PromotionsYes

The modules that require others form one chain. Each arrow points at a module it requires, and a module also requires what that one requires (Paid listings needs Promotions, so it needs the Member portal, Memberships, CRM and Forms too). AI, Newsletter, Translation and Directory require nothing.

flowchart RL
  crm["CRM"] --> forms["Forms"]
  memberships["Memberships"] --> crm
  affiliates["Affiliates"] --> crm
  portal["Member portal"] --> memberships
  claims["Claims"] --> portal
  promotions["Promotions"] --> portal
  memberreport["Member report"] --> portal
  paidlistings["Paid listings"] --> promotions

The DMO section documents the modules only engine-dmo has. The AI, Forms, Newsletter and Translation modules are documented under Features because both bundles have them.

What a module contributes

A module can add any of these, and they exist only in a binary that compiles it:

ContributionWhere you meet it
Content typeslisting and area from Directory. A project can extend them in config/schema/
Configuration filesSuch as config/memberships.yaml or a site’s promotions.yaml
A section of engine.yamlSuch as ai:, forms:, portal: or membership:
Database tablesEach module has its own migration stream, named in engine-dmo migrate status and migrate to <module>/<version>
PermissionsStaff permission verbs, granted to built-in roles (below)
Admin screens, CLI commands, template functions, content MCP tools and seed sectionsEach listed by engine-dmo help --json, engine-dmo template api and engine-dmo describe

Using a module the binary does not have

A project that uses a module’s file, section or type on a binary that does not compile it is refused at the place it is used, naming the module, by check and at boot. On engine-cms:

config/memberships.yaml:1:1: memberships.yaml belongs to the memberships module, which this bundle (cms) does not compile
    1 | tiers:
      | ^
      = use a bundle that compiles memberships, such as the DMO platform's bin/engine-dmo, or remove it
      = error code MRD-1006
config/engine.yaml:22:1: portal: belongs to the portal module, which this bundle does not compile
   22 | portal:
      | ^^^^^^
      = use a bundle that compiles portal, or remove the section
      = error code MRD-1006
config/schema/listing.yaml:1:10: "listing" is not a core type; core types are page, article, event, guide, collection
    1 | extends: listing
      |          ^^^^^^^
      = to declare a custom type use key: instead of extends:
      = error code MRD-1006

The reverse always works: every module of engine-cms is in engine-dmo, so an engine-cms project runs on engine-dmo unchanged. A database that engine-dmo has migrated can be opened by engine-cms; the DMO modules’ migrations are reported in the log as belonging to modules this bundle does not compile, and their tables are left alone.

Modules you do not use

Every compiled module is present whether or not your project uses it, so choosing engine-dmo for a site that needs none of the DMO modules costs one thing worth knowing: the member portal is on. engine-dmo describe project shows it on each site, as portal /members, both, and the portal answers under /members, where a visitor is redirected to its sign-in.

Turn it off for the whole install in config/portal/portal.yaml:

# config/portal/portal.yaml
portal:
  mode: "off"

describe project then shows portal /members, off, and nothing is served under the portal path. A site’s own portal.yaml can do the same for one site (Partners and the portal). Quote "off": YAML reads it unquoted as well, but quoted it cannot be mistaken for a boolean.

The other DMO modules do nothing until you configure them: the listing and area types exist but have no pages until a route names them, and there are no tiers, slots or programmes until their files declare some. If your site uses none of them, engine-cms is the smaller binary, but no published image contains it (Install).

Permissions a module adds

Each module’s verbs join core’s in config/access/roles.yaml, and some are given to built-in roles. The owner role holds every verb of every compiled module.

ModuleVerbsBuilt-in roles that hold them
AIai.use, ai.bulk, ai.generate_image, ai.configureowner only; grant them to your roles
Formssubmissions.viewadministrator, partnership-manager
Newsletternewsletter.view, newsletter.manage, newsletter.draft, newsletter.send, newsletter.exportadministrator all; editor view and draft; analyst view
CRMbroadcast.draft, broadcast.send, crm.importadministrator, partnership-manager
Promotionspromotions.decideadministrator, partnership-manager
Affiliatesaffiliates.view, affiliates.manageadministrator and partnership-manager both; analyst view
Paid listingspaidlistings.manageadministrator, partnership-manager

A role naming a verb no compiled module declares is an error at its line, with the nearest known verb suggested. Users, roles and permissions explains roles and grants.

Finding out what a binary has

engine-dmo version          # the bundle and its modules, one line
engine-dmo version --json   # the same, with each module's id and name
engine-dmo help --json      # every command, with its arguments and flags
engine-dmo describe project # the types, sites, roles and other elements the project has under this binary
meridian v0.8.0 (pure-go) go1.27.3
  bundle dmo: modules ai directory forms crm affiliates memberships newsletter portal claims memberreport promotions paidlistings translation