---
title: Creating extenders for module interface pages
related:
  - https://docs.kentico.com/13/custom-development/creating-custom-modules.md
  - https://docs.kentico.com/13/custom-development/creating-custom-modules/manually-creating-the-interface-for-custom-modules.md
---

> 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).

When building the user interface of a custom module, the Portal Engine development approach allows you to quickly create pages based on common patterns directly in your browser (see the example in [Creating custom modules](https://docs.kentico.com/13/custom-development/creating-custom-modules.md) for more information). However, if you need custom functionality or content adjustments that you cannot configure through the properties of the available UI page templates, you need to develop an _**extender**_ and assign it to the corresponding UI element.

Extenders allow you to prepare custom code that the system executes when initializing the controls used on a page or the page itself. For example, you can use extenders to dynamically modify control properties or assign handler methods to events that occur during the page or control life cycle.

> **Info:** **Development approaches for custom user interface pages**
>
> Before you start developing a user interface page that requires customization, carefully consider which of the following approaches best fits your needs:
>
> - **Pages with Portal Engine templates and extenders** – allows you to leverage the templates and automatic features of the Portal Engine and add custom code-behind logic using extenders. Suitable for pages that share a common pattern with other pages (for example object listing pages), but require one or more custom adjustments.
> - **Manual user interface pages** – for pages that require heavy customization, it may be easier to implement the entire page as a separate web form. Gives you full control over the content and logic of the page, but you need to develop the whole page manually. See [Manually creating the interface for custom modules](https://docs.kentico.com/13/custom-development/creating-custom-modules/manually-creating-the-interface-for-custom-modules.md) for more information about this approach.

## Implementing extenders

To develop a new extender, you need to create a class in the web project and write the required code:

1. Open your Xperience administration solution in Visual Studio (using the **WebApp.sln** file).

2. Add a new class in your module's code folder:

   - If your module uses a separate assembly, add the extender class into the corresponding project.
   - For modules that do not have their own project, use the **CMSModules/** folder in the **Old\_App\_Code** directory.

   > **Tip:** **Extender naming convention**: We recommend using the _Extender_ suffix in the name of the class and the corresponding file.

3. Make the class inherit from an appropriate base class:
   - **ControlExtender** – for extenders that target a control. Set the generic parameter to the type of the control that you are extending (see [Extender types](#extender-types)).
   - **PageExtender** – for extenders that apply to pages. Use **CMSPage** as the type in the generic parameter.

     > **Info:** The extender base classes are located under the **CMS.Base.Web.UI** namespace.

4. Override the **OnInit()** method in the extender class.

   - You can add any custom initialization code for the page or control, for example assign handler methods to events.
   - When extending a control, you can access a strongly typed instance of the given control through the **Control** property of the extender class.

5. Add any other code required to implement your custom functionality (such as the handler methods assigned to events in the _OnInit_ method).

6. Save the file and Rebuild the solution.

```csharp title="Basic example"

using CMS.Base.Web.UI;
using CMS.UIControls;

public class CustomExtender : ControlExtender<UniGrid>
{
    public override void OnInit()
    {
        // Sets a property of the extended page's UniGrid control
        Control.ShowObjectMenu = false;
    }
}

```

## Assigning extenders to module pages

After you implement the extender class, you need to assign it to the UI element representing the related page in the module's interface:

1. Open the **Modules** application and edit the related module.
2. Select the **User interface** tab.
3. Use the tree to select the UI element representing the page that you wish to extend:
4. Open the element's **Properties** tab:
5. Find the appropriate **Extender** property (see [Extender types](#extender-types)).
6. Select your extender class using the property:

   - **Assembly name**

     - If you added the extender to a separate module project, choose the matching assembly\*.\*
     - For extender classes created in the _Old\_App\_Code_ folder, choose _**CMSApp**_.
   - **Class** – select the specific extender class (the value includes the class's namespace).
7. Click **Save**.

The extender is now applied to the given user interface page and the system runs the extender's initialization code when the page is loaded.

![Example of an extender assigned to a UI element with the New / Edit object page template](https://docs.kentico.com/docsassets/13/creating-extenders-for-module-interface-pages/extender_element_example.png "Example of an extender assigned to a UI element with the New / Edit object page template")

## Extender types

The following table lists the default UI page templates that you can customize using extenders:

| UI page template                             | Primary control                             | Extender property (Properties tab of UI elements)                |
| -------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------- |
| Object listing<br>Listing with site selector | UniGrid                                     | Listing -> Grid extender                                         |
| Listing with general selector                | UniGrid<br>UniSelector                      | Listing -> Grid extender<br>Dialog settings -> Selector extender |
| New / Edit object                            | UIForm                                      | Edit -> Edit extender                                            |
| Edit bindings                                | UniSelector                                 | Binding -> Edit extender                                         |
| Vertical tabs<br>(and other tab templates)   | UITabs                                      | Tabs -> Tab extender                                             |
| Custom control                               | Any base class of the selected user control | Control settings -> Control extender                             |

Control extenders must inherit from the **ControlExtender** class, where _TControl_ is a control used on the given UI template. To assign a control extender, edit the module on the **User interface** tab, select the given UI element and use the appropriate extender property on the **Properties** tab.

> **Info:** **Note**: The extender control type does not necessarily need to match the primary control related to the property through which the extender is assigned. If the type does not match, the extender automatically searches the underlying control hierarchy for the first control of the matching type. For example, you can create a _UIForm_ extender for a different type of control without knowing how deep in the control hierarchy the form is located.

If you are developing an extender for the page itself (not a specific control), the extender must inherit from **PageExtender**. To assign a page extender, edit the module on the **User interface** tab, select a UI element and use the **Code behind -> Extender** property on the **Properties** tab. You can assign page extenders to UI elements with any type of page template.

## Example - Extending a module's listing page

The following example demonstrates how to create an extender that adds the following features to the listing page of the sample _Company overview_ module:

- A header action button that allows users to send email to all listed offices
- List actions for sending email to specific offices
- An indicator that shows which offices are currently open, based on the time zone of each office's location

> **Note:** To follow the example, you first need to create the **Company overview** custom module according to the instructions in [Creating custom modules](https://docs.kentico.com/13/custom-development/creating-custom-modules.md).

### Adding class fields

Before you can create the extender, you need to expand the _Office_ class used within the _Company overview_ module to include fields for storing an email address and a reference to a time zone.

1. Open the **Modules** application and edit the **Company overview** module.
2. Select the **Classes** tab and edit the **Office** class.
3. Open the **Fields** tab and create a **New field**:

   - **Field type**: Standard field
   - **Field name**: OfficeEmail
   - **Data type**: Text
   - **Field caption**: Office email address
   - **Form control**: Email
4. Click **Save**.
5. Create another **New field**:

   - **Field type**: Standard field
   - **Field name**: OfficeTimeZoneID
   - **Data type**: Integer number
   - **Reference to**: Time zone
   - **Reference type**: Not required
   - **Field caption**: Office time zone
   - **Form control**: Time zone selector (select via the _(more items...)_ option)
6. Click **Save**.
7. Switch to the **Code** tab of the class editing interface.
8. Click **Save code**.

The _Office_ class now contains the _OfficeEmail_ and _OfficeTimeZoneID_ fields, which allow users to set an email address and select a time zone when editing offices. By saving the new code of the class, you also updated the _OfficeInfo_ API class in the web project (allows the system to handle the new fields).

**Note**: You need to Rebuild the Xperience solution in Visual Studio to process the changes in the code.

### Updating the listing grid definition

Because the example extends the listing page of the _Company overview_ module, you need to update the grid definition that specifies the page's content.

By default, the grid definition used by the listing page of the Company overview module is stored in the _**\~/App\_Data/CMSModules/CompanyOverview/UI/Grids/CompanyOverview\_Office/default.xml**_ file. Edit the grid definition and change it to the following XML code:

```xml

<?xml version="1.0" encoding="utf-8" ?>
<grid>
    <actions>
        <action name="edit" caption="$General.Edit$" fonticonclass="icon-edit" fonticonstyle="allow" />
        <action name="#delete" caption="$General.Delete$" fonticonclass="icon-bin" fonticonstyle="critical" confirmation="$General.ConfirmDelete$" />
        <action name="email" commandargument="OfficeEmail" caption="Send email" fonticonclass="icon-message" />
    </actions>
    <columns>
        <column source="OfficeDisplayName" caption="Office name" wrap="false" localize="true">
            <filter type="text" size="200" />
        </column>
        <column source="OfficeAddress" caption="Address" width="35%" />
        <column source="OfficeTimeZoneID" caption="Is Open (Office hours 9 AM - 5 PM)" externalsourcename="officehours" />
        <column source="OfficeEmail" caption="Email address" width="100%" />
    </columns>
    <options>
        <key name="DisplayFilter" value="true" />
    </options>
</grid>

```

The updated grid definition contains:

- A new _**email**_ action, which will be handled by the extender.
- A column with the **OfficeTimeZoneID** field as its source. The extender will handle the _**officehours**_ source name and provide the output of the column.
- A column displaying the **OfficeEmail** value.

See also: [Reference - UniGrid definition](https://docs.kentico.com/13/custom-development/extending-the-administration-interface/ui-controls/unigrid/reference-unigrid-definition.md)

### Creating the extender class

1. Open your project in Visual Studio.
2. Create a new class named _**OfficeListExtender.cs**_ in the module's code folder (by default _\~/Old\_App\_Code/CMSModules/CompanyOverview_).
3. Add the following _using_ statements to the start of the code:

   ```csharp

   using System;
   using System.Linq;
   using System.Web.UI.WebControls;

   using CMS.Base;
   using CMS.Base.Web.UI;
   using CMS.Base.Web.UI.ActionsConfig;
   using CMS.Globalization;
   using CMS.Helpers;
   using CMS.UIControls;

   ```
4. Use the following code for the class:

   ```csharp

   public class OfficeListExtender : ControlExtender<UniGrid>
   {
       /// <summary>
       /// Adds custom code that occurs during the initialization of the extended control.
       /// </summary>
       public override void OnInit()
       {
           // Calls a method that adds a custom header action button
           InitHeaderActions();

           // Registers a method that handles the functionality of header actions
           Control.HeaderActions.ActionPerformed += HeaderActions_ActionPerformed;

           // Assigns handlers to the UniGrid control's OnAction and OnExternalDatabound events
           Control.OnAction += Control_OnAction;        
           Control.OnExternalDataBound += Control_OnExternalDatabound;        
       }

       /// <summary>
       /// Initializes header actions.
       /// </summary>
       private void InitHeaderActions()
       {
           // Prepares the "Email all" header action
           HeaderAction mailAllButton = new HeaderAction
           {
               ButtonStyle = ButtonStyle.Default,
               Text = ResHelper.GetString("email.all"),

               // Sets a name that identifies the action when handling the 'ActionPerformed' event
               CommandName = "emailall",                       
           };
           Control.HeaderActions.AddAction(mailAllButton);
       }

       /// <summary>
       /// Performs header actions.
       /// </summary>
       private void HeaderActions_ActionPerformed(object sender, CommandEventArgs args)
       {
           switch (args.CommandName.ToLowerCSafe())
           {            
               // Runs when a user clicks the "Email all" header action button
               case "emailall":
                   // Gets a string containing all office emails separated by commas
                   string officeEmails = String.Join(",", CompanyOverview.OfficeInfo.Provider.Get().Select(o => o.OfficeEmail));

                   // Prepares a mailto link for all office emails
                   string officesMailtoLink = HTMLHelper.HTMLEncode(String.Format("mailto:" + officeEmails));

                   // Opens the mailto link when the header action button is clicked
                   string scriptText = "win = window.open('" + officesMailtoLink + "', 'emailWindow');" +
                                       "if (win && win.open && !win.closed) win.close();";
                   ScriptHelper.RegisterStartupScript(Control.Page, typeof(string), "OpenAllMailToLink", ScriptHelper.GetScript(scriptText));
                   break;
           }
       }

       /// <summary>
       /// Handles the UniGrid control's OnAction event.
       /// </summary>
       /// <param name="actionName">The name of the UniGrid action that was used.</param>
       /// <param name="actionArgument">The value of the data source column in the UniGrid row for which the action was used.</param>
       private void Control_OnAction(string actionName, object actionArgument)
       {
           if (actionName == "email")
           {
               // Gets the email of the office for which the "Send email" action was used
               string officeEmail = HTMLHelper.HTMLEncode(ValidationHelper.GetString(actionArgument, ""));

               // Opens a mailto link
               string scriptText = "win = window.open('mailto:" + officeEmail + "', 'emailWindow');" + 
                                   "if (win && win.open && !win.closed) win.close();";
               ScriptHelper.RegisterStartupScript(Control.Page, typeof(string), "OpenMailToLink", ScriptHelper.GetScript(scriptText));
           }
       }

       /// <summary>
       /// Handles the Unigrid control's OnExternalDataBound event.
       /// </summary>
       private object Control_OnExternalDatabound(object sender, string sourceName, object parameter)
       {
           if (sourceName == "officehours")
           {
               // Gets the ID of the time zone assigned to the office
               int timeZoneId = ValidationHelper.GetInteger(parameter, 0);

               // Loads a TimeZoneInfo object representing the office's time zone
               CMS.Globalization.TimeZoneInfo timeZone = CMS.Globalization.TimeZoneInfo.Provider.Get(timeZoneId);

               // Gets the current time, converted to the office's time zone
               DateTime currentDateTime = 
                   TimeZoneHelper.ConvertTimeZoneDateTime(DateTime.UtcNow, CMS.Globalization.TimeZoneInfo.Provider.Get("GMT_UTC"), timeZone, false);

               // Formats the office's local time into a string
               string officeLocalTime = "(Local time: " + currentDateTime.ToString("h:mm tt") + ")";

               // Checks whether the office's local time is within office hours and displays the result in the column
               if (currentDateTime.TimeOfDay < new TimeSpan(9, 0, 0) || currentDateTime.TimeOfDay > new TimeSpan(17, 0, 0))
               {
                   return "<span class=\"StatusDisabled\">" + ResHelper.GetString("general.no") + "</span> " + officeLocalTime;
               }
               else
               {                
                   return "<span class=\"StatusEnabled\">" + ResHelper.GetString("general.yes") + "</span> " + officeLocalTime;
               }
           }

           return parameter;        
       }   
   }

   ```
5. Save the class and **Rebuild** the solution.

The code provides the functionality of the listing page customizations.

> **Info:** For more information about the UniGrid _OnAction_ and _OnExternalDatabout_ events, see: [UniGrid](https://docs.kentico.com/13/custom-development/extending-the-administration-interface/ui-controls/unigrid.md)

### Assigning the extender to a UI element

To apply the extender to the listing page of the _Company overview_ module:

1. In the **Modules** application, edit the **Company overview** module.
2. Select the **User interface** tab.
3. Select the **CMS -> Administration -> Custom -> Company overview** element in the UI element tree.
4. Open the **Properties** tab of the element.
5. Scroll down to the **Listing** category and set the **Grid extender** property:
   - **Assembly name**: CMSApp
   - **Class**: OfficeListExtender
6. Click **Save**.

![Assigning a Grid extender to a UI element that uses the Object listing page template](https://docs.kentico.com/docsassets/13/creating-extenders-for-module-interface-pages/module_extender_assigning.png "Assigning a Grid extender to a UI element that uses the Object listing page template")

The extender is now applied to the listing page of the _Company overview_ module. To view the results, open the **Company overview** application (under the **Custom** category). The **Email all** header action button and the **Send email** actions of individual offices open _mailto_ links, and the **Is Open** column shows which offices currently have office hours. You need to create offices with email address values and an assigned time zone to fully try out the customizations.

![The extended listing page of the Company overview module, with email actions and a custom formatted Is Open column](https://docs.kentico.com/docsassets/13/creating-extenders-for-module-interface-pages/module_extended_listing.png "The extended listing page of the Company overview module, with email actions and a custom formatted Is Open column")
