---
title: Add listing actions
---

> 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 invoke [page commands](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-commands.md) from individual rows, from the page header, and over multiple items selected by the user at once. Add the actions within an override of the `ConfigurePage` method, either on the listing page itself or on its [page extender](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-extenders.md).

## Add row actions

The listing template allows you to assign a page command that gets invoked when users select a row in the listing.

A typical use case for this is opening an item's view or edit page (e.g., for users, contacts). For this purpose, the system provides the `PageConfiguration.AddEditRowAction<TInfoEditPage>` extension method. The method generates a link to an [edit page](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/reference-ui-page-templates/edit-ui-page-template.md) for each item (by ensuring the corresponding object's ID in the URL). Specify the `System.Type` of the corresponding edit layout page as the method's generic parameter.

See the _Page URLs and routing_ section on [UI pages](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages.md) for more information about generating URL structures for admin pages.

## Add header and item actions

The listing page allows you to add actions ([page commands](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-commands.md)) to the page header (displayed to the left of the search box) and to each item in the grid (displayed in the **Actions** column).

Add actions to the listing by calling the following methods on either `PageConfiguration.HeaderActions` or `PageConfiguration.TableActions`:

- `AddDeleteAction` – adds a delete action to individual items in the list. The base class provides a `Delete` page command that deletes items based on an object's ID.

  ```csharp title="Delete page command provided by the ListingPage base class"
  [PageCommand]
  public override Task<ICommandResponse<RowActionResult>> Delete(int id) => base.Delete(id);
  ```
- `AddLink<TPage>` – adds a link to a specified page in the admin UI. Supply the `System.Type` of the target page as the method's generic parameter.
- `AddCommand` – adds a [page command](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-commands.md) to the page header or an item's _Actions_ column.
- `AddCommandWithConfirmation` – adds a page command in exactly the same way as `AddCommand`, but guarantees that users are asked to [confirm the action](#raise-a-confirmation-prompt) before the command is invoked. Both methods accept the same parameters and place the same requirements on the command's handler.

Commands are referenced by the name of their handler method, not by a delegate. Pass `nameof(YourHandler)` as the `commandName` parameter and implement the handler on the listing page class (or on its [page extender](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-extenders.md)), annotated with the `PageCommand` attribute.

The required handler signature depends on where the action is displayed:

- **Item actions** (added to `PageConfiguration.TableActions`) are invoked for a single listed item. The system passes the identifier of the row whose action was selected (the value of the listed object type's ID column) to the handler. The handler returns `ICommandResponse<RowActionResult>`, which instructs the client what to refresh once the command finishes.

  ```csharp title="Item action handler signature"
  [PageCommand]
  public async Task<ICommandResponse<RowActionResult>> MyItemAction(int id, CancellationToken cancellationToken)
  ```
- **Header actions** (added to `PageConfiguration.HeaderActions`) are not bound to any listed item and therefore receive no identifier. Header actions are rendered as standalone buttons that do not evaluate `RowActionResult`, so their handlers return the general `ICommandResponse`.

  ```csharp title="Header action handler signature"
  [PageCommand]
  public async Task<ICommandResponse> MyHeaderAction(CancellationToken cancellationToken)
  ```

Apart from an optional `CancellationToken` parameter and an optional [confirmation model](#raise-a-confirmation-prompt) parameter, command handlers can declare at most one parameter, which receives the data sent with the command. You can declare the parameters in any order.

Set the `PageCommand` attribute's `Permission` property to require a [permission](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-permission-checks.md) for the action's invocation.

```csharp
[PageCommand(Permission = SystemPermissions.UPDATE)]
```

### Implement command actions

Actions can either invoke a page command that the `ListingPage` base class already implements, as `AddDeleteAction` does with the `Delete` command, or a command you implement yourself. To implement your own, declare a handler annotated with the `PageCommand` attribute and reference it from `AddCommand` or `AddCommandWithConfirmation`.

Item action handlers construct their `ICommandResponse<RowActionResult>` using the `ResponseFrom` method, which is available both on listing pages and on page extenders. You can optionally chain the message extension methods described under [UI page commands](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-commands.md#formulate-command-response).

```csharp
return ResponseFrom(new RowActionResult(reload: true))
            .AddSuccessMessage("The item was updated.");
```

`RowActionResult` instructs the client what to refresh once the command finishes. Both of its properties are read-only and set via the constructor:

| Parameter    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reload`     | Reloads the data of the listing's table – the system re-executes the listing's data query and re-renders the grid, including the [action state evaluators](#actionstateevaluator) of all rows. Set to `true` whenever the command changes data displayed in the listing.<br>Set to `false` when the listing's content is unaffected by the command (for example, when the command only sends an email, or when it failed and made no changes). The grid keeps displaying the data it loaded before the action was invoked.                                                                                                                                       |
| `refetchAll` | Re-fetches the data of all UI page templates currently rendered on the page, not just the listing's table. Use it when the command changes state displayed outside of the listing itself, for example a counter in a parent template, a header, or a sidebar.<br>Because the listing template is one of the re-fetched templates, its data is reloaded as well. `refetchAll` therefore takes precedence over `reload`, and setting both to `true` has the same effect as setting `refetchAll` alone.<br>Defaults to `false`. Prefer `reload` unless the action's impact reaches outside the listing – re-fetching all templates is significantly more expensive. |

> **Note:** The `reload` and `refetchAll` flags are only processed for actions in `PageConfiguration.TableActions` and `PageConfiguration.MassActions`. Actions in `PageConfiguration.HeaderActions` do not evaluate the returned result. To refresh the page after a header action, return `NavigateTo(url, refetchAllTemplates: true)` instead, where `url` is the path of the page to display (for example, the listing's own path obtained from `IPageLinkGenerator`).

#### Example – custom item action

The following example adds a _Disable_ action to each item of a listing of `MemberInfo` [objects](https://docs.kentico.com/documentation/developers-and-admins/customization/object-types.md). The example assumes an `IInfoProvider<MemberInfo>` service retrieved into the `memberProvider` field via dependency injection.

```csharp title="Adding and handling a custom item action"
using System.Threading.Tasks;

using CMS.DataEngine;
using CMS.Membership;

using Kentico.Xperience.Admin.Base;

// ...
// Declaration of the containing class and the 'IInfoProvider<MemberInfo> memberProvider' field
// ...

public override Task ConfigurePage()
{
    PageConfiguration.TableActions
        // References the 'Disable' page command by its name
        .AddCommand(new AddCommandParameters(commandName: nameof(Disable), label: "Disable")
        {
            Icon = Icons.Lock,
            Title = "Prevent the member from signing in"
        });

    return base.ConfigurePage();
}


// Handles the 'Disable' action. The system binds the identifier of the row
// whose action was invoked to the 'id' parameter.
[PageCommand(Permission = SystemPermissions.UPDATE)]
public Task<ICommandResponse<RowActionResult>> Disable(int id)
{
    var member = memberProvider.Get(id);

    if (member is null)
    {
        // The listing is out of sync with the database - reloads it to reflect the current state
        return Task.FromResult(ResponseFrom(new RowActionResult(reload: true))
                    .AddErrorMessage("The member no longer exists."));
    }

    member.MemberEnabled = false;
    memberProvider.Set(member);

    // Reloads the listing's data, which displays the member's new state.
    // 'refetchAll' is not needed - the change is contained within this listing.
    return Task.FromResult(ResponseFrom(new RowActionResult(reload: true))
                .AddSuccessMessage($"'{member.MemberName}' was disabled."));
}
```

Declare both members either directly on the listing page class, or on a [page extender](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-extenders.md) of the listing page. Page extenders access the listing's configuration through their `Page` property – use `Page.PageConfiguration.TableActions` instead of `PageConfiguration.TableActions`.

To run custom logic over multiple items at once, add a [mass action](#add-mass-actions) instead.

### Raise a confirmation prompt

You can optionally display a confirmation dialog when users select an action or link. Call the `AddCommandWithConfirmation` or `AddLinkWithConfirmation<TPage>` methods.

`AddCommandWithConfirmation` accepts the same parameters as `AddCommand` and requires the same command handler. The two methods only differ in how they behave when `ConfirmationParameters` are not set – `AddCommandWithConfirmation` falls back to a generic confirmation dialog, while `AddCommand` invokes the command without prompting the user. Setting `ConfirmationParameters` therefore raises a confirmation dialog with either method.

```csharp title="Raise a confirmation prompt on command invocation" highlight="15-16"
public override Task ConfigurePage()
{
    PageConfiguration
        .TableActions
            .AddCommandWithConfirmation
                (
                    new AddCommandParameters(commandName: nameof(DoSomething), label: "Title")
                    {
                        Icon = Icons.ArrowsCrooked,
                        Title = "Text of the tooltip.",
                        // Providing these properties raises a confirmation dialog
                        // when a user invokes the corresponding action
                        ConfirmationParameters = new ConfirmationWithContentParameters
                        {
                            Confirmation = "What do you want to do?",
                            ConfirmationButton = "This, please",
                        }
                    });

    return base.ConfigurePage();
}
```

When users invoke the action, they are prompted with the following confirmation.

![Listing action confirmation dialog](https://docs.kentico.com/docsassets/documentation/add-listing-actions/ListingConfirmationDialog.png "Listing action confirmation dialog")

Additionally, the dialog can also display a form that allows users to submit data to the corresponding command handler. For example, this can be a multiple-choice selector that determines how the back end processes the action.

![Listing action confirmation dialog with a form](https://docs.kentico.com/docsassets/documentation/add-listing-actions/ListingConfirmationDialogWithForm.png "Listing action confirmation dialog with a form")

The form must be defined within a dedicated model class and annotated with [Editing components](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/editing-components.md). [Validation rules](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-form-components/ui-form-component-validation-rules.md) are also supported. The class must be annotated with `CommandConfirmationModel`.

```csharp title="Model defining the confirmation form"
[CommandConfirmationModel]
public class ConfirmationDialogFormModel
{
    [RequiredValidationRule]
    [RadioGroupComponent(Inline = false, Order = 1,
                         Options = "option1;Option 1\r\noption2;Option 2\r\n")]
    public string ChooseOne { get; set; } = "option1";
}
```

Specify the model class as a parameter within `AddCommandWithConfirmation`. The other parameters of the method are identical to `AddCommand`.

```csharp title="Raise a confirmation prompt on command invocation" highlight="18"
public override Task ConfigurePage()
{
    PageConfiguration
        .TableActions
            .AddCommandWithConfirmation
                (
                    new AddCommandParameters(commandName: nameof(DoSomething), label: "Title")
                    {
                        Icon = Icons.ArrowsCrooked,
                        Title = "Text of the tooltip.",
                        // Providing these properties raises a confirmation dialog
                        // when a user invokes the corresponding action
                        ConfirmationParameters = new ConfirmationWithContentParameters
                        {
                            Confirmation = "What do you want to do?",
                            ConfirmationButton = "This, please",
                            // Setting the confirmation model displays a dialog with form
                            ConfirmationModel = typeof(ConfirmationDialogFormModel)
                        }
                    });

    return base.ConfigurePage();
}
```

The corresponding command handler method must expect the form model in its signature. The system automatically binds the form received from the client to the corresponding parameter.

```csharp title="Action command handler"
[PageCommand]
public Task<ICommandResponse<RowActionResult>> ActionWithFormConfirmation(ConfirmationDialogFormModel model)
{
    // Decide what to do based on the received form data...

    return Task.FromResult(ResponseFrom(new RowActionResult(reload: true))
                 .AddSuccessMessage($"You have selected {model.ChooseOne}"));
}
```

### actionStateEvaluator

Some methods optionally take an `Action` delegate that allows you to programmatically disable or modify the commands they add for items meeting your specified criteria.

```csharp title="Example - disable the delete action for specific users"
.AddDeleteAction(
    new AddDeleteActionParameters(commandName: nameof(Delete))
    {
        ActionStateEvaluator = DisableDeleteForSpecificUsers,
    });

// ...

// Disables the delete action for the default global administrator and public users
private Task DisableDeleteForSpecificUsers(ActionConfiguration actionConfiguration, IDataContainer rowData, CancellationToken cancellationToken)
{
    if (!actionConfiguration.Disabled)
    {
        var userName = Convert.ToString(rowData[nameof(UserInfo.UserName)]);

        if (String.Equals(userName, UserInfoProvider.AdministratorUserName, StringComparison.OrdinalIgnoreCase) ||
            String.Equals(userName, UserInfoProvider.PublicUserName, StringComparison.OrdinalIgnoreCase))
        {
            actionConfiguration.Disabled = true;
        }
    }
    return Task.CompletedTask;
}
```

## Add mass actions

The listing page template allows you to add actions executed upon a collection of items selected from the listing.

![Mass actions example](https://docs.kentico.com/docsassets/documentation/add-listing-actions/mass_actions.png "Mass actions example")

Mass actions appear at the top of the listing, immediately below the search bar, when users select at least one listed item.

[Page commands](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-commands.md) that implement mass actions must use the following signature:

```csharp title="Mass action method signature"
public async Task<ICommandResponse<MassActionResult>> MassActionCommand(IEnumerable<int> identifiers, CancellationToken cancellationToken)
```

where `IEnumerable<int> identifiers` is a collection of object type IDs identifying the items selected by the user. Use conventional [provider APIs](https://docs.kentico.com/documentation/developers-and-admins/api/database-table-api.md) to work with these objects.

Add mass actions using the following methods on `PageConfiguration.MassActions`:

- `AddCommand` – adds a command to execute.
- `AddCommandWithConfirmation` – adds a command with execution approved via a [confirmation prompt](#raise-a-confirmation-prompt).
- `AddActionWithCustomComponent` – adds an action that is [handled by a custom React component](#add-actions-handled-by-a-react-component).

> **Note:** Note that the `actionStateEvaluator` parameter offered by these methods is not supported by mass actions.

```csharp title="Register mass actions"
public override Task ConfigurePage()
{
    PageConfiguration
        .MassActions
            .AddCommandWithConfirmation
            (
                label: "Title",
                command: nameof(MyMassActionPageCommand),
                icon: Icons.ArrowsCrooked,
                // Providing these properties raises a confirmation
                // dialog when users invoke the added action
                confirmation: "Confirmation title",
                confirmationButton: "Confirm"
            );

    return base.ConfigurePage();
}
```

## Add actions handled by a React component

For advanced action scenarios, you can use a custom React component to handle:

- [Item and header actions](#add-header-and-item-actions) (`PageConfiguration.TableActions`, and `PageConfiguration.HeaderActions`)
- [Mass actions](#add-mass-actions) (`PageConfiguration.MassActions`)

on listing templates.

Start by preparing the React component's front end (e.g., a _.tsx_ file). The name of the component must end with the _Component_ suffix, e.g., _@acme/web-admin/CustomActionComponent_. Ensure that the component is [exported](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/prepare-your-environment-for-admin-development.md#export-custom-react-components) in your custom admin module's **entry.tsx** file.

In addition, you also need to create the following back-end classes for each action component:

- Component class inheriting from `ActionComponent<TProperties, TClientProperties>`.
  - Override the `ClientComponentName` property, and set the value to the name of the corresponding front-end component. The _Component_ suffix is automatically added to this name when loading the component.
  - Override the `ConfigureClientProperties` method to populate the component's client properties (typically from the value of the corresponding back-end properties class).
- Back-end properties class implementing `IActionComponentProperties`.
- Client properties class implementing `IActionComponentClientProperties`.

```csharp title="Custom action component"
public sealed class CustomActionComponent : ActionComponent<CustomActionProperties, CustomActionClientProperties>
{
    // The name of the front-end type implementing the action component without the "Component" suffix,
    // which is automatically added to this name.
    public override string ClientComponentName => "@acme/web-admin/CustomAction";
    
    protected override Task ConfigureClientProperties(CustomActionClientProperties clientProperties)
    {
        clientProperties.MyProperty = Properties.MyProperty;
        // ...

        return base.ConfigureClientProperties(clientProperties);
    }
}
```

To add the action in the listing template configuration, call `AddActionWithCustomComponent` on the appropriate action collection (e.g., `TableActions` or `MassActions`).

> **Note:** **Permissions and custom component actions**
>
> If you wish to implement [permission checks](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-permission-checks.md) for actions handled by a custom component, we recommend setting the `AddActionWithCustomComponent` method's `disabled` parameter (see the example below). You can also [propagate permission information](https://docs.kentico.com/documentation/developers-and-admins/customization/extend-the-administration-interface/ui-pages/ui-page-permission-checks.md#propagate-permission-information-to-client-ui-templates) to your component via members of the client properties class.

```csharp title="Register a mass action with a custom component"
private readonly IUIPermissionEvaluator uiPermissionEvaluator;

// ...

public override async Task ConfigurePage()
{
    bool userHasPermission = await UserHasUpdatePermission();

    PageConfiguration.MassActions.AddActionWithCustomComponent(
        clientComponent: new CustomActionComponent() 
        {
            Properties = new CustomActionProperties()
            {
                MyProperty = "MyValue"
            }
        },
        label: "Title",
        icon: Icons.PaperPlane,
        disabled: !userHasPermission
    );

    await base.ConfigurePage();
}

private async Task<bool> UserHasUpdatePermission()
{
    return (await uiPermissionEvaluator.Evaluate(SystemPermissions.UPDATE)).Succeeded;
}
```
