Edit on GitHub
Jump to docs navigation

Extending / Intermediate / Custom Field Types

Note: You are currently reading the documentation for Bolt 3.7. Looking for the documentation for Bolt 5.2 instead?

The release of Bolt 3.0 has seen a significant amount of power and flexibility added to custom fields. In the 2.x series of releases custom fields did not have access to the entire lifecycle of a field and was focused more on providing replacement templates for the built in storage types.

With this in mind, the following tutorial is a reworking of the one originally written for the 2.x documentation, it no longer represents best practice for custom field development, for that please see the latest tutorial here.

What this updated tutorial will be useful for is to see what changes will be needed to update field extensions to be compatible with Bolt 3.

Task: Add a Colour Picker to Bolt

Wouldn't it be good if we could allow editors of your site to add a splash of colour to a page banner?

Possibly not, but that's no reason to not try, so we're going to add a new field type to Bolt that allows content editors to pick a colour. Here's a step-by-step guide of how to build and test this extension.

Step 1: Configure the extension

As with all Bolt extensions we need to get started with a configuration composer.json file. So we make a new directory for our extension called bolt- colourpicker and then within the directory type git init. This gets us setup with a clean empty project.

Next comes the configuration, we'll keep things nice and simple so here's the essentials:

{
    "name": "bolt/colourpicker",
    "description": "An extension to add a colourpicker as a field type within Bolt",
    "type": "bolt-extension",
    "require": {
        "bolt/bolt": "^3.0"
    },
    "license": "MIT",
    "authors": [{"name": "Ross Riley", "email": "riley.ross@gmail.com"}
    ],
    "autoload": {
        "psr-4": {
            "Bolt\\Extensions\\Bolt\\ColourPicker\\": "src"
        }
    },
    "extra": {
        "bolt-assets": "web",
        "bolt-class": "Bolt\\Extensions\\Bolt\\ColourPicker\\ColourPickerExtension",
        "bolt-screenshots": ["screenshot.png"]
    }
}

The autoload section maps the root of our extension directory to the PHP namespace, if you've not yet used PSR-4 autoloading for your PHP code, it's probably good at this point to take a look at some examples from the standard documentation here.

Within the extra section we configure two essential bits of information that Bolt uses to run the extension, firstly the bolt-class setting tells Bolt which class can be used to bootstrap this extension. This will be the fully qualified class name, but with two back-slashes to separate namespace paths.

Then the bolt-assets setting tells Bolt which folder stores assets that need to be publicly available. On install Bolt will then copy the assets in this folder to an accessible public folder.

The Extension Class

Now it's time for the heavy lifting, we need to write our extension class. To conform to the standard Bolt Extension interface we need to extend the utility class SimpleExtension and implement a few methods that will define the features our extension provides.

The most important part is that we will need to extend the built in ConfigProvider and add our new field.

First of all, here's the final version of the file that we need to make the extension, then we'll have a quick run-through of why we need each of these setup methods.

<?php
// File is in src/ColourPickerExtension.php
namespace Bolt\Extensions\Bolt\ColourPicker;

use Bolt\Asset\File\JavaScript;
use Bolt\Asset\File\Stylesheet;
use Bolt\Controller\Zone;
use Bolt\Extension\SimpleExtension;
use Bolt\Extensions\Bolt\ColourPicker\Field\ColourPickField;

class ColourPickerExtension extends SimpleExtension
{
    public function registerFields()
    {
        return [
            new ColourPickField(),
        ];
    }

    protected function registerTwigPaths()
    {
        return [
            'templates' => ['position' => 'prepend', 'namespace' => 'bolt']
        ];
    }

    protected function registerAssets()
    {
        return [
            Stylesheet::create('css/colourpicker.css')->setZone(Zone::BACKEND),
            JavaScript::create('js/colourpicker.js')->setZone(Zone::BACKEND),
            JavaScript::create('js/start.js')->setZone(Zone::BACKEND),
        ];
    }
}

Our colourpicker is primarily based on the jQuery Simple Colorpicker so we need to add both the css and javascript assets to the Bolt markup, we also add a start.js file which will initalize the javascript.

These are all added in the registerAssets method, all this method needs to do is return an array of asset objects so we make a new object either new JavaScript(xxx) or new Stylesheets(xxxx) depending on what we need. These files are loaded relative to the root of the extension so web/colourpicker.css loads from the web directory.

Next we need to add our own custom field onto the built in field manager. To do this we need to create a function called registerFields() that will return an array of one or more classes that implement Bolt\Storage\Field\Type\FieldTypeInterface.

This block does just that:

    public function registerFields()
    {
        return [
            new ColourPickField(),
        ];
    }

Finally we want to add our extension's template directory to the system so that Bolt knows it needs to look here for extra templates. We do that by returning an array from the registerTwigPaths() method. The precise syntax we use is:

protected function registerTwigPaths()
{
    return [
        'templates' => ['position' => 'prepend', 'namespace' => 'bolt']
    ];
}

The key of this array is the directory we want to add relative to the root of the extension, the value is an array of options. Both of these settings tell Bolt how and when to load templates from this directory, position => prepend means that this directory takes precedence over the default templates, the namespace is set to bolt as the template we are adding needs to appear in the backend of Bolt and this is the namespace used. If you only want to make templates available in the frontend then you can omit the namespace.

The Field Class

You can see in the provider class above we added a new instance of ColourPickField to the Bolt field manager. Any new field needs to implement the Bolt\Storage\Field\Type\FieldTypeInterface which has a few fairly simple requirements.

We firstly need to tell Bolt what name the field will use (this is how it will be set in contenttypes.yml) and also what template will be used to render the field.

Here's the final code for our new field:

<?php
// File is in src/Field/ColourPickField.php
namespace Bolt\Extensions\Bolt\ColourPicker\Field;

use Bolt\Storage\Field\Type\FieldTypeBase;

class ColourPickField extends FieldTypeBase
{
    public function getName()
    {
        return 'colourpicker';
    }

    public function getTemplate()
    {
        return '@bolt/_colourpicker.twig';
    }

    public function getStorageType()
    {
        return 'text';
    }

    public function getStorageOptions()
    {
        return ['default' => ''];
    }
}

The Template File

Finally we need a template file that specifies how our field looks in the content editor. Note that this template's name will be the name returned by the getTemplate() method in our field class shown above, in this example the file is named _colourpicker.twig.

Here's how our _colourpicker.twig template takes shape:


{#=== Options ============================================================================#}

{% set attr_opt = {
    class:        field.class|default(''),
    name_id:      key,
    required:     field.required|default(false),
    readonly:     field.readonly|default(false)
}%}

{#=== FIELDSET ============================================================================#}

<fieldset class="colourpicker">
    <div class="col-sm-12">
        <div>
            <label class="control-label">{{ field.label|default(key|humanize) }}</label>
        </div>
        <select data-colourpicker {{ macro.attr(attr_opt) }}>
            {% for valuekey, value in field.values %}
                <option value="{{ valuekey }}"{% if valuekey == context.content.get(key) %} selected="selected"{% endif %}>
                    {{value}}
                </option>
            {% endfor %}
        </select>
    </div>
</fieldset>

As you can see the field under the js is a select dropdown of colour options. When you define your own field, all of the options specified in contenttypes.yml are available within the field object. Some of the potential values are accessed in the options block. In the case of our colourpicker field, we will look for a list of values and use the key/value to build the option list.

The Final Completed Extension

We now have a completed extension and can add it to our Bolt site. Firstly we need to add the field to our contenttypes.yml file. We add a field like this:

    banner:
        type: colourpicker
        label: "Choose A Banner Colour"
        values:
            "#000": Solid Black
            "#E1E1E1": Almost White
            "#444": Dark Grey
            "#FF0011": Bright Red

Our new ColourPicker Field


The Source Code

It's a good idea to look at the final source code for this extension to help you get started making a similar one. There is a repository on GitHub here.



Edit this page on GitHub
Couldn't find what you were looking for? We are happy to help you in the forum, on Slack or on Github.