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
ReportFamilyandDescriptionof 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 = trueare 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.