Developing and rendering custom content blocks

This page describes how to define and render custom content blocks that can be grouped to form content compositions in implementations of the Content editor.

Compatibility note

Custom content editor and block definitions created in the Magnolia 5 UI framework must be migrated.

Block types

The Content editor provides predefined block types that you can use in your custom content editor app:

  • text - Compared to TextFieldDefinition, the text block implements a special LightRichTextFieldDefinition class, which features basic text formatting functions:

    Light RichText Field block with control box

    templateId: content-editor:blocks/text
    icon: text-block
    label: Text
      implementationClass: info.magnolia.editor.field.LightRichTextFormView
          class: info.magnolia.editor.field.LightRichTextFieldDefinition
            - pages-app
            - dam-chooser
  • image - Configured using the ordinary 6 UI damLinkField and textField field types:

    templateId: content-editor:blocks/image
    icon: file-image
    label: Image
          $type: damLinkField
          label: Image
          placeholder: Add image...
          buttonSelectNewLabel: Select new...
          label: Alt text
          $type: textField
          placeholder: Add alt text...
          label: Caption
          placeholder: Add caption...
          $type: textField
          label: Credits
          placeholder: Add credits...
          $type: textField
  • externalLink - Utilizing the PeekFieldDefinition class from the Magnolia Link Unfurl Module, a submodule in the Content Editor module.

    templateId: link-unfurl:components/externalLink
    icon: embed
    label: Embed content
          class: info.magnolia.unfurl.ui.PeekFieldDefinition
          label: Embedded content
              class: info.magnolia.unfurl.ui.UrlValidatorDefinition
              errorMessage: link-unfurl.components.externalLink.tabUrl.url.validation.errorMessage
  • video - A combination of the following 6 UI field types: checkBoxField, comboBoxField, damLinkField, expandingTextField, radioButtonGroupField, switchableField and textField.

Demo block types

The Magnolia Demo decorates the default Stories app and provides two additional block types you can use:

  • date

  • tour

These block types implement the 6 UI dateField and linkField, respectively. To see these blocks, you must have the Magnolia demo modules installed.

Defining a custom content block

To define a custom block, use a YAML definition file and apply the BlockDefinition class of the Content editor module.

  1. Create a YAML file in the blocks folder of your module and add the following required definition elements.

    class: info.magnolia.block.BlockDefinition (1)
    templateId: <module-name>:<the-path-to-the-block-relative-to-the-module> (2)
    icon: <icon-name>
    label: <i18n-label>
    block: (3)
        $type: jcrBlockGetIndexedChildNode
    1 The info.magnolia.block.BlockDefinition class.
    2 The templateId of your block.
    3 A block node, with a list of fields the content block consists of. Use the properties in the same way as the properties for the CompositeFieldDefinition.
  2. Provide a template definition file and a template script for your block in the templates/blocks subfolder of your module.

  3. Optionally, in the i18n folder of your module, provide a file with i18n keys for labels and descriptions of the block’s fields.

Creating a custom block with Magnolia CLI


In this CLI version, the block prototypes are not yet compatible with the migrated (6 UI) Content Editor module and Stories app.

The block definition created will be based on the 5 UI info.magnolia.editor.block.stock.FieldSetBlockDefinition.

If you want to use the 6 UI-native block definition (based on info.magnolia.block.BlockDefinition), you have to migrate the definition created by the command.

This command creates:

  • A block definition YAML file.

  • A template definition YAML file.

  • A freemarker template script.

The command succeeds only if the current directory (or the directory defined by the optional -p <path> parameter) is a light module with a minimal folder structure.

The files which are created when executing this command are built from the files in the prototypes folder of your configuration. The files contain some standard code with some commonly used properties. This is generally a good starting point to build a block.


mgnl create-block <blockName> [options]

Parameter Description



The name of the new block definition. The block name cannot contain spaces.

Short form Long form Description

-p <path>

--path <path>


The path to the light module to add the new block to.

If you do not specify a path, you must run the command from an existing light module folder.

-P <name>

--prototype <name>


The name of the prototype variant to create the block.

mgnl create-block my-block

If you do not use the -p <path> option, you must run the command from an existing light module folder.

Rendering blocks in a FreeMarker script

This section explains how to render block content in a page or a component template.

The cms:block directive

The Content editor module provides cms:block, a Magnolia FreeMarker directive for fetching and rendering block elements.

The directive expects a node of the type mgnl:block as argument, identifies the template definition of the block and calls the associated template script.

[#assign blocks = cmsfn.children(articleContent, "mgnl:block") /]
  [#list blocks as block]
    [@cms.block content=block /]

Examples of block rendering

The examples show how to render blocks within a template script of a page or component template. Using the Magnolia directive cms.block, the template script of the block is executed during the rendering of the page or component.

  1. Get story content:

    [#assign articleContent = cmsfn.contentById(content.article, "") /]

  2. Get the blocks for that story:
    [#assign blocks = cmsfn.children(articleContent, "mgnl:block") /]

  3. Retrieve all blocks for a piece of content:

    [#if articleContent?hasContent]
      [#assign blocks = cmsfn.children(articleContent, "mgnl:block") /]
      [#list blocks as block]
        [@cms.block content=block /]

    Line 4: The Magnolia directive cms.block ensures that the template script associated to the passed block is called to render the block content.

  4. Retrieve two blocks, for instance to display an excerpt of a story in a list:

    [#if articleContent?hasContent]
      [#assign blocks = cmsfn.children(articleContent, "mgnl:block") /]
      [#list blocks as block]
        [#if block?index == 2]
        [@cms.block content=block /]

    Line 7: The Magnolia directive cms.block calls the template script associated to the passed block content.

Content i18n and migration to version 2.1 (and higher)

Internationalization (i18n) of content is supported since version 2.1 of the Content Editor module.

If you are upgrading from Magnolia 6.2 or from a 1.x version of the Content Editor, please mind the migration procedures below to enable i18n in the Stories app, as well as in other Content Editor based apps you may have.

Compatibility of content and block definitions

Block definitions and data structures created in the older versions of the module must be migrated.

Flat vs nested content structure

The data model has changed for internationalized stories. Whereas in versions 1.3.x and 2.0.x of the module, the mgnl:block elements are stored in a flat node structure,

└── story1
    ├── 0
    └── 1

in the i18n-supported version (2.1 and higher), the nodes are locale-nested under intermediate nodes of type mgnl:contentNode, named blocks_de and blocks in this example:

└── story1
    └── blocks_de
    │   ├── 0_de
    │   └── 1_de
    └── blocks
        ├── 0
        └── 1

This must be reflected in your MultiJcrBlockDefinition, where you need to add and enable the i18n property.

Instead of the CurrentItemProvider, the CompatibleBlockProvider is set as the default provider, which can resolve both flat and nested block nodes. You do not need to declare it in your block definition.

Example definition
  label: Blocks
  $type: multiJcrBlock
  i18n: true
    - text
This applies only to the 2.0.x block definitions. Block definitions created for version 1.3.x (5 UI) of the module are not compatible with the 2.1 version and must be fully migrated.

Migrating content

There are two ways you can migrate the non-i18n blocks to the i18n-compatible hierarchy: using a version handler or a Groovy script.

We strongly recommend you have the latest version of the Content Editor before migrating your content.

In versions prior to 2.1.3, the MigrateBlockToIntermediateParentTask doesn’t preserve the order of nodes when migrating to the latest version. Running this task can actually reorder the nodes to an incorrect state.

Version handler

When upgrading the Stories app submodule to version 2.1 or higher, all block nodes in the stories workspace will be moved to intermediate nodes, see the MigrateBlockToIntermediateParentTask task.

Groovy script

You can run the migration task in the Groovy app, especially in case a block node is stored in another workspace.

Example Groovy script
import info.magnolia.editor.setup.MigrateBlockToIntermediateParentTask
import info.magnolia.module.InstallContextImpl
import info.magnolia.module.ModuleRegistryImpl
import info.magnolia.objectfactory.Components
import javax.jcr.Session

Session session =  MgnlContext.getJCRSession("stories");
task = new MigrateBlockToIntermediateParentTask("stories", "/", "blocks");

The parameters in the MigrateBlockToIntermediateParentTask:

  • stories - workspace name

  • / - path

  • blocks - name of the intermediate node, the name of the multiJcrBlock field.


DX Core



This widget lets you know where you are on the docs site.

You are currently perusing through the DX Core docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules
6.3 beta

Magnolia 6.3 beta

Magnolia 6.3 is in beta. We are updating docs based on development and feedback. Consider the 6.3 docs currently in a state of progress and not final.

We are working on some 6.3-beta known issues during this phase.