Documentation

Creating Modules

Technical guidance for adding first-party DesertCMS modules with settings, public routes, capability mapping, plan gates, renderer output, and tests.

Source: CREATING_MODULES.md

DesertCMS modules are first-party feature surfaces, not external plugins. A module usually has a settings flag, optional public output, admin UI, capability policy, renderer behavior, and tests.

Module Definition

Add the module to lib/DesertCMS/Modules.pm.

A definition should include:

{
    key                  => 'example',
    label                => 'Example',
    description          => 'Adds the public /example/ page and example settings.',
    public_path          => '/example/',
    settings_path        => '/admin/settings/modules/example',
    setting_key          => 'module_example_enabled',
    default_plan_enabled => 0,
}

If the module is master-managed on contributor sites, add:

managed_by_master_contributor => 1

Settings

Add a default setting in DesertCMS::Config:

module_example_enabled => 0,

If the setting must persist through the admin settings UI, wire it through the relevant DesertCMS::Settings save path and admin form.

Plan Gates

Contributor plans use the module feature map.

The module catalog computes:

  • available
  • enabled
  • configured_enabled
  • locked_by_plan
  • requires_upgrade
  • managed_by_master

Do not hide a master-managed contributor feature only by removing a link. The module state should explain whether the feature is plan-locked or platform-managed.

Capability Policy

Map admin routes in DesertCMS::CapabilityPolicy.

Use read/write capability pairs when possible:

return _read_or_write($method, 'view_features', 'enable_allowed_modules') if _matches(
    $path,
    qr{\A/admin/settings/modules/example(?:/|\z)}
);

If the module has public admin content editing, use view_content and edit_content. If it changes design, use view_design and customize_theme. If it is an operator task, use an operations or provider capability.

Contributor mode should block master-managed routes in _contributor_master_managed_route.

Admin UI

Use the existing admin shell and product split:

  • Master-only modules can appear under MasterCMS settings or operations.
  • Contributor-available modules should appear in Features.
  • Contributor content tools should use site-builder language.
  • Platform-managed options should say they are managed by the platform.
  • Plan-locked options should point to Billing or upgrade.

Avoid operator terms on contributor pages.

Public Rendering

If the module writes a public page, add renderer behavior in DesertCMS::Renderer.

The normal pattern is:

  1. Check whether the module is enabled.
  2. Skip generated output if published content claims the same URL.
  3. Render through the shared public module shell.
  4. Write into public_root.
  5. Remove generated artifacts when disabled.

Generated pages should use the same theme and layout as other public content.

Routes

Public module routes may need httpd forwarding if they are dynamic CGI routes, such as forms or shop checkout.

Static generated module pages do not need CGI forwarding once written to the public root.

Schema

If the module needs database tables, add them to sql/schema.sql and migration code through the existing migration flow.

Keep tenant ownership explicit for contributor-facing data. Bind destructive operations to the exact owner or site record.

Media

If the module uses media:

  • Use DesertCMS::Media.
  • Do not expose private source paths.
  • Use public image derivatives for images.
  • Use Resource Downloads for public non-image files.
  • Respect media capabilities and plan limits.

Tests

Add or update tests for:

  • Module definition.
  • Settings default.
  • Enabled and disabled public output.
  • Plan-gated feature state.
  • Contributor mode route policy.
  • Renderer cleanup when disabled.
  • Admin terminology when contributor-accessible.
  • Public route behavior.

For module-gated editor controls, preserve stored values when a feature is disabled. Do not clear fields just because the UI panel is hidden.