This guide documents the step-by-step process for extending Ibexa CMS with new content types, taxonomy types, and landing page block types.
Add the content type definition in ibexa/config/packages/project/static_parameters/content_definition.yaml:
ibexa_design_integration:
system:
default:
content_definition:
article:
name:
fre-FR: 'Article'
description:
fre-FR: ''
nameSchema: '<title>'
urlAliasSchema: '<title>'
defaultAlwaysAvailable: true
defaultSortField: published
defaultSortOrder: desc
container: false
fields:
title:
name: { fre-FR: Titre }
description: { fre-FR: '' }
type: string
required: true
searchable: true
translatable: true
category: Content
intro:
name: { fre-FR: Introduction }
description: { fre-FR: '' }
type: richtext
required: false
searchable: true
translatable: true
category: Content
body:
name: { fre-FR: Corps de texte }
description: { fre-FR: '' }
type: richtext
required: false
searchable: true
translatable: true
category: Content
image:
name: { fre-FR: Image }
description: { fre-FR: '' }
type: image
required: false
searchable: false
translatable: false
category: Contentthe available field types can be found in content.md
Create a migration file for the content type in the proper format:
-
type: content_type
mode: create
metadata:
identifier: article
contentTypeGroups: [ Content ]
mainTranslation: fre-FR
nameSchema: '<title>'
container: true
translations: { fre-FR: { name: Article } }
fields:
- identifier: title
type: ezstring
position: 10
translations: { fre-FR: { name: 'Titre' } }
required: true
searchable: true
- identifier: intro
type: ezrichtext
position: 20
translations: { fre-FR: { name: 'Introduction' } }
required: false
searchable: true
- identifier: body
type: ezrichtext
position: 30
translations: { fre-FR: { name: 'Corps de texte' } }
required: false
searchable: true
- identifier: image
type: ezimageasset
position: 40
translations: { fre-FR: { name: 'Image' } }
required: false
searchable: falseCreate templates for each view (full, line, etc.) in ibexa/templates/themes/site/content/:
This step can be omitted for content without views
{# article/full.html.twig #}
{% component {
name: 'Article full view',
properties: {
content: 'content("article")'
},
parameters: []
} %}
<article class="article">
<h1>{{ content.name }}</h1>
{% if content.fields.image is defined and content.fields.image is not empty %}
<div class="article__image">
{{ ez_render_field(content.content, 'image') }}
</div>
{% endif %}
<div class="article__intro">
{{ ez_render_field(content.content, 'intro') }}
</div>
<div class="article__body">
{{ ez_render_field(content.content, 'body') }}
</div>
</article>Add the taxonomy entry definition in ibexa/config/packages/project/static_parameters/taxonomy_definitions.yaml:
ibexa_design_integration:
system:
default:
taxonomy_entry_definition:
theme_tag:
models:
- name: tag1
identifier: tag1
- name: tag2
identifier: tag2
fields:
name:
required: true
type: string
identifier:
required: true
type: string
parent:
required: false
type: taxonomy_entry
options:
type: theme_tag
max: 1the available field types can be found in taxonomy_entry.md
Create a migration file for the taxonomy using a template with variables:
###
# Variables :
# - taxonomy_identifier: theme
# - taxonomy_content_type_name: Thème
# - taxonomy_name: Thématiques
###
-
type: content_type
mode: create
metadata:
identifier: theme_tag
contentTypeGroups: [ Taxonomy ]
mainTranslation: fre-FR
nameSchema: '<name|identifier>'
container: false
translations: { fre-FR: { name: Thème } }
fields:
- identifier: name
type: ezstring
position: 10
translations: { fre-FR: { name: 'Nom' } }
required: true
- identifier: identifier
type: ezstring
position: 20
translations: { fre-FR: { name: 'Identifiant' } }
required: true
- identifier: parent
type: ibexa_taxonomy_entry
position: 30
translations: { fre-FR: { name: 'Parent' } }
required: false
fieldSettings: { taxonomy: theme }
### FOLDERS
-
type: content
mode: create
metadata:
contentType: folder
mainTranslation: fre-FR
alwaysAvailable: true
section: { identifier: taxonomy }
location:
locationRemoteId: theme_taxonomy_folder
parentLocationRemoteId: taxonomy_root_folder
fields:
- { fieldDefIdentifier: name, languageCode: fre-FR, value: Thématiques }
### PERMISSIONS
-
type: role
mode: update
match: { field: identifier, value: Anonymous }
policies:
mode: append
list:
- module: taxonomy
function: read
limitations:
- { identifier: Taxonomy, values: [ 'theme' ] }
### ROOT TAG
-
type: content
mode: create
metadata:
contentType: theme_tag
mainTranslation: fre-FR
alwaysAvailable: true
section: { identifier: taxonomy }
location:
locationRemoteId: theme_taxonomy_tag_root
parentLocationRemoteId: theme_taxonomy_folder
fields:
- { fieldDefIdentifier: identifier, languageCode: fre-FR, value: theme_root }
- { fieldDefIdentifier: parent, languageCode: fre-FR, value: { taxonomy_entry: ~, taxonomy: theme } }
- { fieldDefIdentifier: name, languageCode: fre-FR, value: Thématiques }
references:
- name: taxonomy_theme_root_taxonomy_id
type: taxonomy_idUpdate the configuration in ibexa/config/packages/project/ibexa/taxonomy.yaml:
parameters:
app.default.taxonomy_menu:
- tags
- theme
ibexa_taxonomy:
taxonomies:
theme:
register_main_menu: false
content_type: theme_tag
parent_location_remote_id: theme_taxonomy_folder
field_mappings:
identifier: identifier
parent: parent
name: nameAdd the block definition in ibexa/config/packages/project/static_parameters/block_definitions.yaml:
ibexa_design_integration:
system:
default:
block_definition:
last_articles:
views:
default: '@ibexadesign/landing_page/block/last_articles.html.twig'
attributes:
title:
type: "string"
required: false
articles:
type: "content"
required: true
options:
type: article
max: 5the available attributes types can be found in block.md
Create a template in ibexa/templates/themes/site/landing_page/block/last_articles.html.twig:
A need at least a default view
{% component {
name: 'Block "Last Articles"',
description: 'Block displaying the latest articles with a title',
properties: {
block: 'block("last_articles")'
},
parameters: []
} %}
<div class="last-articles-block">
{% if block.attributes.title is not empty %}
<h2 class="last-articles-block__title">{{ block.attributes.title }}</h2>
{% endif %}
{% if block.attributes.articles is not empty %}
<div class="last-articles-block__content">
<div class="articles-grid">
{% for article in block.attributes.articles %}
<div class="article-card">
{% if article.fields.image is defined and article.fields.image is not empty %}
<div class="article-card__image">
{{ ez_render_field(article.content, 'image', {
'parameters': {
'alias': 'article_card'
}
}) }}
</div>
{% endif %}
<div class="article-card__content">
<h3 class="article-card__title">
<a href="{{ path('ez_urlalias', {'contentId': article.id}) }}">{{ article.name }}</a>
</h3>
{% if article.fields.intro is defined and article.fields.intro is not empty %}
<div class="article-card__intro">
{{ ez_render_field(article.content, 'intro') }}
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>Update the configuration in ibexa/config/packages/project/ibexa/landing_page.yaml:
ibexa:
system:
default:
page_builder:
blocks:
last_articles:
name: Last Articles
category: Content
thumbnail: '/assets/images/blocks/last_articles.svg'
views:
default:
template: themes/site/landing_page/block/last_articles.html.twig
name: Default
attributes:
title:
type: text
name: Title
category: Content
articles:
type: collection
name: Articles
category: Content
validators:
- { name: ContentTypeConstraint, options: { contentTypes: [article] } }For each extension type, here's a checklist of necessary operations:
- ✅ Content type definition in
content_definitions.yamlwith proper structure - ✅ Content type migration file using the correct format with translations
- ✅ Content type view templates (full, line, etc.)
- ✅ Taxonomy entry definition in
taxonomy_definitions.yaml - ✅ Taxonomy migration file with content type, folders, permissions and root tag
- ✅ Taxonomy configuration in
taxonomy.yaml
- ✅ Block type definition in
block_definitions.yaml - ✅ Block view templates
- ✅ Block configuration in
landing_page.yaml
-
Multilingual Support:
- Always include translations for field names (at minimum in
fre-FR) - Define proper name schema and URL alias schema for content types
- Always include translations for field names (at minimum in
-
Taxonomy Structure:
- Create a root tag for each taxonomy
- Set up proper permissions for taxonomy access
- Use references for taxonomy IDs when needed
-
Migration Best Practices:
- Use remote IDs for consistent references across environments
- Include proper section identifiers (e.g.,
taxonomyfor taxonomy content) - Set up proper parent-child relationships for taxonomy entries
-
Cache Management:
- Clear cache after adding new content types, taxonomies, or blocks:
php bin/console cache:clear
- Clear cache after adding new content types, taxonomies, or blocks: