ExpressionEngine Docs

v3 Add-on Migration

This guide is an overview of the changes you will need to make to have your add-ons up and running under 3.0. The most notable changes to add-ons are the addon.setup.php file and the new Control Panel Style Reference.

There are some deprecated features and code, so be sure to check the Developer Logs in the control panel for messages regarding method deprecation to make sure your add-on is not calling any deprecated code.

If you’re just getting started, we recommend reviewing some of the new service documentation as well as the following pages:

Required Changes

Create an addon.setup.php File

In ExpressionEngine 3.0, all add-ons must have a file called addon.setup.php. It defines basic information about your add-on, such as its name, version, and some namespace. It is a required file, without it your add-on will not be recognized.

To get started, create a file called addon.setup.php in the main directory of your add-on. Then fill it in with this template, changing the values to match your add-on:


return array(
  'author'      => 'Example, Inc',
  'author_url'  => '',
  'name'        => 'Hello World',
  'description' => 'Displays a friendly "Hello world!" message.',
  'version'     => '2.0.0',
  'namespace'   => 'Example\HelloWorld'

Your add-on should now be visible in the Add-Ons section of the control panel. See addon.setup.php file for full documentation of available keys.

Component Changes

Depending on what parts your add-on has, you should also review the following pages:

In-App Documentation

Add-ons can now provide some in-app documentation by including a file in the main directory of the add-on. This file will be parsed as markdown. Please note that the first h1 heading will be ignored. All other headings will automatically be listed in the sidebar.

Extension Changes

Before going to update your extension, please check the Extension Hooks listing and check for any changes to the hooks you’re using. There are several new hooks in ExpressionEngine 3, as well as some changes and removals.

Custom Settings Pages

If you are using the settings method to show your extension settings you should not need to make any changes. However, if you’re using the settings_form method then you will need to update your extension to the new control panel style.

Please refer to the Module Control Panel changes for details on how to create these. The form should submit to addons/settings/<addonName>/save, where <addonName> is the folder name of your add-on.

Plugin Changes

Remove Plugin Info

The $plugin_info array is no longer needed and will be ignored by 3.0. All of this data should already be in your addon.setup.php file and will be read from there.

Typography Flag

To make your plugin available as a typography plugin, you must add a plugin.typography key to your addon.setup.php file array, and assign it a value of TRUE:

'plugin.typography' => TRUE

Install Plugin

All plugins must be installed via the Add-On Manager before they can be used. If your plugin is not working as expected, double check to make sure it is installed.

Syntax Changes

This section is an overview of syntax changes for third party integration with the new ExpressionEngine.

General Syntax Changes

ExpressionEngine 3.0 adopts PSR-1 and PSR-4, which means StudlyCaps for class names, camelCase for method names, namespaces, and autoloading.


cp_url(...) has been deprecated, please use ee('CP/URL', '...'). For the full documentation see Control Panel Styles.

Model Service

The model service helps smooth out interacting with ExpressionEngine’s data types by providing an API that mimics their natural language description as closely as is feasible. You do not query for channel_data joined on channel_titles; instead, you simply get a channel entry:

$oscar = ee('Model')->get('Member')
  ->filter('screen_name’, ‘Oscar')

$oscar->bio = 'I got this.';

For the full documentation see Model Service.

Fieldtype Changes

Add Fieldtypes to addon.setup.php

For ExpressionEngine 3, you should enumerate your fieldtypes in your addon.setup.php file. To start, create a new ‘fieldtypes’ array in you addon.setup.php file. For each fieldtype, list its shortname as a key and then an array of its name:

'fieldtypes' => array(
  'rte' => array(
    'name' => 'Rich Text Editor'

Define Compatibility

Changing from one field type to another can lead to unexpected results, as for example switching from a file field to a date field. On the other hand, many fieldtypes contain similar data, such as text.

If your fieldtypes stores a common datatype, you should list it in its compatibility field:

'fieldtypes' => array(
  'rte' => array(
    'name' => 'Rich Text Editor',
    'compatibility' => 'text'

Compatibility can be any string values, the commonly used ones are listed in this section of the addon.setup.php documentation.

Update Settings Format

The format for the fieldtype settings pages has been changed to the more consistent Shared Form View format. Please refer to the Fieldtype Settings documentation for an example.

Use the Validation Service in Settings

ExpressionEngine 3.0 comes with a new Validation Service. Your fieldtypes settings validation should return a result object from this service:

function validate_settings($settings)
  $validator = ee('Validation')->make(array(
      'size' => 'required|numeric'

  return $validator->validate($settings);

Module Changes

Most of the changes to modules in ExpressionEngine 3 will be in the control panel files. All parsing code should be backwards compatible.

A fictional Fortune Cookies module is used in the following examples.

3.0 Module Overview

The module changes in ExpressionEngine 3 are mostly related to the control panel redesign. Your parsing code should remain identical, which means you can focus just on updating the control panel.

New URL Logic

The Control Panel URLs for your module follow the pattern:


For example, if we had a fortune cookie module with a method to list our cookies its URL might be:


Like 2.x the routing is automatic; all public methods in your mcp.package_name.php are automatically routed. We will also pass any arguments to your method found in the url. For this URL:


We would need to have the following method signature:

public function edit_cookie($id) {...}

Control Panel File (mcp.package_name.php)

If your module does not have a control panel, you still need an blank mcp file in the format:


class Package_name_mcp
    var $version = '1.0';


Control Panel Output, Breadcrumbs, and Headings

There are two ways to output your control panels. You may either return an HTML string, or you may return an associative array.

If you return a string that data will be used in the “body” section of the Control Panel layout inside our Add-On Manager. The breadcrumb will default to Add-On Manager / Your Add-On Name and the heading will default to Your Add-On Name Configuration. In our fortune cookie module example we would have Add-On Manager / Fortune Cookies as the breadcrumb and Fortune Cookie Configuration as the heading.

If you return an associative array it must contain the key body and may contain the keys breadcrumb, and heading. The body key should contain an HTML string which will be used in the “body” section of the Control Panel layout inside our Add-On Manager. The breadcrumb key should contain an associative array of URLs as keys and strings for the bodies. The header value will be added as the final segment in the breadcrumb. The heading key should contain a string for use as the heading (i.e. Fortune Cookie Management).

If your add-on needs a sidebar use the Sidebar Service.

$url = ee('CP/URL', 'addons/settings/fortune_cookie')->compile();

return array(
  'body'       => ee('View')->make('fortune_cookie:index')->render($vars),
  'breadcrumb' => array(
    $url => lang('fortune_cookie_management')
  'heading'  => lang('edit_fortune_cookie'),

Publish Tab File (tab.package_name.php)

We have made a few changes to the Publish Tab API. We have renamed the methods we call within the class, and a few have changes to their parameters:

A tab’s save() method is called during a ChannelEntry entity’s afterSave event. Likewise a tab’s delete() method is called during a ChannelEntry entity’s beforeDelete event.

Display Tools

We have a few display tools available that are useful when creating your output.

The Table Service

Tables are the most common way to view and navigate data in the ExpressionEngine control panel. Since tables share a lot of common functionality, we’ve abstracted most of it out to a Table service to handle tasks such as displaying the table markup, and sorting and filtering tabular content:

$table = ee('CP/Table', array('autosort' => TRUE, 'autosearch' => FALSE, 'limit' => 20));
    'manage' => array(
      'type'  => Table::COL_TOOLBAR
      'type'  => Table::COL_CHECKBOX


See CP/Table Service for full documentation.

The Pagination Service

Adding pagination to the control panel is a common task and we created a pagination service to assist. This service handles all the mathematical calculations. All you need is the number of items you are going to paginate and a URL object:

$base_url = ee('CP/URL', 'publish/edit');
$pagination = ee('CP/Pagination')->make($total_count)

See CP/Pagination Service for full documentation.

The CP/Alert Service

Alerts are for providing feedback on an action and calling attention to warnings or errors. We have created an Alert Service for creating alerts in your code. For example:




See CP/Alert Service for full documentation.

The Shared Settings Form

Forms are created by creating an array of field descriptions. This keeps the view code consistent and modular. Below is an example of a simple form with a text input and a checkbox:

$vars['sections'] = array(
          'title' => 'tool_set_name',
          'desc' => 'tool_set_name_desc',
          'fields' => array(
              'toolset_name' => array(
                  'type' => 'text',
                  'value' => $toolset_name,
                  'required' => TRUE
          'title' => 'choose_tools',
          'desc' => 'choose_tools_desc',
          'fields' => array(
              'tools' => array(
                  'type' => 'checkbox',
                  'choices' => $tools,
                  'value' => $toolset['tools'],
                  'wrap' => FALSE


<?php $this->embed('ee:_shared/form')?>

See Shared Form View for full documentation.


Under 3.0 modals belong to a specific spot in the Control Panel’s DOM, and that place isn’t accessible from a module’s view. To solve that we have created a modal service. There are two basic calls to use it within your view files, ee('CP/Modal')->startModal($name); and ee('CP/Modal')->endModal();. Everything between those two lines will be be stored in the modals block and output in the correct spot of the DOM.

<?php ee('CP/Modal')->startModal($name); ?>
<div class="modal-wrap modal-test">
  <div class="modal">
      <div class="col-group">
          <div class="col w-16">
              <div class="box">
<?php ee('CP/Modal')->endModal(); ?>

See CP/Modal Service for full documentation.

PHP Related Guidelines

External Links

To protect the users control panel URL from ending up in web server referrer logs, use Cp::masked_url from the Control Panel Class:


Will result in:

Internal Links

Internal control panel links should be generated with the ee('CP/URL') helper function which generates control panel URLs based on parameters passed in.

For example, to link to the publish controller, pass it in as the first parameter to ee('CP/URL'):

ee('CP/URL', 'publish');

To link to a particular method in a controller, add it to the parameter with a slash:

ee('CP/URL', 'publish/method_name');

If the method accepts arguments, they can be passed in cleanly by adding them on the end separated by a slash:

ee('CP/URL', 'publish/method_name/5');

If the link requires any other GET parameters, they can be passed in via an associative array in the second parameter:

ee('CP/URL', 'publish/edit', array('filter_by_channel' => $channel_id));

See CP/URL Service for the full documentation.

Control Panel Constants