Meridian handbook
Destination platform (engine-dmo)

What engine-dmo adds

The modules the DMO bundle compiles on top of the core content platform, what each one is for, how they depend on each other and which configuration files belong to them.

engine-dmo

This section is about the DMO bundle (engine-dmo). Everything in it is absent from engine-cms.

The examples in this section write the locale code en-GB. Replace it with your own site’s code in every label, slug and seed value. engine-dmo init writes en, and an example pasted under another code fails check --strict or, for a landing’s slugs, only warns.

engine-dmo is the core content platform (everything engine-cms does) plus the modules a destination marketing organisation runs on: a directory of places, the businesses behind them, their memberships, a portal where those businesses keep their own listings, and the commercial features built on that relationship. The modules are compiled into the binary; there is nothing to install or switch on. A module’s features appear as soon as you configure them, and most of them do nothing until you do.

Check which bundle and modules a binary carries with version:

engine-dmo version
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

engine-cms version lists ai forms newsletter translation only. The modules shared by both bundles (AI assistance, forms, the newsletter and translation) are documented under Features.

The modules

ModuleWhat it addsRequiresPage
directoryThe core listing and area types, landing pages (a place crossed with a category at a clean URL), entry.landingsnothingDirectory, Landing pages
crmOrganisations and people, their custom fields, partner roles, smart lists of organisations and people, broadcasts, the CRM import, merge and erasureformsCRM
membershipsMembership tiers, each organisation’s membership on each site, renewals and reminders, memberTier()crmPartners and the portal
portalThe member portal (sign in, screens, invitations), the member API, the requireMember tagcrm, membershipsPartners and the portal, Portal screens and actions
claimsA business claiming its entry: the public claim form, verification, scoring and the staff queuecrm, memberships, portalClaims
promotionsPromotion slots, partners’ requests from the portal, the staff queue, promoted() and promotionLabel()crm, memberships, portalPromotions
memberreportThe member report (portal, member API, PDF, monthly digest) and scheduled organisation reportscrm, memberships, portalMember reports
affiliatesAffiliate programmes, tracked links under /go/, conversion imports, the signed postbackcrmAffiliates
paidlistingsPrices for tiers and promotions, orders, Stripe Checkout or staff invoices, fulfilmentcrm, memberships, portal, promotionsPaid listings

The arrows point from a module to the modules it requires; a requirement reached through another module is drawn once:

flowchart BT
  forms["forms (core)"]
  directory
  crm --> forms
  memberships --> crm
  portal --> crm
  portal --> memberships
  claims --> portal
  promotions --> portal
  memberreport --> portal
  affiliates --> crm
  paidlistings --> promotions
  paidlistings --> portal

How they fit together

The directory is content: listings and areas are documents like any other, edited in the admin, routed, rendered and searched by the core engine. The CRM is records: an organisation is a business or body, a person is someone you deal with, and a person is linked to an organisation in a partner role. The two meet at one point: a document can be linked to an organisation, which is then its owner. In the admin the link is the Organisation panel of the document editor; in templates it is entry.organisation.

This is the partner side’s data model, in outline:

erDiagram
  NETWORK ||--|{ SITE : "groups"
  NETWORK ||--o{ ORGANISATION : "holds"
  NETWORK ||--o{ PERSON : "holds"
  PERSON ||--o{ CONTACT : "is"
  ORGANISATION ||--o{ CONTACT : "has"
  PARTNER_ROLE ||--o{ CONTACT : "grants"
  ORGANISATION ||--o{ MEMBERSHIP : "holds"
  SITE ||--o{ MEMBERSHIP : "sells"
  TIER ||--o{ MEMBERSHIP : "ranks"
  ORGANISATION |o--o{ DOCUMENT : "owns"

A contact is a person’s link to an organisation in a partner role; a person may be linked to several organisations. A membership is one organisation’s on one site at one tier. Documents are shared across the install; their organisation must be in the network of the document’s owner site.

Everything else hangs off that link:

  1. An organisation holds a membership on a site, at a tier (config/memberships.yaml). The tier decides what its people may change on its listings (member_editable on the type), which promotion slots and portal screens it may use, and which report digest it receives.
  2. The organisation’s people sign in to the portal and edit their listings within those rules. Their changes become revisions that wait for review or publish at once, as the tier says.
  3. Visitors’ interactions with the listing (impressions, views, website clicks, phone reveals, enquiries) are counted per organisation and returned to it as the member report.
  4. A business with no portal account yet can claim its organisation from the public site.
  5. Members can ask for promotions in the slots a site declares, and paid listings can take payment for tiers and promotions.
  6. Sites in the same network share organisations, people and portal accounts; sites in different networks share none of them. See Multisite and networks.

Configuration files the modules read

All of these are optional. A project that declares none of them still runs on engine-dmo; it simply has no tiers, no slots and the portal’s defaults.

FileModulePage
config/memberships.yamlmembershipsPartners and the portal
config/networks.yamlcore (used by the CRM and portal)Multisite and networks
config/crm/organisation.yaml, config/crm/person.yamlcrmCRM
config/portal/portal.yamlportalPartners and the portal
config/portal/roles.yamlcrm, portalPartners and the portal
config/portal/enquiries.yamlportalPartners and the portal
config/sites/<site>/portal.yamlportalPortal screens and actions
config/sites/<site>/promotions.yamlpromotionsPromotions
config/analytics/reports.yamlmemberreportMember reports
config/paidlistings.yamlpaidlistingsPaid listings
landings: on a route in routes.yamldirectoryLanding pages
member_editable: in a type’s schema filememberships, portalPartners and the portal
memberships.tiers in site.yamlmembershipsPartners and the portal
claims: and membership: in engine.yamlclaims, membershipsClaims, Partners and the portal

engine-dmo config schema --file <name> prints the JSON Schema of any of them (memberships, networks, crm, portal, site-portal, partner-roles, enquiries, promotions, reports), and engine-dmo check validates every one, reporting each problem at its file, line and column.

Staff permissions the modules add

The core staff permissions include the CRM’s (crm.organisation.*, crm.person.*, crm.membership.view, crm.membership.edit, crm.tag.manage, crm.smartlist.manage, crm.claim.review). The modules register these as well:

PermissionModuleGranted to built-in roles
broadcast.draft, broadcast.sendcrmadministrator, partnership-manager
crm.importcrmadministrator, partnership-manager
promotions.decidepromotionsadministrator, partnership-manager
affiliates.viewaffiliatesadministrator, partnership-manager, analyst
affiliates.manageaffiliatesadministrator, partnership-manager
paidlistings.managepaidlistingsadministrator, partnership-manager

The built-in owner role holds every permission. Grant the others through config/access/roles.yaml and config/access/grants.yaml as for any staff permission (see Users, roles and permissions).

Partners (the people signing in to the portal) never hold staff permissions. Their permissions come from partner roles, described in Partners and the portal.

Commands the modules add

CommandWhat it doesPage
engine-dmo crm ...Organisations, memberships, invitations, import, merge, erase, duplicate candidates, claimsCRM
engine-dmo broadcast ...Draft, test, schedule, send and report broadcastsCRM
engine-dmo lists ...Check, count and run smart listsCRM
engine-dmo portal ...Check, scaffold and inspect the portalPortal screens and actions
engine-dmo api member ...The member API’s OpenAPI document, routes and form descriptionsPortal screens and actions
engine-dmo affiliates ...Networks, imports, connections, postback keysAffiliates
engine-dmo analytics report, digest, report-runMember reports and organisation reportsMember reports
engine-dmo dev tier, dev role, dev slot, dev portal, dev networkDeclare tiers, partner roles, slots, portal screens and actions, and networks, with their idsthe page for each

Run engine-dmo <command> --help for every flag, or engine-dmo help --json for the whole command tree.

Where to start

Turning off what a site does not use

Most modules do nothing until configured, but two answer from the start on every engine-dmo site: the member portal at /members (with the member API) and, through it, the claim form under /members/claim/. engine-dmo describe project shows the portal as portal /members, both, ... even on a site with no partners. To switch it off for the whole install:

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

/members then answers 404, and describe project shows portal /members, off. A single site can do the same in config/sites/<site>/portal.yaml with mode: off. Setting auth.member.registration: invite_only in engine.yaml closes only the claim form.

A site with no listings simply declares no route for the listing and area types; their tables stay empty.

Example projects

The example projects (dmo-minimal with listings, tiers, affiliates and enquiries; dmo-destination with two sites, landing pages, promotions and custom portal screens and actions; dmo-multisite with two sites in two networks) live in the Meridian source repository, under examples/. They are not inside the binary or the container image, and engine-dmo init with no flag writes only the bare starter. engine-dmo init <dir> --from <path> copies a project directory you already have on disk, so it works only with a copy of the repository’s examples/ folder. Without one, build up from the starter with the pages of this section and the dev commands; see Your first project.