AI agents: the content index is at /llms.txt. Every page is also available as Markdown — send the request header Accept: text/markdown, or append .md to the page URL.
Xperience Docs

Cookie consent

We use necessary cookies (opens in a new tab) to run our website and improve your experience while browsing. Additional cookies are only used with your consent. You may revoke your consent on the Cookies Policy (opens in a new tab) page or in your browser at any time.

Configure (opens in a new tab)
  • Documentation
    Administration interface basicsDevelopers and adminsBusiness usersChangelogGlossary
  • Guides
    Welcome to Xperience by KenticoAdministration interface basicsCustomer journeysDigital marketingConfigurationDevelopmentArchitectureUpgrade to Xperience by Kentico
  • Training
    For AdminsFor Business usersFor DevelopersFor Solution architects
  • API Examples
    ConfigurationContent managementDevelopmentDigital marketing
  • Changelog
    Documentation updatesSecurity advisories
  • Community Questions & Answers (opens in a new tab)
  • Contact support (opens in a new tab)
  1. Home
  2. Developers and admins
  3. Customization
  4. Extend the administration interface
  5. UI pages
  6. Reference - UI page templates
  7. Listing UI page template
  8. Export listing data

Export listing data


Was this page helpful?

Send us feedback Go back to top

Listing pages can offer an Export action that downloads the listed data as a CSV file. The action is displayed in the listing header, next to the search box and header actions, and opens a dialog where users select which columns to include.

Enable data export

Export is disabled by default. Enable it by calling the EnableExport extension method on PageConfiguration within ConfigurePage.

C#Enable the export with the default configuration
using System.Threading.Tasks;

using Kentico.Xperience.Admin.Base;

public class UserObjectListing : ListingPage
{
    // ...

    public override Task ConfigurePage()
    {
        // Adds the 'Export' action to the listing header
        PageConfiguration.EnableExport();

        return base.ConfigurePage();
    }
}

The parameterless EnableExport call uses the default export configuration, described in the ListingExportConfiguration reference below. To adjust any of the defaults, pass a ListingExportConfiguration object instead.

C#Enable the export with a custom configuration
PageConfiguration.EnableExport(new ListingExportConfiguration
{
    FileName = "users",
    MaxRowCount = 10000,
});

Exported file contents

The export produces a CSV file whose contents depend on the configuration of the listing page, which determines which columns can be exported and how their values are written, and the state of the listing when the user exports it, which determines the rows and columns that end up in the file.

Columns

The Export data dialog offers every column that can be exported, all selected by default:

  • The columns of the listing table, except columns with Visible set to false.
  • The additional export columns, which are not displayed in the table but offered for export.

The file contains the columns that the user leaves selected, in the order in which they are configured: the table columns first, followed by the additional columns. The first row of the file contains the column captions.

Rows

The file contains all rows that match the search term and filter applied at the moment of the export. The rows are sorted the same way as the listing.

The number of exported rows is limited by the MaxRowCount property (50,000 by default, see Reference - ListingExportConfiguration). When the matching rows exceed the limit, the Export action is disabled and the system never exports a partial result. If this occurs, users need to narrow down the listing using search or filters first.

Values

Values in Localizable columns are resolved via localization macros, and the result is passed to the column's Formatter. Additionally:

  • Columns rendered by a React component provided by Xperience export the value that the component displays, for example the label of a status or the names of languages. Columns rendered by custom React components export the value of the column in the listed data, not the value the component displays, unless you register a cell value extractor. See Export values of cells rendered by React components.
  • DateTime values are converted from the server's time zone to the time zone of the user's browser and formatted as YYYY-MM-DD HH:MM:SS. If the browser's time zone cannot be determined, the values remain in the server's time zone.
  • Text values and column captions that start with a character that spreadsheet editors interpret as a formula (=, +, -, @, a tab, or a carriage return) are prefixed with an apostrophe ('), so that exported data cannot execute as a formula. Numeric values are exported unchanged, so negative numbers keep their sign.
  • Every value is enclosed in double quotes, and the file is encoded as UTF-8 with a byte order mark.

Export values of cells rendered by React components

Columns rendered by React components provided by Xperience, such as statuses, tags, links, switches, and languages, export the text the component displays without any additional configuration.

Columns rendered by custom components by default export the value stored in the corresponding column data instead. To export values as formatted and displayed in the admin UI, implement a cell value extractor.

  1. Create a class that inherits from ComponentCellValueExtractor<TProps>, where TProps is the type of the component model produced by the column's modelRetriever function.
  2. Override the GetValue method and return the value you want in the exported file – the displayed text, or a typed value (for example a number) when preserving the value type in the file is preferable.
  3. Register the extractor using the RegisterListingCellValueExtractor assembly attribute. The first parameter is the full name of the React component, i.e., the same value passed as componentKey to AddComponentColumn.
C#Export the value displayed by a custom component
using System.Threading;
using System.Threading.Tasks;

using Kentico.Xperience.Admin.Base;

// Registers the extractor for the cells rendered by the 'MyComponent' React component
[assembly: RegisterListingCellValueExtractor("@orgName/projectName/MyComponent", typeof(AssignedRolesCellValueExtractor))]

// Extracts the exported value from the props of the rendered component
public class AssignedRolesCellValueExtractor : ComponentCellValueExtractor<ReactComponentToDisplayProps>
{
    public override Task<object> GetValue(ReactComponentToDisplayProps componentProps, CancellationToken cancellationToken = default)
    {
        return Task.FromResult<object>(componentProps.prop1);
    }
}

The following rules apply to extractors:

  • Each component is handled by a single extractor. An extractor you register for a component provided by Xperience replaces the system extractor. Registering more than one extractor for the same component across your assemblies results in an error when the export first uses the extractors, and the export of every column rendered by a React component then fails.
  • The system creates a single instance of the extractor for the lifetime of the application. Only request services with a singleton lifetime in its constructor.
  • The attribute is only collected from assemblies that have class discovery enabled.
  • Cells whose component model is of a different type than TProps export an empty value.
  • The extractor runs once for every exported row. Do not retrieve data from an external source within GetValue – prepare the data of the whole result set using a DataModifier instead.

Export columns not displayed in the listing

Use AdditionalColumns to export data that the listing table does not display. Each entry is a ColumnConfiguration object, configured the same way as the columns of the table.

C#Export a column that is not displayed in the listing
PageConfiguration.EnableExport(new ListingExportConfiguration
{
    AdditionalColumns =
    [
        new ColumnConfiguration
        {
            Name = nameof(UserInfo.UserGUID),
            Caption = "User GUID"
        }
    ]
});

The columns appear in the Export data dialog after the columns of the table, where users select them like any other exported column.

The columns are retrieved together with the table columns, which places the following requirements on them:

  • Each column must either be a column of the listed data, or have LoadedExternally set to true.
  • Column names must be unique within AdditionalColumns.

Transform exported values

To adjust exported values that depend on other data, for example names of related objects, assign a DataModifier. The function receives all exported rows at once. Each row is an IDataContainer holding the columns of the listed object, accessible using indexer notation.

The rows the modifier receives hold only the columns retrieved by the listing. You can change the values of these columns, but setting any other column on them has no effect. To export a new column, return new rows that include it:

  1. Declare the column in AdditionalColumns with LoadedExternally set to true.
  2. For each received row, create a DataContainer, copy the row's columns into it, and set the value of the new column.

The following example exports the number of contacts in each contact group from the Contact groups application.

C#Resolve exported values in a single batch
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

using CMS.Base;
using CMS.ContactManagement;
using CMS.DataEngine;

using Kentico.Xperience.Admin.Base;
using Kentico.Xperience.Admin.DigitalMarketing.UIPages;

[assembly: PageExtender(typeof(ContactGroupListExtender))]

public class ContactGroupListExtender : PageExtender<ContactGroupList>
{
    private const string CONTACT_COUNT_COLUMN = "ContactCount";

    private readonly IInfoProvider<ContactGroupMemberInfo> contactGroupMemberProvider;

    public ContactGroupListExtender(IInfoProvider<ContactGroupMemberInfo> contactGroupMemberProvider)
    {
        this.contactGroupMemberProvider = contactGroupMemberProvider;
    }


    public override Task ConfigurePage()
    {
        Page.PageConfiguration.EnableExport(new ListingExportConfiguration
        {
            // Declares the exported column filled by the data modifier
            // The column does not exist in the database, so it is loaded externally
            AdditionalColumns =
            [
                new ColumnConfiguration
                {
                    Name = CONTACT_COUNT_COLUMN,
                    Caption = "Number of contacts",
                    LoadedExternally = true
                }
            ],
            DataModifier = AddContactCounts
        });

        return base.ConfigurePage();
    }


    // Adds the number of contacts in each exported contact group
    private async Task<IEnumerable<IDataContainer>> AddContactCounts(IEnumerable<IDataContainer> rows, CancellationToken cancellationToken)
    {
        var exportedRows = rows.ToList();
        var contactGroupIds = exportedRows.Select(row => (int)row[nameof(ContactGroupInfo.ContactGroupID)]).ToList();

        // Counts the contact members of all exported contact groups in a single query
        var contactCounts = (await contactGroupMemberProvider.Get()
            .Columns(
                new QueryColumn(nameof(ContactGroupMemberInfo.ContactGroupMemberContactGroupID)),
                new CountColumn(nameof(ContactGroupMemberInfo.ContactGroupMemberID)).As(CONTACT_COUNT_COLUMN))
            .WhereIn(nameof(ContactGroupMemberInfo.ContactGroupMemberContactGroupID), contactGroupIds)
            .WhereEquals(nameof(ContactGroupMemberInfo.ContactGroupMemberType), (int)ContactGroupMemberTypeEnum.Contact)
            .GroupBy(nameof(ContactGroupMemberInfo.ContactGroupMemberContactGroupID))
            .GetDataContainerResultAsync(cancellationToken: cancellationToken))
            .ToDictionary(
                count => (int)count[nameof(ContactGroupMemberInfo.ContactGroupMemberContactGroupID)],
                count => (int)count[CONTACT_COUNT_COLUMN]);

        // The retrieved rows only hold the columns selected by the listing query, so each row
        // is copied into a container that can also hold the additional column
        return exportedRows.Select(row =>
        {
            var container = new DataContainer();
            foreach (var columnName in row.ColumnNames)
            {
                container[columnName] = row[columnName];
            }

            container[CONTACT_COUNT_COLUMN] = contactCounts.GetValueOrDefault((int)row[nameof(ContactGroupInfo.ContactGroupID)]);

            return (IDataContainer)container;
        }).ToList();
    }
}

The modifier runs after the data is retrieved and before the CSV file is built.

Enable the export on an existing listing page

To add the export to a listing page provided by Xperience, call EnableExport from a page extender of the page. Extenders access the configuration of the listing through their Page property.

C#Enable the export of the Contacts listing
using System.Threading.Tasks;

using CMS.ContactManagement;

using Kentico.Xperience.Admin.Base;
using Kentico.Xperience.Admin.DigitalMarketing.UIPages;

[assembly: PageExtender(typeof(ContactListExtender))]

public class ContactListExtender : PageExtender<ContactList>
{
    public override Task ConfigurePage()
    {
        // Enables the export of the contact listing with the specified configuration
        Page.PageConfiguration.EnableExport(new ListingExportConfiguration
        {
            FileName = "contacts",

            // Exports the identifier of each contact, which the listing table does not display
            AdditionalColumns =
            [
                new ColumnConfiguration
                {
                    Name = nameof(ContactInfo.ContactGUID),
                    Caption = "Contact GUID"
                }
            ]
        });

        return base.ConfigurePage();
    }
}

EnableExport replaces the entire export configuration of the page. On listing pages that already offer the export, such as form submissions, calling it from an extender discards the existing configuration.

Require a dedicated export permission

By default, the export requires the SystemPermissions.VIEW permission, so anyone who can open the listing can export it. Set Permission to require a different permission – typically a custom permission dedicated to exporting, which lets you grant read access to a listing without allowing bulk extraction of its data.

C#Require a custom permission for the export
PageConfiguration.EnableExport(new ListingExportConfiguration
{
    Permission = "Export"
});

The permission must be defined for the application containing the listing page. Users without the permission see the Export action disabled, with a tooltip explaining that the permission is missing.

Run custom code after an export

The system raises the AfterExportListingEvent event (available in the Kentico.Xperience.Admin.Base namespace) after every successful export, on every listing page that has the export enabled. Handle the event for scenarios such as auditing or compliance logging, where you need a record of who exported which data and when.

The event uses the asynchronous event model described for content events. To handle the event, create a class implementing the IAsyncEventHandler<AfterExportListingEvent> interface (from the CMS.Base namespace) and define its HandleAsync method. Access the event object's Data property to get an ExportListingEventData object with the following properties:

Property

Description

ListingPageTypeName

The full type name of the listing page whose data was exported. For example, exports performed in the Forms application report Kentico.Xperience.Admin.DigitalMarketing.UIPages.FormSubmissionsTab.

Use this value to distinguish between listing pages when multiple applications have the export enabled.

UserID

The ID of the user who performed the export. The value is 0 if the system cannot determine the user.

Timestamp

The local time of the server at which the export was completed.

All properties of the event data are read-only and the event does not provide access to the exported file or its content. Handlers cannot modify or cancel the export.

C#Log listing data exports
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

using CMS.Base;

using Kentico.Xperience.Admin.Base;

// Handler triggered after a user exports the data of a listing page
public class ExportAuditHandler : IAsyncEventHandler<AfterExportListingEvent>
{
    private readonly ILogger<ExportAuditHandler> logger;

    // Gets an ILogger instance using constructor DI
    public ExportAuditHandler(ILogger<ExportAuditHandler> logger)
    {
        this.logger = logger;
    }

    public Task HandleAsync(AfterExportListingEvent asyncEvent, CancellationToken cancellationToken)
    {
        // Accesses the strongly-typed event data
        ExportListingEventData data = asyncEvent.Data;

        // Logs the details of the export
        // The 'name' value of the EventId object sets the event code in the Xperience event log
        logger.LogInformation(
            new EventId(0, "EXPORT"),
            "User {UserID} exported '{ListingPageTypeName}' at {Timestamp}.",
            data.UserID,
            data.ListingPageTypeName,
            data.Timestamp);

        return Task.CompletedTask;
    }
}

The example uses the Information log level when logging events. Information level events are not written to the Xperience event log by default. To include them, set the log level for the event category in the XperienceEventLog logging provider. See Configure logging.

Register the handler by calling the AddEventHandler<TAsyncEvent, THandler>() extension method on IServiceCollection⁠ (opens in a new tab). Handlers are registered as singletons, so you can resolve any required services using constructor injection.

C#Program.cs
using CMS.Base;

using Kentico.Xperience.Admin.Base;

// ...

// Registers the handler for the AfterExportListingEvent event
builder.Services.AddEventHandler<AfterExportListingEvent, ExportAuditHandler>();

Reference – ListingExportConfiguration

Property

Description

FileName

Name of the exported file, without the extension. The system appends the .csv extension.

Derived from the localized name of the listing page, if not specified – characters not allowed in file names and white space are replaced by hyphens (for example, the Event log page produces Event-log.csv).

MaxRowCount

Maximum number of rows a single export can contain. Defaults to 50,000.

When the listing contains more rows than the limit, the Export action is disabled and displays a tooltip stating that the limit was exceeded. Users need to narrow the listing down using the search box or the filter before they can export.

Note: Be careful when setting the limit. All exported rows are retrieved and held in memory before the file is built, so a high limit increases the memory consumption and the time users wait for the download, and may cause the request to time out. Keep the limit as low as your scenario allows and let users narrow the listing down via filters instead.

Permission

Permission required to export the data. Defaults to SystemPermissions.VIEW. Every user who can display the listing can also export it. See Require a dedicated export permission.

AdditionalColumns

Columns included in the export without being displayed in the listing table. Exported after the table columns. Defaults to an empty collection. See Export columns that are not displayed in the table.

DataModifier

Function that transforms the whole exported result set before it is written to the file. Defaults to null, i.e., the data is exported as retrieved. See Transform exported values.

On this page

  • Enable data export
  • Exported file contents
  • Export values of cells rendered by React components
  • Export columns not displayed in the listing
  • Transform exported values
  • Enable the export on an existing listing page
  • Require a dedicated export permission
  • Run custom code after an export
  • Reference – ListingExportConfiguration