Skip to the content
Documentation
Help Start
Reading in:
  • German: 9,059 of 9,059 pages
About the help in other languages
All help pages

The content model and editing content

This guide is for editors and site builders. It shows how content is shaped in Exponential and how to work with it every day. You define a content class, create content from it, and edit it with the online editor…

Download as PDF

Address: https://doc.exponential.earth/exphelp/viewer/guides/content-model-and-editing.md

On this page

The content model and editing content

This guide is for editors and site builders. It shows how content is shaped in Exponential and how to work with it every day. You define a content class, create content from it, and edit it with the online editor (including the TinyMCE 8 engine). Then you work on many items at once, use the sub-items table, recover an item from the trash and run a large operation as a background job.

Allow about 30 minutes, with a browser and one terminal. You need a running Exponential 6 installation (Getting started) and an administrator login. The administration interface is on your admin siteaccess, for example https://edit.example.com/ or http://localhost:8087/admin/. Commands run from the installation root.

1. How the content model fits together

TermMeaning
Content classa type of content: a name, an identifier and a list of attributes (for example Article: Title, Intro, Body, Image)
Class groupa folder of classes in the class editor (Content, Users, Media, Setup)
Datatypethe kind of one attribute: ezstring (one line), eztext (several lines), ezxmltext (rich text), ezimage, ezobjectrelationlist and so on
Content objectone piece of content, an instance of a class; has versions (draft, published, archived) and languages
Node / locationa place of an object in the content tree; one object can have several

The datatypes available for new attributes are the AvailableDataTypes[] list of settings/content.ini; the shipped list has 35 entries. Check: grep -c 'AvailableDataTypes\[\]=' settings/content.ini prints 35. Other extensions add their own (for example ezkeyword, ezmatrix and ezselection are in the list; third-party ones are registered in their settings/).

2. Create a class

  1. Open /class/grouplist in the administration interface. This lists the class groups.
  2. Click the group Content, then New class. (/class/classlist/<group id> is the list of its classes.)
  3. Give it a name, for example Note, and an identifier note. Tick Object name pattern with <title>.
  4. In Add attribute, choose the datatype Text line and click Add; name it Title, identifier title, tick Required. Add a second attribute Text block, name Body, identifier body.
  5. Click OK (apply and close). Expected: the class appears in the list.

Expected result: Setup > Classes (or /class/view/<id>) shows Note with two attributes. The class can now be created anywhere its parent's policies allow.

3. Create and edit an object

  1. Open the content structure (/content/view/full/2), select a folder, choose Note in the create menu and click Create here.
  2. Fill the Title, press Send for publishing. Expected: the page of the new object is shown, and it is in the sub-items list of the folder.
  3. Edit it again with Edit. Each save creates a version; an unsaved edit leaves a draft you find again on /content/draft. The number of archived versions kept per class is limited by [VersionManagement] DefaultVersionHistoryLimit (default 45; VersionHistoryClass[1]=75 for folders) in settings/content.ini; the cleanup is ./console exp:cleanupversions --help --allow-root-user (read the help first; without --help it deletes old versions).

Autosave stores a draft every 180 seconds when the ezautosave extension is active (autosave.ini, [AutosaveSettings] Interval).

4. The online editor and TinyMCE 8

The rich text attribute (ezxmltext) is edited with the online editor. Two engines exist: TinyMCE 3.5.12, the default, and TinyMCE 8.9.2, opt-in since 6.0.15. Both write the same stored XML, so you can switch at any time.

Try TinyMCE 8 in one minute, for your user only:

  1. Open /user/preferences/set/ezoe_engine/tinymce8.
  2. Edit any object with a rich text attribute (for example an Article). The editor toolbar is the new engine; the status bar shows the tag path such as "Path: paragraph » embed".
  3. Go back with /user/preferences/set/ezoe_engine/tinymce3.

For everybody on a siteaccess, put into settings/siteaccess/<name>/ezoe.ini.append.php:

<?php /* #?ini charset="utf-8"?

[EditorSettings]
EditorEngine=tinymce8
EngineSwitch=enabled

*/ ?>

EngineSwitch=enabled shows the engine switch below the editor ("Editor" with every engine, the one in use marked). Then clear the INI cache: php bin/php/ezcache.php --clear-tag=ini --allow-root-user. Check the setting is read: grep -n 'EditorEngine\|EngineSwitch' extension/ezoe/settings/ezoe.ini.

Inside the editor: the toolbar embeds objects (search tab, Upload tab), links to nodes or objects, inserts custom tags, tables and lists; double click an embed to edit its view, class, size and alignment. Uploads accept only the types in UploadFileExtensions[] and refuse names such as shell.php.jpg. Depth: online editor TinyMCE 8, ezoe upgrade notes.

The order of search results in the editor's dialogs

The search tab of the embed and link dialogs, and the relation search of the administration interface, use the ezjscore search (ezjsc::search). With eZ Find as the search engine, its results come in the order of ezfind.ini [SearchSettings] SortBy[]; ezjscore ships SortBy[published]=desc, newest first, the order these searches always had. To put the best matches first, for example:

# settings/override/ezfind.ini.append.php
<?php /* #?ini charset="utf-8"?

[SearchSettings]
SortBy[]
SortBy[score]=desc
SortBy[published]=desc

*/ ?>

Clear the INI cache afterwards. Each entry is a field of eZ Find's sort (published, modified, score, name, class_identifier, path, an attribute as class/attribute) with asc or desc; an entry that is not is left out with a warning. Without eZ Find (the built-in search), or without the setting, the order stays newest first. Nothing changes on an upgrade. With thanks to Mugo Web (LoveStack, commits 3d2d337b0d and 3af1ec3aee, 2025).

Custom tag controls

For editors: click into a custom tag (a fact box, a quote, a strike-through ...) and a small bar appears on it:

ButtonKeysWhat it does
▲ Move upAlt+Shift+Upmoves a block custom tag above the block before it
▼ Move downAlt+Shift+Downmoves it below the next block (a paragraph is added after it when it becomes the last one)
✎ EditAlt+Shift+Eopens the tag's dialog
✕ RemoveAlt+Shift+Deleteremoves the tag and keeps its text

Backspace in an empty block custom tag removes it, and every block custom tag shows its name above it. A tag's dialog shows an example image when a design has images/editor/custom_tags_thumbs/<name>.png. The same works in TinyMCE 3, TinyMCE 8 (the bar is the tag's context toolbar; Ctrl+F9 reaches it from the keyboard) and Tiptap. Nothing of the bar or the labels is stored with the text.

For administrators: new installations have it on. An upgraded site keeps today's editor until you switch it on:

# settings/override/ezoe.ini.append.php
<?php /* #?ini charset="utf-8"?

[EditorSettings]
CustomTagControls=enabled

*/ ?>

Then clear the INI cache and the packed scripts: php bin/php/ezcache.php --clear-tag=ini --clear-id=ezjscore-packer --allow-root-user.

Hidden custom tags

A custom tag can stay in use without being offered for new text, for example a tag you are phasing out. List it in content.ini:

# settings/override/content.ini.append.php
<?php /* #?ini charset="utf-8"?

[CustomTagSettings]
IsHidden[]=quote

*/ ?>

The editors (TinyMCE 3, TinyMCE 8, Tiptap) leave it out of the custom tag list for a new tag. Where it is already in a text it is shown, rendered and saved as before, and its own dialog still opens with its name. None is hidden by default, so nothing changes on an upgrade. For developers: eZOEXMLInput::hiddenCustomTags(), eZOEXMLInput::offeredCustomTags( $tags, $current ), and the keys hidden and preview of eZOEXMLInput::getCustomTagDefinitions().

Both were rebuilt from LoveStack, Mugo Web's distribution of the same kernel, with thanks to Mugo Web (commits 63334f1681, the custom_tags_controls plugin, 2019, and e21ef46691, 2019). The sub/sup dialog closing on OK (LoveStack commit 3a8c14bc0c) was already part of Exponential's editor.

5. The sub-items table

Open a folder that has children. The table below the node's view is the sub-items list.

  1. Click Table options.
  2. Tick columns (129 are available, grouped Basic, Node, Object, Dates, SEO and so on) and drag to reorder; choose a preset (SEO, Editorial, Technical) or a page size.
  3. Under items per page pick Custom and type a number from 1 to 10000, press Enter.
  4. Use the CSV export to download the visible columns. Your choice is stored per user.

Expected: the table reloads at once with the chosen columns and rows. Depth: sub-items table, catalogue and how to add a column, paging settings.

6. Edit many items in one form (multi-node edit)

  1. In the sub-items list tick several items, then More actions > Edit selected. Or search (/content/search), tick results across the whole site and click Edit selected.
  2. The form shows one section per content class and a collapsible panel per object. Expand all and Collapse all are above it.
  3. Press Save drafts to keep the work without publishing, Publish all to publish each object, or Discard to throw the drafts away. The report lists which objects were published, waiting for approval or refused.

To make several new items at once use Create multiple new next to Create new (up to 50 at a time). After creating, check the new items appear under their parent. Depth: multi-node edit.

7. The trash

  1. Select an item, Remove, confirm. The item moves to the trash (unless you choose delete).
  2. Open /content/trash. Each row shows who trashed the item, where it was, how many nodes were below it and its languages. Filter by user, type or date; Restore puts an item back (/content/restore/<object id>).
  3. Empty trash deletes everything for good, whatever the filter. From the shell the same is ./console exp:trashpurge --apply --allow-root-user: it is permanent, so do not run it on a site you care about.

Depth: trash: who and where, trash upgrade notes.

8. Large operations as content jobs

Removing, copying or moving a big subtree can run as a background job that works in batches and resumes after a crash.

  1. Start a remove, copy or move on a subtree. The confirmation page offers run now or in the background; from 50 nodes on, "background" is offered first ([ContentJobSettings] SynchronousLimit=50 in settings/content.ini).
  2. The browser follows /content/job/<id>: progress, batches, buttons to cancel or resume. All jobs are listed on /content/jobs.
  3. From the shell:
./console exp:expcontentjob list --allow-root-user
./console exp:expcontentjob show <id> --allow-root-user
./console exp:expcontentjob resume <id> --allow-root-user
./console exp:expcontentjob cancel <id> --allow-root-user

On an installation without jobs list prints No active or failed content jobs (--all lists every job).. The contentjobs cronjob part restarts a worker whose process died. Depth: content jobs, content jobs guide, audit trail (every job is recorded).

9. Required alternative text, trimmed text lines and names made by PHP

These four options came to Exponential 6.0.15 from LoveStack, Mugo Web's distribution of the legacy kernel, with thanks to Mugo Web for the ideas and the original code. All of them are off until you switch them on, on new and upgraded installations alike, so nothing changes on an existing site by itself.

Alternative text is required (image fields)

For editors: an image field whose class has the option switched on cannot be stored with an image but without an alternative text. The edit form marks the field Alternative image text (required) and answers An alternative text is required for this image. when it is empty. The same check applies in the online editor's upload dialog (also when the Tiptap engine uploads through it) and in the AJAX uploader of relation fields, which now has an Alternative text field for classes with an image attribute. An image field left empty needs no text.

For administrators: Setup > Classes, edit the class, find the image attribute and tick Alternative text is required; click OK. The class view then shows Alternative text is required: Yes. The setting is stored in the class attribute's data_int2 (the maximum file size stays in data_int1) and travels with the class in a package as <alternative-text-required>1</alternative-text-required>.

Not covered: the generic file upload (content/upload, multi-upload) and imports create images without asking for a text; fill it in afterwards. The expai extension's Missing alt text list shows such images.

Trim the stored value (text line fields)

For editors: with the option on, spaces, tabs and line breaks at the start and the end of a text line are removed when it is stored; space inside the text is kept. For administrators: edit the class, tick Trim the stored value on the text line attribute. It applies to the edit form, information collection, imports (fromString()) and the simple string insertion of uploads. Stored in data_int2; in a package <trim>1</trim>. Text stored before is left as it is until it is saved again.

The class edit form sends a hidden marker with both checkboxes, so a design whose class edit template does not show an option leaves its value as it is.

Names made by a PHP class (developers)

A class's Object name pattern or URL alias name pattern may start with a PHP class name in braces, for example {myNameResolver} or {myNameResolver} <title>. That class then makes the name. It must:

  1. be listed in settings/content.ini (an override) [NamePatternSettings] ResolverClasses[]=myNameResolver;
  2. implement expNamePatternResolverInterface (kernel/classes/expnamepatternresolverinterface.php): it is constructed with the rest of the pattern after {myNameResolver} and its resolveNamePattern( $object, $version, $translation, $length, $sequence ) returns the name;
  3. be autoloadable (an extension's classes/, then php bin/php/ezpgenerateautoloads.php -e).

A name that is not listed is never loaded or made: it is logged as a warning and the pattern is resolved from its attribute tokens as before. A listed class without the interface, or one that throws, is logged as an error and the tokens are used. The name is shortened to the usual limits ([ContentSettings] ContentObjectNameLimit, [URLTranslator] UrlAliasNameLimit). In PHP: eZNamePatternResolver::resolve( $pattern, $object, ... ).

Class filter default for fetches (template developers)

fetch( 'content', 'list', hash( 'parent_node_id', 2, 'class_filter_array', array( 'article' ) ) ) without class_filter_type ignores the array today. With settings/content.ini [FetchSettings] DefaultClassFilterType=include such a fetch lists only those classes (exclude: every class but those). It applies to list, tree, list_count, tree_count and calendar; a fetch that gives class_filter_type keeps it. Empty, the shipped value, keeps today's results. Switch it on only after checking the templates that rely on the array being ignored: grep -rn "class_filter_array" design extension --include=*.tpl | grep -v class_filter_type.

10. Copying, locations and how selections are shown

These come from LoveStack (mugoweb/ezpublish-legacy), rebuilt in Exponential with thanks to Mugo Web; the commits are named with each item.

Copy from the full view

Next to Move and Remove, the full view of an item in the administration interface (admin, admin3 and admin4) has a Copy button (LoveStack commit 4d56593614). It opens the same steps as Copy in the right-click menu: choose where the copy goes, then, if your site asks for it, which versions to copy. The button is active when you may create content in the item's parent, as the menu is; otherwise it is greyed out and says why. You still need create access where the copy goes, which is checked when you choose the place. Nothing to switch on.

The Locations window keeps a hidden location hidden

In the edit form's Locations window, the Visibility after publishing choice of an existing location now starts at Unchanged instead of Visible (EZP-26501, LoveStack commit f1b7f056b4). Publishing after adding a location therefore no longer reveals a location that was hidden. A new location starts as it was given (Hidden when it was added hidden, else Unchanged, which follows its parent). Visible and Hidden can still be chosen for any location. This is a fix and applies to every site.

Selection and matrix fields without stray line breaks

The standard view templates of the selection (ezselection) and matrix (ezmatrix) fields write no stray <br /> (LoveStack commits eee7f69fc3, 67eec8f972, 492a946963): one selected option is shown on its own, several are separated by one <br /> each, in the order of the class, and the matrix is a table and nothing else. A site with its own override of these templates is not affected.

Settings used in this guide

FileBlockKeyDefaultScope
settings/content.ini[DataTypeSettings]AvailableDataTypes[]35 datatypesinstallation, siteaccess
settings/content.ini[VersionManagement]DefaultVersionHistoryLimit45 (VersionHistoryClass[1]=75)installation, siteaccess
settings/content.ini[ContentJobSettings]SynchronousLimit50 nodesinstallation, siteaccess
settings/content.ini[NamePatternSettings]ResolverClasses[]emptyinstallation, siteaccess
settings/content.ini[FetchSettings]DefaultClassFilterTypeempty (array ignored)installation, siteaccess
extension/ezautosave/settings/autosave.ini.append.php[AutosaveSettings]Interval180 secondsinstallation, siteaccess
extension/ezoe/settings/ezoe.ini[EditorSettings]EditorEnginetinymce3installation, siteaccess, user preference ezoe_engine
extension/ezoe/settings/ezoe.ini[EditorSettings]EngineSwitchdisabledinstallation, siteaccess
extension/ezoe/settings/ezoe.ini[EditorSettings]CustomTagControlsdisabled (enabled in new installations)installation, siteaccess
settings/content.ini[CustomTagSettings]IsHidden[]emptyinstallation, siteaccess

Change a value in settings/override/ or settings/siteaccess/<name>/, never in the shipped file, then clear the INI cache: php bin/php/ezcache.php --clear-tag=ini --allow-root-user.

Become an Exponential Partner

Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.

Something wrong on this page? Report an error

Documentation license

This documentation is free documentation, licensed under the GNU Free Documentation License 1.3 or later.

Exponential Copyright © 1998-2026 Exponential Foundation and 7x