A field on a fieldable UI content type is something a site builder adds by clicking through Structure → Content types → Manage fields. A field on a code-defined content entity is something you declare in PHP, inside a single static method: baseFieldDefinitions(). Same underlying Field API, same storage mechanics — but instead of an admin filling in a form, you're writing the equivalent directly, which means it ships with your module and every site that installs it gets the exact same fields, guaranteed.
What you'll learn in this lesson
- Where
baseFieldDefinitions()lives and when Drupal calls it - How to define a plain field, an entity-reference field, and a list field with fixed options
- The difference between a field's storage settings and its display settings
baseFieldDefinitions() — one method, one field per entry
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setDescription(t('The name of the Contact entity.'))
->setSettings([
'max_length' => 255,
'text_processing' => 0,
])
->setDisplayOptions('view', [
'label' => 'above',
'type' => 'string',
'weight' => -6,
])
->setDisplayOptions('form', [
'type' => 'string_textfield',
'weight' => -6,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
return $fields;
}
Drupal calls this method once, at install time, to determine your entity's storage schema — the database columns are generated automatically from whatever fields you return here, and again whenever the entity type's field definitions are considered (so a genuine change usually needs an update hook, not just edited code, on a site that's already installed). Each entry is keyed by machine name and built with BaseFieldDefinition::create($field_type), where $field_type is the exact same string you'd see in the Field UI's "Add field" dropdown — string, integer, entity_reference, boolean, and so on.
Storage settings vs. display settings — a distinction worth getting right
setSettings() configures the field's storage — max_length and text_processing above affect what's actually written to the database column and can't be changed later without a real schema migration. setDisplayOptions('view', ...) and setDisplayOptions('form', ...) only control how the field is presented — the formatter used to render it, or the widget used to edit it — and can be changed freely at any time with zero data impact.
An entity reference field
$fields['user_id'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('User Name'))
->setSetting('target_type', 'user')
->setSetting('handler', 'default')
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'settings' => [
'match_operator' => 'CONTAINS',
'match_limit' => 10,
],
]);
An entity_reference field stores another entity's ID, not a copy of its data — here, a link to a user entity. target_type says which entity type it can point to; handler controls the selection logic (the default handler offers every entity of that type, but you can restrict by bundle or write your own). The entity_reference_autocomplete widget is what gives editors that "type to search" behavior in the form.
A field with a fixed set of options
$fields['role'] = BaseFieldDefinition::create('list_string')
->setLabel(t('Role'))
->setSettings([
'allowed_values' => [
'administrator' => 'administrator',
'user' => 'user',
],
])
->setDefaultValue('user')
->setDisplayOptions('form', [
'type' => 'options_select',
]);
list_string stores one of a fixed set of string keys — exactly what you'd reach for to model a dropdown of predefined choices. allowed_values is a storage setting (the valid values are part of the schema contract), while options_select is purely a display choice — you could swap it for radio buttons without touching a single stored value.
setDisplayConfigurable(TRUE) hands control to the site builder. Call it on both 'form' and 'view' and a site administrator can rearrange or hide this field via Manage Form Display / Manage Display in the UI, exactly like a field added through the Field UI. Leave it FALSE (the default) and your display options above are the only ones that will ever apply — useful for fields that should never be reconfigurable, like an internal ID.Quick check: you need to change a field's
max_lengthfrom 255 to 500 on a site that already has this entity type installed with live data. Is editing the number inbaseFieldDefinitions()and clearing cache enough? No — storage settings for an installed entity type require anhook_update_N()that calls the entity/field definition update mechanism (or, in this specific case, an actual schema change); code changes tobaseFieldDefinitions()alone only affect brand-new installs.
Key takeaways
baseFieldDefinitions()is a static method returning an array ofBaseFieldDefinitionobjects, keyed by machine name — this is where your entity's schema comes from.- Storage settings (
setSettings(),setDefaultValue()) affect the database schema; display settings (setDisplayOptions()) only affect presentation and are always safe to change. - Use
entity_referencefields to link to other entities by ID, andlist_string/list_integerfor a fixed set of allowed choices. setDisplayConfigurable(TRUE)lets a site builder rearrange the field via the normal Manage Display/Manage Form Display UI; leave it off for fields that must never be reconfigured.- Changing storage settings on an entity type that's already installed on a live site needs an update hook, not just a code edit.
Coming up next
You now know how to shape the fields your entity stores. Next, in Querying Entities, we put those fields to work — building conditions against them with \Drupal::entityQuery() to find exactly the records you need.