Table of Contents

Dynamic report templates

Dynamic report templates allow cluster developers to expose report templates whose source (standard file or custom database copy) is resolved at runtime. End users can enable or disable templates and create custom copies directly from an administration UI, without any code deployment.

How it works

The feature is provided by the NeosReportCustomization module. It introduces a decorator pattern over the standard file-based report loading pipeline:

flowchart LR
    A[Report engine\nrequests template] --> B{Name starts\nwith 'Custom:'?}
    B -- No --> C[ContentRootReportTemplateLoader\n.mrt file on disk]
    B -- Yes --> D[DatabaseReportTemplateLoader\nDB custom copy]
    D -- Not found\nor disabled --> E[null — report not available]

The Custom: prefix is the inter-process convention used to distinguish a database-backed custom copy from a standard file. The SelectReportTemplateAsync shared UI method handles this prefix transparently; cluster developers never need to construct it manually.

Key components

Component Role
StandardReportTemplateRegistry Singleton that collects all standard template registrations at startup
TenantDatabaseMigrationInterceptor Runs on tenant migration to synchronize the $NeosReportTemplate table with registered standards
DatabaseReportTemplateLoader Decorator on IReportTemplateLoader that serves custom copies from the database
SelectReportTemplateAsync Shared UI method — main entry point for cluster developers

Setup

1. Reference the module

Add NeosReportCustomization to your cluster's referenced modules list so that the $NeosReportTemplate table and the administration UIs are included.

2. Create a report family

ReportFamily is a shared enum that groups report templates by business domain. Add a value for each report type your cluster exposes.

# metadata/EnumMembers/ReportFamily.yml
- Name: MyModuleOrder
  Caption: My module order
  PersistedValue: MyModuleOrder
  Position: 100
Note

The Unknown value is reserved by the framework. Do not use it in cluster code.

3. Create the standard report file and metadata

Create a .mrt file in your module's reports/ directory, then declare the corresponding Report metadata entry that references it.

Tip

The framework automatically serves the .mrt file from the module's reports/ directory when the report engine requests a template whose name matches the file name (without extension). See Creating a report.

4. Register the standard template

In your module's Startup.cs, call AddStandardReportTemplate to register the report with the framework:

using GroupeIsa.Neos.ReportCustomization.Application;
using GroupeIsa.Neos.ReportCustomization.Domain.Enums;
using GroupeIsa.Neos.Shared.Localization;

public static class Startup
{
    public static void ConfigureServices(IServiceCollection services)
    {
        services.AddStandardReportTemplate(
            ReportFamily.MyModuleOrder,
            "MyStandardOrderReport",
            new LocalizableString("Standard order report")
                .WithTranslation("fr", "Rapport standard de commande"));
    }
}

The parameters are:

Parameter Description
reportFamily The ReportFamily enum value that groups this template
reportName The logical name of the .mrt file (without extension)
reportDescription A localizable description displayed in the administration UI

On the next tenant database migration, TenantDatabaseMigrationInterceptor will automatically create a row in $NeosReportTemplate for this standard template.

Important

The reportName must match the name of the .mrt file exactly (case-sensitive). It is also used as the alternate key in the $NeosReportTemplate table, so it must be unique across all registered standard templates.

5. Open the report dynamically

Use the SelectReportTemplateAsync shared UI method as the entry point. It queries available templates for the given ReportFamily, opens a selection UI when multiple templates are available, and returns a NeosReportTemplateInfo containing the resolved template name to pass to the report engine.

A typical cluster implementation wraps this in a module-level shared UI method:

// In a UISharedMethod
NeosReportTemplateInfo? selectedTemplateInfo =
    await UIMethods.NeosReportCustomization.SelectReportTemplateAsync(reportFamily);

if (string.IsNullOrEmpty(selectedTemplateInfo?.Name))
{
    return;
}

var options = new ExecuteReportOptions(selectedTemplateInfo.Name)
    .WithFilter(myFilter);

await this.ExecuteReportWithOptionsAsync(options);

Or, if you are displaying the report in a viewer:

await UI.NavigateAsync(
    new NavigationOptions(UIViews.MyReportViewerUI)
        .WithParameter(MyParams.ReportName, selectedTemplateInfo.Name));
Note

When SelectReportTemplateAsync returns a custom template, the name is automatically prefixed with Custom:. Pass it as-is to ExecuteReportOptions or as a navigation parameter — the framework's DatabaseReportTemplateLoader will resolve the actual binary content from the database.

Selection behavior

Enabled templates available Behavior
0 Warning message — no report available
1 Opens directly, no user interaction required
2 or more Opens the template selection UI (NeosReportTemplateSelectionUI)
Note

If the user does not have read access to NeosReportTemplateSelectionUI (e.g., the license does not include the customization feature), the framework falls back to the standard template as long as it is enabled.

Administration UI

The NeosReportCustomization module provides three built-in UI views accessible through the administration menu:

View Purpose
NeosReportTemplatesWriterListUI List all templates, enable/disable them
NeosReportTemplateWriterDetailUI View and upload a custom .mrt file to replace the standard template
NeosReportTemplatesReaderListView Read-only view for users without write permission

From the detail view, users can create a custom copy of a standard template using the Create copy action, which calls the CreateNeosReportTemplateCopy server method. The copied template is stored as a binary .mrt blob in the $NeosReportTemplate table.

Migration strategy

The TenantDatabaseMigrationInterceptor keeps the $NeosReportTemplate table in sync with the standard registrations on each tenant database migration. It:

  • Creates a new row for any standard template that is not yet in the table.
  • Updates the ReportFamily and Description of an existing standard row if they have changed.
  • Does not re-enable a standard template that was explicitly disabled by a user between migrations.
  • Never overwrites a custom template (only rows where IsStandard = true are candidates for update).
Caution

If you rename a standard report template registration (change the reportName value), the existing row in the database will not be updated — a new row will be created. Remove the obsolete row manually or provide a data migration script.

See also