FieldTypes, FieldWidgets and FieldFormatters
This documentation needs review. See "Help improve this page" in the sidebar.
Overview
Drupal ships with a big library of base classes which allow you to work with your very own content. When it comes to content entities you want to use Fields. It is important to understand Fields as that is where your entities store their data.
FieldTypes
Core field types:
Machine name (type) | Label | Description | Core module |
boolean | Boolean | An entity field containing a boolean value. | |
changed | Last changed |
An entity field containing a UNIX timestamp of when the entity has been last updated. |
|
comment | Comments |
This field manages configuration and presentation of comments on an entity. |
comment |
created | Created |
An entity field containing a UNIX timestamp of when the entity has been created. |
|
datetime | Date | Create and store date values. | datetime |
daterange | Date range | Create and store date ranges. | datetime_range |
decimal | Number (decimal) |
This field stores a number in the database in a fixed decimal format. |
|
An entity field containing an email value. | |||
entity_reference | Entity reference | An entity field containing an entity reference. | |
file | File |
This field stores the ID of a file as an integer value. |
file |
file_uri | File URI |
An entity field containing a file URI, and a computed root-relative file URL. |
file |
float | Number (float) |
This field stores a number in the database in a floating point format. |
|
image | Image |
This field stores the ID of an image file as an integer value. |
image |
integer | Number |
This field stores a number in the database as an integer. |
|
language | Language | An entity field referencing a language. | |
link | Link |
Stores a URL string, optional varchar link text, and optional blob of attributes to assemble a link. |
link |
list_float | List (float) |
This field stores float values from a list of allowed 'value => label' pairs, i.e. 'Fraction': 0 => 0, .25 => 1/4, .75 => 3/4, 1 => 1. |
options |
list_integer | List (integer) |
This field stores integer values from a list of allowed 'value => label' pairs, i.e. 'Lifetime in days': 1 => 1 day, 7 => 1 week, 31 => 1 month. |
options |
list_string | List (text) |
This field stores text values from a list of allowed 'value => label' pairs, i.e. 'US States': IL => Illinois, IA => Iowa, IN => Indiana. |
options |
map | Map |
An entity field for storing a serialized array of values. |
|
password | Password | An entity field containing a password value. | |
path | Path |
An entity field containing a path alias and related data. |
path |
string | Text (plain) | A field containing a plain string value. | |
string_long | Text (plain, long) | A field containing a long string value. | |
telephone | Telephone number |
This field stores a telephone number in the database. |
telephone |
text | Text (formatted) | This field stores a text with a text format. | text |
text_long | Text (formatted, long) | This field stores a long text with a text format. | text |
text_with_summary | Text (formatted, long, with summary) |
This field stores long text with a format and an optional summary. |
text |
timestamp | Timestamp |
An entity field containing a UNIX timestamp value. |
|
uri | URI | An entity field containing a URI. | |
uuid | UUID | An entity field containing a UUID. |
Core field formatter types:
Machine name (type) | Field types | Label | Description |
author | entity_reference | Author | Display the referenced author user entity |
comment_username | string | Author name | Display the author name |
basic_string | string_long, email | Plain text | Plain text |
boolean | boolean | Boolean | Boolean |
comment_default | comment | Comment list | |
comment_permalink | string, uri | Comment Permalink | |
content_moderation_state | string | Content moderation state | |
daterange_custom | daterange | Daterange Custom | |
daterange_default | daterange | Daterange Default | |
daterange_plain | daterange | Daterange Plain | |
datetime_custom | datetime | Datetime Custom | |
datetime_default | datetime | Datetime Default | |
datetime_plain | datetime | Datetime plain | |
datetime_time_ago | datetime | Time ago | |
number_decimal | decimal, float | Decimal number | |
file_link | string | File Link | |
entity_reference_entity_view | entity_reference | Rendered entity | Render the referenced entity. |
entity_reference_entity_id | entity_reference | Entity ID | Display the ID of the referenced entities. |
entity_reference_label | entity_reference | Entity Label | Display the label of the referenced entities. |
entity_reference_rss_category | entity_reference | RSS category | Display reference to taxonomy term in RSS. |
file_audio | file | Audio | Display the file using an HTML5 audio tag. |
file_extension | string | File extension | |
file_mime | string | File MIME | |
file_size | integer | File size | |
file_uri | uri, file_uri | File URI | |
file_video | file | Video | Display the file using an HTML5 video tag. |
file_default | file | Generic file | |
image | image | Image | |
number_integer | integer | Number Integer | |
language | language | Language | |
link | link | Link | |
link_separate | link | Separate link text and URL | |
email_mailto | |||
media_thumbnail | entity_reference | Thumbnail | |
number_unformatted | integer, decimal, float | Unformatted number | |
oembed | link, string, string_long | oEmbed content | |
list_default | list_integer, list_float, list_string | List Default | |
list_key | list_integer, list_float, list_string | List Key | |
responsive_image | image | Responsive image | |
file_rss_enclosure | file | RSS enclosure | |
string | string, uri | Plain text | |
file_table | file | Table of files | |
telephone_link | telephone | Telephone link | |
text_default | text, text_long, text_with_summary | Text Default | |
text_summary_or_trimmed | text_with_summary | Summary or trimmed | |
text_trimmed | text, text_long, text_with_summary | Trimmed | |
timestamp_ago | timestamp, created, changed | Time ago | |
timestamp | timestamp, created, changed | Default | |
uri_link | uri | Link to URI | |
file_url_plain | file | URL to file | |
user_name | string | User name | Display the user or author name. |
Core field widget types:
- boolean_checkbox
- comment_default
- daterange_datelist
- daterange_default
- datetime_datelist
- datetime_default
- datetime_timestamp
- email_default
- entity_reference_autocomplete
- entity_reference_autocomplete_tags
- field_plugins_test_text_widget
- file_generic
- image_image
- image_module_test_dummy_ajax_widget
- language_select
- layout_builder_widget
- link_default
- media_library_inception_widget
- media_library_widget
- moderation_state_default
- number
- oembed_textfield
- options_buttons
- options_select
- path
- shape_only_color_editable_widget
- string_textarea
- string_textfield
- telephone_default
- test_field_widget
- test_field_widget_multilingual
- test_field_widget_multiple
- test_field_widget_multiple_single_value
- text_textarea
- text_textarea_with_summary
- text_textfield
- uri
Custom field types
Whenever you want to represent data in a way Drupal doesn't provide; you might want to create a new field type for your data.
Let's say you have a content entity which holds sensitive data. The creator of this content can allow specific users to access the entity via a password which differs for every user. If we talk in database tables you want to create something like this:
| entity_id | uid | password |
-----------------------------------
| 1 | 1 | 'helloworld' |
| 1 | 2 | 'goodbye' |
As you can see our entity with the id 1 has two different passwords for two different users. So how can we implement it in Drupal without having to create the table all by hand? We create a new field type.
Because Drupal implements the field logic as Plugin, we always have a base class that we can inherit from to make it work in Drupal. For a new field type you want to create the following folder structure in your module:
modules/custom/MODULENAME/src/Plugin/Field/FieldType
It is quite a long path and a little bit annoying but it makes it easier for Drupal to handle all the different functions that can coexist in your modules.
For our example, we create the file EntityUserAccessField.php
namespace Drupal\MODULENAME\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
* )
*/
class EntityUserAccessField extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
//ToDo: Implement this.
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
//ToDo: Implement this.
}
}
As you can see a field type looks very similar to a content entity. Actually, there is no real difference between those two but this is a topic for another node ;)
First of all, we have an Annotation for our field type
- @FieldType: This calls the Annotation class FieldType from the Drupal library
- id: This is the machine name of our field type so we can reuse it. Be sure not to override any predefined names from php and such.
- label: This can be a user readable translation for the machine name.
- description: If the label is not enough you can also add a description for the field type.
Second, our class extends FieldItemBase which makes us implement two methods so we can use this field type properly:
- propertyDefinitions(): This method is similar to a baseFieldDefinition of a content entity (it is not the same!). We can define data which appears on entity forms where this field type is being used.
- schema(): On entities, this method is deprecated but we still have it on fields. This method should implement the data representation of the field in a database. It can differ from the properties.
Because it is not that obvious what to write down on these methods let us add some code to them for the convenience.
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['uid'] = DataDefinition::create('integer')
->setLabel(t('User ID Reference'))
->setDescription(t('The ID of the referenced user.'))
->setSetting('unsigned', TRUE);
$properties['password'] = DataDefinition::create('string')
->setLabel(t('Password'))
->setDescription(t('A password saved in plain text. That is not safe dude!'));
$properties['created'] = DataDefinition::create('timestamp')
->setLabel(t('Created Time'))
->setDescription(t('The time that the entry was created'));
// ToDo: Add more Properties.
return $properties;
}
It is also possible to save the user id via a DataReferenceDefinition this might be covered here in the future.
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$columns = array(
'uid' => array(
'description' => 'The ID of the referenced user.',
'type' => 'int',
'unsigned' => TRUE,
),
'password' => array(
'description' => 'A plain text password.',
'type' => 'varchar',
'length' => 255,
),
'created' => array(
'description' => 'A timestamp of when this entry has been created.',
'type' => 'int',
),
// ToDo: Add more columns.
);
$schema = array(
'columns' => $columns,
'indexes' => array(),
'foreign keys' => array(),
);
return $schema;
}
The schema() is necessary to make Drupal know about how to save our data. The schema columns need to be a subset of the properties defined in propertyDefinitions().
We now have a whole new field type created. It does not have any logic on it how to handle any data input but it can be used on any content entity as a field. If you want you can use it as a baseField on an entity:
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
// Some fields above.
$fields['entity_user_access'] = BaseFieldDefinition::create('entity_user_access')
->setLabel(t('Entity User Access'))
->setDescription(t('Specify passwords for any user that want to see this entity.'))
->setCardinality(-1); // Ensures that you can have more than just one member
// Even more fields below.
return $fields;
}
- BaseFieldDefinition::create(): You have to use the machine name of the field type in the create()
- setCardinality(-1): The cardinality represents the amount of field data one entity can have. E.g. if we write a 2 in it only two users could access the entity, 3 would be 3 users and so on. -1 represents infinite users.
FieldWidget
If you have custom data, then you might want custom representation of this data. Widgets are used to represent how the user can input this custom data on forms. E.g
- if an integer has to be submitted in the form but the user can only check a checkbox
- if you want autocompletion for your data
- if password input has to be done via a special gui
etc.
A field widget in Drupal can be found under
modules/custom/MODULENAME/src/Plugin/Field/FieldWidget
which is a very long path too. At this point, it should be clear why Drupal uses this style to separate .php files. It is very easy to overlook which files belong where.
We create a field widget in EntityUserAccessWidget.php
namespace Drupal\MODULENAME\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Plugin implementation of the 'entity_user_access_w' widget.
*
* @FieldWidget(
* id = "entity_user_access_w",
* label = @Translation("Entity User Access - Widget"),
* description = @Translation("Entity User Access - Widget"),
* field_types = {
* "entity_user_access",
* },
* multiple_values = TRUE,
* )
*/
class EntityUserAccessWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
// ToDo: Implement this.
}
}
Did you notice already? Drupal 8 uses this style of code over and over again if you want to implement features. There is an annotation and a base class you have to inherit. Yay, Drupal can use it!
- @FieldWidget: Specifies the annotation class
- id: The machine name of the widget
- field_types: An array of field type machine names which can use this widget
- multiple_values: Default is FALSE. If it's true this will allow you to submit more than one value on an entity form
If you now want to use this widget with your field type you have to edit the annotation of the field type like this
// ...
/**
* @FieldType(
* id = "entity_user_access",
* label = @Translation("Entity User Access"),
* description = @Translation("This field stores a reference to a user and a password for this user on the entity."),
* default_widget = "entity_user_access_w",
* )
*/
// ...
Yeah all done! No, wait nothing happens yet because we have to implement the formElement() in our widget.
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['userlist'] = array(
'#type' => 'select',
'#title' => t('User'),
'#description' => t('Select group members from the list.'),
'#options' => array(
0 => t('Anonymous'),
1 => t('Admin'),
2 => t('foobar'),
// This should be implemented in a better way!
),
);
$element['passwordlist'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('Select a password for the user'),
);
//setting default value to all fields from above
$childs = Element::children($element);
foreach ($childs as $child) {
$element[$child]['#default_value'] = isset($items[$delta]->{$child}) ? $items[$delta]->{$child} : NULL;
}
return $element;
}
If you now open a form with this widget then you will see at least two input fields. One is a select for the user and the other is a password field. If you want to implement how the data is being saved then you have to implement validation methods on this widget or on the entity form. See Drupal 8 Form API for more info.
By now you have done a most of the work regarding a custom field. If you don't understand what is going on then just try out the code or have a look at the core modules for deeper knowledge on the topic.
FieldFormatters
The last thing that is missing is the representation of the data in the so called view mode of an entity - by the way the widget is the form mode. This is most common if you call an entity via yourdrupalpage.com/myentity/1/view
As there is not so much to talk about here we will come directly to the code. Under modules/custom/MODULENAME/src/Plugin/Field/FieldFormatter
create EntityUserAccessFormatter.php
namespace Drupal\MODULENAME\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
/**
* Plugin implementation of the 'entity_user_access_f' formatter.
*
* @FieldFormatter(
* id = "entity_user_access_f",
* label = @Translation("Entity User Access - Formatter"),
* description = @Translation("Entity User Access - Formatter"),
* field_types = {
* "entity_user_access",
* }
* )
*/
class EntityUserAccessFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
foreach ($items as $delta => $item) {
$elements[$delta] = array(
'uid' => array(
'#markup' => \Drupal\user\Entity\User::load($item->uid)->getUsername(),
),
// Add more content
);
}
return $elements;
}
}
This example has a very similar annotation as the widget so we do not need to talk about it that much. The viewElements() just shows the username of the saved user id of our field type. The access implementation has to be done in the entity. So this implementation will show all the user names which have a password on the entity.
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion