---
title: Export listing data
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

[Listing pages](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/listing-ui-page-template.md) 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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/listing-ui-page-template/add-listing-actions.md#add-header-and-item-actions), and opens a dialog where users select which columns to include.

![Export action in the header of the Event log listing](https://docs.kentico.com/docsassets/documentation/export-listing-data/listing_export_action.png "Export action in the header of the Event log listing")

## Enable data export

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

```csharp title="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](#reference-listingexportconfiguration) below. To adjust any of the defaults, pass a `ListingExportConfiguration` object instead.

```csharp title="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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/listing-ui-page-template/display-and-format-listing-data.md#format-displayed-data) set to _false_.
- The [additional export columns](#export-columns-not-displayed-in-the-listing), 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](#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](https://docs.kentico.com/documentation/developers-and-admins/customization/admin-ui-localization.md), and the result is passed to the column's `Formatter`. Additionally:

- Columns rendered by a [React component](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/listing-ui-page-template/display-and-format-listing-data.md#add-react-components-to-cells) 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](#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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/listing-ui-page-template/display-and-format-listing-data.md#add-react-components-to-cells) 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`.

```csharp title="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](https://docs.kentico.com/documentation/developers-and-admins/customization/integrate-custom-code.md#enable-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](#transform-exported-values) 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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/listing-ui-page-template/display-and-format-listing-data.md#format-displayed-data).

```csharp title="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.

```csharp title="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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-extenders.md) of the page. Extenders access the configuration of the listing through their `Page` property.

```csharp title="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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-permission-checks.md) – typically a custom permission dedicated to exporting, which lets you grant read access to a listing without allowing bulk extraction of its data.

```csharp title="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](https://docs.kentico.com/documentation/developers-and-admins/customization/handle-global-events/handle-content-events.md). 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_.<br>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.

```csharp title="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;
    }
}
```

> **Note:** The example uses the `Information` log level when logging events. `Information` level events are not written to the [Xperience event log](https://docs.kentico.com/documentation/developers-and-admins/configuration/event-log.md) by default. To include them, set the log level for the event category in the _XperienceEventLog_ logging provider. See [Configure logging](https://docs.kentico.com/documentation/developers-and-admins/development/logging.md#configure-logging).

Register the handler by calling the `AddEventHandler<TAsyncEvent, THandler>()` extension method on [IServiceCollection](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection). Handlers are registered as singletons, so you can resolve any required services using constructor injection.

```csharp title="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.<br>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.<br>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.<br>**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](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-permission-checks.md) 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](#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](#export-columns-not-displayed-in-the-listing).                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| 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](#transform-exported-values).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
