Ultra Double Secret Manual: Shared Form Part Four

I really like the Shared Form functionality. It makes sense to me. Define a data structure, pass it to a view array, and you’re done. Even wrote 3 articles explaining and showcasing how easy Shared Forms are, to boot. The reaction was not what I had thought it’d be…

People starting talking about Shared Form in the ExpressionEngine Slack channel. And, oh boy, did they have a lot to say about it, and it was all on point. The community pointed out that the Shared Form can get complicated for large forms. It can increase mental costs debugging errors. How, “nested arrays are painful” and “wow, that’s a lot of code though”. They weren’t wrong. In fact, they were so not wrong that the entire discussion was moved to a private channel (by the mods) so the problems could be talked out. Like grown ups.

The end consensus of which was, “We like the uniformity of the output, wish there were more options for customization if we’re being honest, but really don’t like having to scroll through hundreds of lines of nested array code to find the field we want to modify. Oh. And, also, the docs need work.”, which lead to someone just throwing out there, “Let’s maybe build a better way?”. So the community did just that, and the new CP\Form library is the result.

The new Shared Form View is included with ExpressionEngine 6.4 and above and allows developers to create Shared Form Views using an object interface instead of dealing with arrays.

For example:

$form = ee('CP/Form');
$field_group = $form->getGroup('General Settings');

$field_set = $field_group->getFieldSet('First Name');
$field_set->getField('first_name', 'text');

$field_set = $field_group->getFieldSet('Last Name');
$field_set->getField('last_name', 'text');

$vars = $form->toArray();
Which is equivalent to:
$vars['cp_page_title'] = lang($this->getCpPageTitle());
$vars['base_url'] = $this->url($this->getRoutePath($id));
$vars['sections'] = [
            'title' => 'first_name',
            'fields' => [
                'first_name' => [
                    'name' => 'first_name',
                    'type' => 'text',
            'title' => 'last_name',
            'fields' => [
                'last_name' => [
                    'name' => 'last_name',
                    'type' => 'text',
$vars['save_btn_text'] = lang('save');
$vars['save_btn_text_working'] = 'saving';

So basically, a simpler and easier way to visualize and create Forms within ExpressionEngine. There’s a lot of flexibility.

The General Design

Just like with the traditional Shared Form View layer, the basic idea is that every form contains one or more Field Groups, which contain one or more Field Sets, which contain one or more Fields. In the CP/Form use case, you create the Form, which you create a Field Group from, which you create a Field Set from, which you create Fields with. In code, this reads as:

$form = ee('CP/Form');
$field_group = $form->getGroup('The Group');
$field_set = $field_group->getFieldSet('The Field Set');
$field = $field_set->getField('the_field', 'the_field_type');

You can add and remove all the various items (Groups, Field Sets, Fields, and Buttons). That’s just the basic workflow though; each object contains its own options and configurability, so be sure to read up on the docs.

If you’re like me and like to read the code, you can find it at the entry point ExpressionEngine\Library\CP\Form within the codebase.

The Form Object

The Form object allows control over the Form details. As mentioned above, the Form contains Field Groups but also so much more. The Form object is where you define your form as outputting Tabs, custom Alerts to display, page title(s), whether it’ll contain file upload fields, or any custom hidden fields, just to name a few.

$form = ee('CP/Form');
$form->setCpPageTitle('My Custom Form')

Be sure to read up on the full API in the docs to see what’s possible.

The Field Group Object

The Field Group object is unique in that it has a basic interface, but it’s also the basis for any Tabs you want to define. If your Form is set to output Tabs, every Field group will be its own Tab, with every Field Set being used for the content.

$form = ee('CP/Form');
$field_group = $form->getGroup('General Settings');

Field Groups aren’t too deep, but you can learn more in the full API docs.

The Field Set Object

With Field Sets, you have a ton of flexibility. Yes, this is where you start defining your Fields, but it’s also where you define Field Set attributes. You can set the title, description, provide example copy, and apply buttons for JavaScript events.

$form = ee('CP/Form');
$field_group = $form->getGroup('General Settings');

$field_set = $field_group->getFieldSet('Field Set Name');
$field_set->withButton('My Button', 'my-rel', 'my-for')
    ->setDesc('Field Set Description')
    ->setTitle('The Field Set Title')
    ->setDescCont('Here\'s the next line of description')
    ->setExample('do something with another thing');

You can learn more in the official documentation.

The Field Object

And here’s the actual form input object. As mentioned, you get the Form\Field Object from the Form\Set object.

$form = ee('CP/Form');
$field_group = $form->getGroup('General Settings');
$field_set = $field_group->getFieldSet('Field Set Name');

$field = $field_set->getField('first_name', 'text')
    ->setPlaceholder('First Name')

To say that the Field object layer is deep would be an understatement. You’re really going to want to take a look at the official documentation to see all the possibilities.


To show how this all works together, we’ll build a sample add-on Control Panel page that has multiple tabs with different inputs. Below you can see the complete code for this form and matching screenshots of how the form would output in the Control Panel.

$form = ee('Cp/Form');

// Create our first Tab called "header 1"
$field_group = $form->getGroup('header 1');

//Now add a "First Name" and "Last Name" field
$field_set = $field_group->getFieldSet('First Name');
$field_set->getField('first_name', 'text')

//notice that where adding a button with the Last Name
$field_set = $field_group->getFieldSet('Last Name')->withButton('Click Me');
$field_set->getField('last_name', 'text')
    ->setPlaceholder('Last Name')

//Create tab called "Custom Input Example"
$field_group = $form->getGroup('Custom Input Example');
$field_set = $field_group->getFieldSet('email');
$field_set->getField('email', 'email')
    ->setPlaceholder('Your Email Address')

$field_set = $field_group->getFieldSet('color');
$field_set->getField('color', 'color')->setValue('#C86565');

$field_set = $field_group->getFieldSet('number');
$field_set->getField('number', 'number')->params(['min' => 100, 'max' => 1000])->setRequired(true);

//Create tab called "Contact details"
$field_group = $form->getGroup('Contact details');
$field_set = $field_group->getFieldSet('Address');
$field_set->getField('address1', 'text');
$field_set->getField('address2', 'action_button')->setText('Hello');
$field_set->getField('state', 'dropdown')->withNoResults('Nothing Here', 'fdsa', 'fdsa');

$form->withActionButton('My Action Button', 'https://google.com');
$button = $form->getButton('button_1');
$button->setType('submit')->setText('Submit Button')->setWorking('Submitting...');


$hidden_field = $form->getHiddenField('my_hidden_field');

//Create tab called "Table Example"
$field_group = $form->getGroup('Table Example');
$field_set = $field_group->getFieldSet('My Table Data');
$table = $field_set->getField('my_table', 'table');

    'lang_cols' => true,
    'class' => 'product_channels'

    'details' => ['sort' => false],
    'value' => ['sort' => false],

$table->setNoResultsText(sprintf(lang('no_found'), lang('product_channels')));
$table->setBaseUrl( ee('CP/URL')->make($this->base_url ));
$data = [];
$data[] = [

    'No, Hello',
    'To You!',

//Create tab called "Filepicker Example"
$field_group = $form->getGroup('Filepicker Example');
$field_set = $field_group->getFieldSet('My Filepicker');
$file_picker = $field_set->getField('my_file_picker', 'file-picker');

//Create tab called "Grid Example"
$field_group = $form->getGroup('Grid Example');
$field_set = $field_group->getFieldSet('My Grid')->withGrid();
$grid = $field_set->getField('my_grid_field', 'grid');

    'field_name' => $grid->getName(),
    'reorder'    => true,

    'text example' => ['sort' => false],
    'select example' => ['sort' => false],
    'password example' => ['sort' => false],
    'checkbox example' => ['sort' => false],
    'textarea example' => ['sort' => false],
    'upload example' => ['sort' => false],

$options = ['foo' => 'Foo', 'bar' => 'Bar'];
$cols = [
    ['name' => 'foo-text', 'type' => 'text', 'value' => ''],
    ['name' => 'barr-select', 'type' => 'select', 'value' => '', 'choices' => $options],
    ['name' => 'foo-password', 'type' => 'password', 'value' => ''],
    ['name' => 'bar-checkbox', 'type' => 'checkbox', 'value' => 1],
    ['name' => 'foo-textarea', 'type' => 'textarea', 'value' => '', 'cols' => 2, 'rows' => 5],
    ['name' => 'bar-upload', 'type' => 'file', 'value' => '', 'cols' => 2, 'rows' => 5],
    ['foo-text' => 'bar', 'barr-select' => 'foo', 'foo-password' => 'fdsa', 'bar-checkbox' => 1, 'foo-textarea' => '', 'bar-upload' => ''],
    ['foo-text' => 'fdsafdsa', 'barr-select' => 'bar', 'foo-password' => 'fdsa', 'bar-checkbox' => true, 'foo-textarea' => '', 'bar-upload' => '']

$grid->setNoResultsText(sprintf(lang('no_found'), lang('table-thing')), 'add');
$grid->setBaseUrl( ee('CP/URL')->make($this->base_url ));

$test_form = $form->toArray();

Eric Lamb's avatar
Eric Lamb

Builds things. Actively looking for clients.

Comments 0

Be the first to comment!