Developing Custom Content Types in Drupal
Content types in Drupal are entities with a set of fields. Standard Article and Page are replaced or supplemented with types for specific tasks: News, Case Study, Vacancy, Product. Configured in UI, configuration exported to YAML and committed to git.
Creating via UI
Path: /admin/structure/types/add. Set name (Label) and machine name (slug). Drupal creates type with basic fields: Title, Body, Published.
After creation — add custom fields: /admin/structure/types/manage/{type}/fields/add-field.
Export Configuration to YAML
After UI setup:
drush config:export
Files appear in config/sync/:
node.type.news.yml # type itself
field.storage.node.field_subtitle.yml # field storage
field.field.node.news.field_subtitle.yml # field for specific type
core.entity_form_display.node.news.default.yml # edit form
core.entity_view_display.node.news.default.yml # display view
core.entity_view_display.node.news.teaser.yml # teaser view
Example node.type.news.yml:
langcode: en
status: true
dependencies: {}
name: News
type: news
description: 'Company news'
help: ''
new_revision: true
preview_mode: 1
display_submitted: false
Field Configuration
# field.field.node.news.field_subtitle.yml
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_subtitle
- node.type.news
id: node.news.field_subtitle
field_name: field_subtitle
entity_type: node
bundle: news
label: 'Subtitle'
description: 'Brief description for lists and cards'
required: false
translatable: true
default_value: []
settings: {}
field_type: string
Programmatic Type Creation (via install hook)
If type is created by module, not manually:
// my_module.install
function my_module_install(): void {
// Create content type
$node_type = \Drupal\node\Entity\NodeType::create([
'type' => 'vacancy',
'name' => 'Vacancy',
'description' => 'Open company vacancies',
'display_submitted' => FALSE,
'new_revision' => TRUE,
]);
$node_type->save();
// Field "City"
$field_storage = \Drupal\field\Entity\FieldStorageConfig::create([
'field_name' => 'field_city',
'entity_type' => 'node',
'type' => 'string',
'cardinality' => 1,
]);
$field_storage->save();
\Drupal\field\Entity\FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'vacancy',
'label' => 'City',
'required' => TRUE,
'settings' => ['max_length' => 100],
])->save();
// Field "Salary range" — decimal
$salary_storage = \Drupal\field\Entity\FieldStorageConfig::create([
'field_name' => 'field_salary_min',
'entity_type' => 'node',
'type' => 'decimal',
'settings' => ['precision' => 10, 'scale' => 0],
]);
$salary_storage->save();
\Drupal\field\Entity\FieldConfig::create([
'field_storage' => $salary_storage,
'bundle' => 'vacancy',
'label' => 'Salary from',
])->save();
// Entity reference to "Direction" taxonomy
$ref_storage = \Drupal\field\Entity\FieldStorageConfig::create([
'field_name' => 'field_direction',
'entity_type' => 'node',
'type' => 'entity_reference',
'settings' => ['target_type' => 'taxonomy_term'],
'cardinality' => \Drupal\Core\Field\FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
]);
$ref_storage->save();
\Drupal\field\Entity\FieldConfig::create([
'field_storage' => $ref_storage,
'bundle' => 'vacancy',
'label' => 'Direction',
'settings' => [
'handler' => 'default:taxonomy_term',
'handler_settings' => [
'target_bundles' => ['directions' => 'directions'],
'auto_create' => FALSE,
],
],
])->save();
// Configure display forms
\Drupal\Core\Entity\Entity\EntityFormDisplay::load('node.vacancy.default')
->setComponent('field_city', [
'type' => 'string_textfield',
'weight' => 10,
'settings' => ['size' => 60],
])
->setComponent('field_direction', [
'type' => 'options_buttons',
'weight' => 20,
])
->save();
}
Typical Field Types
| Situation | Field Type |
|---|---|
| Short text | string |
| Long text / HTML | text_long or text_with_summary |
| Integer number | integer |
| Fractional number | decimal or float |
| Date | datetime |
| Reference to another entity | entity_reference |
| Image | image |
| File | file |
| Boolean | boolean |
| List (select) | list_string or list_integer |
email |
|
| Link (URL) | link |
Related Types and Reference Fields
Typical structure: Case → Client (entity reference), Case → Services (multiple entity reference).
# field.storage.node.field_client.yml
field_name: field_client
entity_type: node
type: entity_reference
cardinality: 1
settings:
target_type: node
# field.field.node.case.field_client.yml
field_name: field_client
bundle: case
label: 'Client'
settings:
handler: 'default:node'
handler_settings:
target_bundles:
client: client
sort:
field: title
direction: ASC
auto_create: false
Revisions
For types where change history is needed, enable new_revision: true. Working with revisions:
// Load all revisions of node
$storage = \Drupal::entityTypeManager()->getStorage('node');
$revision_ids = $storage->revisionIds($node);
$revisions = array_map(fn($rid) => $storage->loadRevision($rid), $revision_ids);
// Revert to revision
$node->setNewRevision(FALSE);
$node = $storage->loadRevision($target_revision_id);
$node->isDefaultRevision(TRUE);
$node->save();
Timelines
One content type with fields via UI + config export: half day. Multiple types with relationships, display form setup, Views: 1–2 days.







