Previous Key:doc_core_api
Description:Reference to the Core APIs of TYPO3, e.g. main classes, Extension API, RTE API.
Keywords:tsref, typoscript, reference, forDevelopers, forAdvanced
Author:Documentation Team
License:Open Publication License available from www.opencontent.org/openpub/
Rendered:2016-10-23 12:10

The content of this document is related to TYPO3 CMS, a GNU/GPL CMS/Framework available from www.typo3.org

Official Documentation

This document is included as part of the official TYPO3 documentation. It has been approved by the TYPO3 Documentation Team following a peer- review process. The reader should expect the information in this document to be accurate - please report discrepancies to the Documentation Team (documentation@typo3.org). Official documents are kept up-to-date to the best of the Documentation Team's abilities.

Core Manual

This document is a Core Manual. Core Manuals address the built in functionality of TYPO3 CMS and are designed to provide the reader with in- depth information. Each Core Manual addresses a particular process or function and how it is implemented within the TYPO3 source code. These may include information on available APIs, specific configuration options, etc.

Core Manuals are written as reference manuals. The reader should rely on the Table of Contents to identify what particular section will best address the task at hand.




TYPO3 is known for its extensibility. However to really benefit from this power, a complete documentation is needed. "Core APIs" and its companion, "Inside TYPO3", aim to provide such information to developers and administrators. Not all areas are covered with the same amount of details, but at least some pointers are provided.

"Inside TYPO3" contains the overall introduction to the architecture of the TYPO3 core. It also contains API descriptions to a certain degree but mostly in the form of examples and short table listings. "Core APIs" goes into much more detail about such APIs and covers subjects more closely related to development.

These documents do not contain any significant information about the frontend of TYPO3. Creating templates, setting up TypoScript objects etc. is not the scope of these documents; they are about the backend part of the core only.

The TYPO3 Documentation Team hopes that these two documents, "Inside TYPO3" and "TYPO3 Core APIs", will form a complete picture of the TYPO3 Core architecture, the backend and be the reference of choice in your work with TYPO3. It took Kasper more than a year to get the first version published and we've tried to maintain it as best we could.

What's new

This version is updated for TYPO3 CMS 6.2.

Many recent changes in the TYPO3 CMS code base were documented in this new version of Core APIs, some dating back to version 6.1. The release of a new LTS version was the opportunity to put extra efforts into the manuals.

Highlights from new features brought by TYPO3 CMS 6.2:

  • an Application Context, backported from TYPO3 Flow.
  • changes to the caching framework, in particuler the new cache groups.
  • well-known folder t3lib is now gone and so is constant PATH_t3lib.
  • a new API for registering AJAX handlers which provides CSRF protection (documentation yet missing, but will come very soon).
  • the system categories API has matured and the related chapter was extended. In particular, it is now possible to have more than one categories field per table.
  • usage of flash messages in Extbase has changed.
  • it is possible to define a custom mirror to fetch extension translations from.

Code examples

Many of the code examples found in this document come from the TYPO3 Core itself.

Quite a few others come from the "examples" extension which is available in the TER. You can install it if you want to try out these examples yourself and use them as a basis for your own stuff.

Yet some other examples just belong to this manual. Some may be moved to the "examples" extension at some later stage.


For general questions about the documentation get in touch by writing to documentation@typo3.org .

If you find a bug in this manual, please be so kind as to check the online version on https://docs.typo3.org/typo3cms/CoreApiReference/. From there you can hit the "Edit me on GitHub" button in the top right corner and submit a pull request via GitHub. Alternatively you can just file an issue using the bug tracker: https://github.com/TYPO3-Documentation/TYPO3CMS-Reference-CoreApi/issues.

Maintaining high quality documentation requires time and effort and the TYPO3 Documentation Team always appreciates support. If you want to support us, please join the documentation mailing list/forum (http://forum.typo3.org/index.php/f/44/).


This manual was originally written by Kasper Skårhøj. It was further maintained, refreshed and expanded by François Suter.


I want to dedicate this document to the people in the TYPO3 community who have the discipline to do the boring job of writing documentation for their extensions or contribute to the TYPO3 documentation in general. It's great to have good coders, but it's even more important to have coders with character to carry their work through till the end - even when it means spending days writing good documents. Go for completeness!

- kasper

Extension Architecture


TYPO3 can be extended in nearly any direction without loosing backwards compatibility. The Extension API provides a powerful framework for easily adding, removing, installing and developing such extensions to TYPO3. This is in particular powered by the Extension Manager (EM) inside TYPO3 and the online TYPO3 Extension Repository (TER) found at typo3.org for easy sharing of extensions.

"Extensions" is a general term in TYPO3 which covers many kinds of additions to TYPO3. The main types are:

  • Plugins which play a role on the website itself, e.g. a discussion board, guestbook, shop, etc. It is normally enclosed in a PHP class and invoked through a USER or USER_INT cObject from TypoScript. A plugin is an extension in the frontend.
  • Modules are backend applications which have their own entry in the main menu. They require a backend login and work inside the framework of the backend. We might also call something a module if it exploits any connectivity of an existing module, that is if it simply adds itself to the function menu of existing modules. A module is an extension in the backend.
  • Services are libraries that provide a given service through a clearly defined API. A service may exist both in the frontend and the backend. Please refer to the TYPO3 Services Reference for more information about this type of extension.
  • Distributions are fully packaged TYPO3 CMS web installations, complete with files, templates, extensions, etc. Distributions are covered in their own chapter.

Extensions and the Core

Extensions are designed in a way so that extensions can supplement the core seamlessly. This means that a TYPO3 system will appear as "a whole" while actually being composed of the core application and a set of extensions providing various features. This philosophy allows TYPO3 to be developed by many individuals without loosing fine control since each developer will have a special area (typically a system extension) of responsibility which is effectively encapsulated.

So, at one end of the spectrum system extensions make up what is known as "TYPO3" to the outside world. At the other end, extensions can be entirely specific to a given project and contain only files and functionality related to a single implementation.

Files and locations

An extension consists of:

  1. a directory named by the extension key (which is a worldwide unique identification string for the extension)
  2. standard files with reserved names for configuration related to TYPO3 (of which most are optional, see list below)
  3. any number of additional files for the extension itself.
Reserved filenames

This list of filenames are all reserved filenames in the root directory of extensions. None of them are required but for example you cannot have a TYPO3 extension recognized by TYPO3 without the "ext_emconf.php" file etc. You can read more details like that in the table below.

In general, do not introduce your own files in the root directory of extensions with the name prefix "ext_".

Filename Description

Definition of extension properties.

Name, category, status etc. Used by the EM. The content of this file is described in more details below. Note that it is auto-written by EM when extensions are imported from the repository.


If this file is not present the EM will not find the extension.


Addition to LocalConfiguration.php which is included if found. Should contain additional configuration of $TYPO3_CONF_VARS and may include additional PHP class files.

All ext_localconf.php files of included extensions are included right after the typo3conf/LocalConfiguration.php file has been included and database constants defined. Therefore you cannot setup database name, username, password though, because database constants are defined already at this point.


Observe rules for content of these files. See section on caching below.


Included if found. Contains extensions of existing tables, declaration of modules, backend styles etc. All code in such files is included after all the default definitions provided by the Core.

Since TYPO3 CMS 6.1, definition of new database tables should be done entirely in Configuration/TCA/(name of the table).php. These files are expected to contain the full TCA of the given table (as an array) and simply return it (with a return statement).

Since TYPO3 CMS 6.2, customizations of existing tables should be done entirely in Configuration/TCA/Overrides/(name of the table).php. This way the TCA changes are cached.


SQL definition of database tables.

This file should contain a table-structure dump of the tables used by the extension. It is used for evaluation of the database structure and is therefore important to check and update the database when an extension is enabled.If you add additional fields (or depend on certain fields) to existing tables you can also put them here. In that case insert a CREATE TABLE structure for that table, but remove all lines except the ones defining the fields you need.

The ext_tables.sql file may not necessarily be "dumpable" directly to MySQL (because of the semi-complete table definitions allowed defining only required fields, see above). But the EM or Install Tool can handle this. The only very important thing is that the syntax of the content is exactly like MySQL made it so that the parsing and analysis of the file is done correctly by the EM.


Static SQL tables and their data.

If the extension requires static data you can dump it into a sql-file by this name.Example for dumping mysql data from bash (being in the extension directory):

mysqldump --password=[password] [database name] [tablename] --add-drop-table > ./ext_tables_static.sql

--add-drop-table will make sure to include a DROP TABLE statement so any data is inserted in a fresh table.

You can also drop the table content using the EM in the backend.


The table structure of static tables needs to be in the ext_tables.sql file as well - otherwise an installed static table will be reported as being in excess in the EM!


Preset TypoScript constants. Will be included in the constants section of all TypoScript templates.


Use such a file if you absolutely need to load some TS (because you would get serious errors without it). Otherwise static templates or usage of the Extension Management API are preferred.


Preset TypoScript setup. Will be included in the setup section of all TypoScript templates.


Use such a file if you absolutely need to load some TS (because you would get serious errors without it). Otherwise static templates or usage of the Extension Management API are preferred.


Extension Configuration template.

Configuration code in TypoScript syntax setting up a series of values which can be configured for the extension in the EM. Read more about the file format here.

If this file is present the EM provides you with an interface for editing the configuration values defined in the file. The result is written as a serialized array to LocalConfiguration.php in the variable $TYPO3_CONF_VARS['EXT']['extConf'][ *extension_key* ]

If you want to do user processing before the content from the configuration form is saved (or shown for that sake) there is a hook in the EM which is configurable with $TYPO3_CONF_VARS ['SC_OPTIONS']['typo3/mod/tools/em/index.php']['tsStyleConfigForm'][] = " *function reference* "

ext_icon.gif, ext_icon.png or ext_icon.svg

Extension Icon

18x16 GIF, PNG or SVG icon for the extension.


Extension icon will look nicer when provided as vector graphics (SVG) rather than bitmaps (GIF or PNG).


Local Update tool class

If this file is found it will install a new menu item, "UPDATE", in the EM when looking at details for the extension. When this menu item is selected the class inside of this file (named "ext_update") will be instantiated and the method "main()" will be called and expected to return HTML content.

Also you must add the function "access()" and make it return a boolean value whether or not the menu item should be shown. This feature is meant to let you disable the update tool if you can somehow detect that it has already been run and doesn't need to run again.The point of this file is to give extension developers the possibility to provide an update tool if their extensions in newer versions require some updates to be done.


Since TYPO3 CMS 4.3, it is possible to declare classes in this file so that they will be automatically detected by the TYPO3 autoloader. This means that it is not necessary to require the related class files anymore. See the Autoloading chapter for more details.

Not needed anymore since TYPO3 CMS 6.1, when using namespaces.


PHP API data

A file containing a serialized PHP array with API information for the PHP classes in the extension. The file is created - and viewed! - with tools found in the extension "extdeveval" (Extension Development Evaluator)

Reserved folders

The current standard for files location - except for the special files mentioned above - is inspired by TYPO3 Flow. It is necessary to use such structure in Extbase-based extensions and recommended for all extensions anyway.

In order to use Namespaces, class files must be located in a Classes folder.

Refer to the Extbase and Fluid book for more information on extension structure. Also look at the "examples" extension.

The Extension Builder extension will create the right structure for you. It is described below:

Contains MVC Controller classes.
Contains MVC Domain model classes.
Contains data repository classes.
Helper classes used in the views.
TypoScript static setup (setup.txt) and constants (constants.txt). Use subfolders if your have several static templates.
One file per database table, using the name of the table for the file, plus ".php". Only for new tables.
For extending existing tables, one file per database table, using the name of the table for the file, plus ".php".
Contains the manual in reStructuredText format (read more on the topic).
XLIFF files for localized labels.
Main layouts for the views.
Partial templates for repetitive use.
One template per action, stored in a folder named after each Controller.
Any CSS file used by the extension.
Any images used by the extension.
Any JS file used by the extension.
Contains unit testing classes.
Legacy structure

The structure of older extensions was not so clearly defined, but it generally adhered to the following conventions:

Filename Description
pi*/ Typical folder for a frontend plugin class.
mod*/ Typical folder for a backend module.
sv*/ Typical folder for a service.

Extensions normally consist of other files: Classes, images, html- files etc. Files not related to either a frontend plugin (pi/) or backend module (mod/) might be put in a subfolder of the extension directory named "res/" (for "resources") but you can do it as you like (inside of the extension directory that is).The "res/" folder content will be listed as files you can select in the configuration interface.

Files in this folder can also be selected in a selector box if you set up Extension configuration in a "ext_conf_template.txt" file.

System, Global and Local extensions

The files for an extension are located in a folder named by the extension key . The location of this folder can be either inside typo3/sysext/, typo3/ext/ or typo3conf/ext/.

The extension must be programmed so that it does automatically detect where it is located and can work from all three locations. If it is not possible to make the extension that flexible, it is possible to lock its installation requirement to one of these locations in the ext_emconf.php file (see "lockType").

Local extensions

Local extensions are located in the typo3conf/ext/ directory.

This is where to put extensions which are local for a particular TYPO3 installation. The typo3conf directory is always local, containing local configuration (e.g. LocalConfiguration.php), local modules etc. If you put an extension here it will be available for a single TYPO3 installation only. This is a "per-database" way to install an extension.


Local extension can successfully be symlinked to other local extensions on a server as long as they are running under the same TYPO3 source version (which would typically also be symlinked). This method is useful for maintenance of the same local extension running under several sites on a server.

Global extensions

Global extensions are located in the typo3/ext/ directory.

This is a "per-server" way to install an extension; they are global for the TYPO3 source code on the web server. These extensions will be available for any TYPO3 installation sharing the source code.


This features has not been consistently supported in recent versions of TYPO3, so you may encounter problems when using it.

System extensions

System extensions are located in the typo3/sysext/ directory.

This is system default extensions which cannot and should not be updated by the EM. They are distributed with TYPO3 core source code and generally understood to be a part of the core system.

Loading precedence

Local extensions take precedence which means that if an extension exists both in typo3conf/ext/ and typo3/ext/ the one in typo3conf/ext/ is loaded. Likewise global extension takes precedence over system extensions. This means that extensions are loaded in the order of priority local-global-system.

In effect you can therefore have - say - a "stable" version of an extension installed in the global dir (typo3/ext/) which is used by all your projects on a server sharing source code, but on a single experimental project you can import the same extension in a newer "experimental" version and for that particular project the locally available extension will be used instead.

Choosing an extension key

The "extension key" is a string uniquely identifying the extension. The folder where the extension resides is named by this string. The string can contain characters a-z0-9 and underscore. No uppercase characters should be used (keeps folder-,file- and table/field-names in lowercase). Furthermore the name must not start with an "tx" or "u" (this is prefixes used for modules) and because backend modules related to the extension should be named by the extension name without underscores, the extension name must still be unique even if underscores are removed (underscores are allowed to make the extension key easily readable).

The naming conventions of extension keys are automatically validated by the registration at the repository, so you have nothing to worry about here.

There are two ways to name an extension:

  • Project specific extensions (not generally usable or shareable): Select any name you like and prepend it "user_" (which is the only allowed use of a key starting with "u"). This prefix denotes that this extension is a local one which does not come from the central TYPO3 Extension Repository or is ever intended to be shared. Probably this is an "adhoc" extension you have made for some special occasion.
  • General extensions: Register an extension name online at the TYPO3 Extension Repository. Your extension name will automatically be validated and you are sure to have a unique name returned which nobody else in the world uses. This makes it very easy to share your extension later on with every one else, because it ensures that no conflicts with other extension will happen. But by default a new extension you make is defined "private" which means nobody else but you have access to it until you permit it to be public. It's free of charge to register an extension name. By definition all code in the TYPO3 Extension Repository is covered by the GPL license because it interfaces with TYPO3. You should really consider making general extensions!


It is far easier to settle for the right extension key from the beginning. Changing it later involves a cascade of name changes to tables, modules, configuration files etc. Think carefully.

About GPL and extensions

Remember that TYPO3 is GPL software and at the same moment you extend TYPO3 your extensions are legally covered by GPL. This does not force you to share your extension, but it should inspire you to do so and legally you cannot prevent anyone who gets hold of your extension code from using it and further develop it.The TYPO3 Extension API is designed to make sharing of your work easy as well as using others work easy. Remember TYPO3 is Open Source Software and we rely on each other in the community to develop it further.


It's also your responsibility to make sure that all content of your extensions is legally covered by GPL. The webmaster of TYPO3.org reserves the right to kick out any extension without notice that is reported to contain non-GPL material.


You are responsible for security issues in your extensions. People may report security issues either directly to you or to the TYPO3 Security Team. Whatever the case you should get in touch with the Security Team which will validate the security fixes. They will also include information about your (fixed) extension in their next Security bulletin. If you don't respond to requests from the Security Team, your extension will be forcibly removed from the TYPO3 Extension Repository.

More details on the security team's policy on handling security issues can be found at http://typo3.org/teams/security/extension-security-policy/.

Registering an extension key

Before starting a new extension you should register an extension key on typo3.org (unless you plan to make an implementation-specific extension – of course – which it does not make sense to share).

Go to typo3.org, log in with your (pre-created) username / password and go to Extensions > Extension Keys and click on the "Register keys" tab. On that page you can enter the key name you want to register.

The extension registration form

The extension registration form on typo3.org.

Naming conventions

Based on the extension key of an extension these naming conventions should be followed:

  General Example User-specific Example

Extension key

(Lowercase "alnum" + underscores. )

Assigned by the TYPO3 Extension Repository. cool_shop Determined by yourself, but prefixed "user_" user_my_shop
Database tables and fields Prefix with "tx_[ key ]_" where key is without underscores!

Prefix: tx_coolshop_




Prefix with "[ key ]_"

Prefix: user_my_shop_




Backend module

(Names are always without underscores!)

Name: The extension key name without underscores, prefixed "tx" txcoolshop Name: No underscores, prefixed "u" uMyShop or umyshop or ...

For frontend PHP classes, follow the same conventions as for database tables and field, but prepend class file names with class.

You may also want to refer to the TYPO3 Core Coding Guidelines for more on general naming conventions in TYPO3.


If you study the naming conventions above closely you will find that they are complicated due to varying rules for underscores in key names. Sometimes the underscores are stripped off, sometimes not.

The best practice you can follow is to avoid using underscores in your extensions keys at all! That will make the rules simpler. This is highly encouraged.

Note on "old" extensions:

Some the "classic" extensions from before the extension structure came about do not comply with these naming conventions. That is an exception made for backwards compatibility. The assignment of new keys from the TYPO3 Extension Repository will make sure that any of these old names are not accidentially reassigned to new extensions.

Further, some of the classic plugins (tt_board, tt_guest etc) users the "user_" prefix for their classes as well.

Extending "extensions classes"

As a standard procedure you should include the "class extension code" even in your own extensions. This is placed at the bottom of every class file:

if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/myext/pi1/class.tx_myext_pi1.php'])) {

Normally the key used as example here ("ext/myext/pi1/class.tx_myext_pi1.php") would be the full path to the script relative to the PATH_site constant. However because modules are required to work from both typo3/sysext/, typo3/ext/ and typo3conf/ext/ it is a policy that any path before "ext/" is omitted.

Installing extensions

There are only two (possibly three) steps involved in using extensions with TYPO3:

  1. You must import it.

    This simply means to copy the extensions files into the correct directory in either typo3/ext/ (global) or typo3conf/ext/ (local). More commonly you import an extension directly from the online TYPO3 Extension Repository (TER). When an extension is found located in one of the extension locations, it is available to the system.

    The Extension Manager (EM) should take care of this process, including updates to newer versions if needed.Notice that backend modules will have their "conf.php" file modified in the install process depending on whether they are installed locally or globally!

  2. You must install it.

    An extension is loaded only if its state is set to active in the PackageStates.php file. Extensions are loaded in the order they appear in this list.

    An enabled extension is always global to the TYPO3 Installation - you cannot disable an extension from being loaded in a particular branch of the page tree.The EM takes care enabling extensions. It's highly recommended that the EM is doing this, because the EM will make sure the priorities, dependencies and conflicts are managed according to the extension characteristics, including clearing of the cache-files if any.

  3. You might need to configure it.

    Certain extensions may allow you to configure some settings. Again the EM is able to handle the configuration of the extensions based on a certain API for this. Any settings - if present - configured for an extension are available as an array in the variable $TYPO3_CONF_VARS["EXT"]["extConf"][extension key].

Loaded extensions are registered in a global variable, $TYPO3_LOADED_EXT, available in both frontend and backend of TYPO3.

This is how the data structure for an extension in this array looks:

$TYPO3_LOADED_EXT[extension key] = array(
        "type" =>                S, G, L for system, global or local type of availability.
        "siteRelPath" => Path of extension dir relative to the PATH_site constant
                                e.g. "typo3/ext/my_ext/" or "typo3conf/ext/my_ext/"
        "typo3RelPath" => Path of extension dir relative to the "typo3/" admin folder
                                e.g. "ext/my_ext/" or "../typo3conf/ext/my_ext/"
        "ext_localconf" => Contains absolute path to 'ext_localconf.php' file if present
        "ext_tables" => [same]
        "ext_tables_sql" => [same]
        "ext_tables_static+adt.sql" => [same]
        "ext_typoscript_constants.txt" => [same]
        "ext_typoscript_setup.txt" => [same]
        "ext_typoscript_editorcfg.txt" => [same]

The order of the registered extensions in this array corresponds to the order they were listed in PackageStates.php.

The inclusion of ext_tables.php or ext_localconf.php files (see next chapter) is done by traversing (a copy of) the $TYPO3_LOADED_EXT array.

Declaration file

The ext_emconf.php is the single most important file in an extension. Without it, the Extension Manager (EM) will not detect the extension, much less be able to install it. This file contains a declaration of what the extension is or does for the EM. The only thing included is an associative array, $EM_CONF[extension key]. The keys are described in the table below.

When extensions are imported from the online repository this file is written anew! So don't put any custom stuff in there - only change values in the $EM_CONF array if needed.

Key Data type Description
title string, required The name of the extension in English.
description string, required Short and precise description in English of what the extension does and for whom it might be useful.
category string

Which category the extension belongs to:

  • be

    Backend (Generally backend-oriented, but not a module)

  • module

    Backend modules (When something is a module or connects with one)

  • fe

    Frontend (Generally frontend oriented, but not a "true" plugin)

  • plugin

    Frontend plugins (Plugins inserted as a "Insert Plugin" content element)

  • misc

    Miscellaneous stuff (Where not easily placed elsewhere)

  • services

    Contains TYPO3 services

  • templates

    Contains website templates

  • example

    Example extension (Which serves as examples etc.)

  • doc

    Documentation (e.g. tutorials, FAQ's etc.)

  • distribution

    Distribution, an extension kickstarting a full site

constraints array

List of requirements, suggestions or conflicts with other extensions or TYPO3 or PHP version. Here's how a typical setup might look:

'constraints' => array(
    'depends' => array(
        'typo3' => '4.5.0-6.1.99',
        'php' => '5.3.0-5.5.99'
    'conflicts' => array(
        'dam' => ''
    'suggests' => array(
        'tt_news' => '2.5.0-0.0.0'
List of extensions that this extension depends on. Extensions defined here will be loaded before the current extension.
List of extensions which will not work with this extension.

List of suggestions of extensions that work together or enhance this extension. Extensions defined here will be loaded before the current extension. Dependencies take precedence over suggestions.

Note: If a "suggested" extension depends on the current extension (directly or indirectly), the suggestion is not taken into account for loading order calculation. Read more at Forge #57825.

The above example indicated that the extension depends on a version of TYPO3 between 4.5 and 6.1 (as only bug and security fixes are integrated into TYPO3 when the last digit of the version changes, it is safe to assume it will be compatible with any upcoming version of the corresponding branch, thus .99). Also the extension has been tested and is known to work properly with PHP 5.3, 5.4 and 5.5. It will conflict with the DAM (any version) and it is suggested that it might be worth installing "tt_news" (version at least 2.5.0).

state string

Which state is the extension in

  • alpha

    Alpha state is used for very initial work, basically the state is has during the very process of creating its foundation.

  • beta

    Under current development. Beta extensions are functional but not complete in functionality. Most likely beta-extensions will not be reviewed.

  • stable

    Stable extensions are complete, mature and ready for production environment. You will be approached for a review. Authors of stable extensions carry a responsibility to be maintain and improve them.

  • experimental

    Experimental state is useful for anything experimental - of course. Nobody knows if this is going anywhere yet... Maybe still just an idea.

  • test

    Test extension, demonstrates concepts etc.

  • obsolete

    The extension is obsolete or deprecated. This can be due to other extensions solving the same problem but in a better way or if the extension is not being maintained anymore.

  • excludeFromUpdates

    This state makes it impossible to update the extension through the extension manager (neither by the Update mechanism, nor by uploading a newer version to the installation). This is very useful if you made local changes to an extension for a specific installation and don't want any admin to overwrite them.

    New since TYPO3 4.3.

uploadfolder boolean If set, then the folder named "uploads/tx_[extKey-with-no- underscore]" should be present!
createDirs list of strings Comma list of directories to create upon extension installation.
clearCacheOnLoad boolean If set, the EM will request the cache to be cleared when this extension is loaded.
author string Author name (Use a-z)
author_email email address Author email address
author_company string Author company (if any company sponsors the extension).
docPath string

Path to documentation. This has never been fully supported neither by the TER nor by the Extension Manager. The documentation is expected to be in folder doc when using OpenOffice/LibreOffice format and in folder Documentation when using reStructuredText (recommended). See Adding documentation for more information.


CGLcompliance keyword

Compliance level that the extension claims to adhere to. A compliance defines certain coding guidelines, level of documentation, technical requirements (like XHTML, DBAL usage etc).


CGLcompliance_note string

Any remarks to the compliance status. Might describe some minor incompatibilities or other reservations.


private boolean

If set, this version of the extension is not included in the public list!

Not supported anymore

download_password string

If set, this password must additionally be specified if people want to access (import or see details for) this the extension.

Not supported anymore

version main.sub.dev Version of the extension. Automatically managed by EM / TER. Format is [int].[int].[int]
Configuration files

Files ext_tables.php and ext_localconf.php are the two most important files for the execution of extensions within TYPO3. They contain configuration used by the system on almost every request. They should therefore be optimized for speed.

  • ext_localconf.php is always included in global scope of the script, either frontend or backend.

    While you can put functions and classes into the script, it is a really bad practice because such classes and functions would always be loaded. It is better to have them included only as needed.

    So stick to changing values in TYPO3_CONF_VARS only!

  • ext_tables.php is not always included in global scope (in the frontend)

    It should still not contain functions and classes as it still very often loaded.

  • Use the API of class extMgm for tasks such as adding tables, merging information into arrays, etc.

  • Before the inclusion of any of the two files, the variables $_EXTKEY is set to the extension key and $_EXTCONF is set to the configuration from $TYPO3_CONF_VARS["EXT"]["extConf"][extension key]

  • $TYPO3_LOADED_EXT[extension key] contains information about whether the module is loaded as local, global or system type, including the proper paths you might use, absolute and relative.

  • The inclusion can happen in two ways:

    • Either the files are included individually on each request (many file includes) ($TYPO3_CONF_VARS["EXT"]["extCache"]=0;)
    • or (better) the files are automatically imploded into one single temporary file (cached) in typo3temp/Cache/Code/cache_core directory (only one file include) ($TYPO3_CONF_VARS["EXT"]["extCache"]=1;). This is default.

    In effect this means:

    • Your ext_tables.php and ext_localconf.php file must be designed so that they can safely be read and subsequently imploded into one single file with all the other configuration scripts!
    • You must never use a "return" statement in the files global scope - that would make the cached script concept break.
    • You should not rely on the PHP constant __FILE__ for detection of include path of the script - the configuration might be executed from a cached script and therefore such information should be derived from the $TYPO3_LOADED_EXT[extension key] array, e.g. $TYPO3_LOADED_EXT[$_EXTKEY]["siteRelPath"].
Configuration options

In the ext_conf_template.txt file configuration options for an extension can be defined. They will be accessible in the TYPO3 backend from the Extension Manager.

There's a specific syntax to declare these options properly, which is similar to the one used for TypoScript constants (see "Declaring constants for the Constant editor" in "TypoScript Syntax and In-depth Study"). This syntax applies to the comment line that should be placed just before the constant. Consider the following example (taken from system extension "rsaauth"):

# cat=basic/enable; type=string; label=Path to the temporary directory:This directory will contain...
temporaryDirectory =

First a category (cat) is defined ("basic") with the subcategory "enable". Then a type is given ("string") and finally a label, which is itself split (on the colon ":") into a title and a description (this should actually be a localized string). The above example will be rendered like this in the EM:

Configuration screen for the rsaauth extension

The configuration tab displays all options from a single category. A selector is available to switch between categories. Inside an option screen, options are grouped by subcategory. At the bottom of the screenshot, the label – split between header and description – is visible. Then comes the field itself, in this case an input, because the option's type is "string".

Once you saved the configuration in the ExtensionManager, it will be stored in $GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['your_extension_key'] as a serialized array.

To fetch the value of temporaryDirectory from the example above, you could simply use:

$extensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['your_extension_key']);
$temporaryDirectory = $extensionConfiguration['temporaryDirectory'];

Or even better use the API to get the information merged with the default settings if the settings have not been saved yet:

/** @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility */
$configurationUtility = $this->objectManager->get('TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility');
$extensionConfiguration = $configurationUtility->getCurrentConfiguration('themes');

You can also define nested options using the TypoScript notation:

directories {
   # cat=basic/enable; type=string; label=Path to the temporary directory
   tmp =
   # cat=basic/enable; type=string; label=Path to the cache directory
   cache =

This will result in a multidimensional array:

$extensionConfiguration = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['your_extension_key']);


Notice the dot at the end of the directories key. This notation must be used for every grouping key and is a convention of the TypoScript parser.

The Extension Manager (EM)

Extensions are managed from the Extension Manager inside TYPO3 by "admin" users. The module is located at "Admin tools > Ext Manager" and offers a menu with options to see loaded extensions (those that are installed or activated), available extensions on the server and the possibility to import extensions from online resources, typically the TER (TYPO3 Extension Repository) located at typo3.org.

The Extension Manager (up to TYPO3 4.5)

Interface of the Extension Manager (up to TYPO3 4.5) showing the list of available extensions.

The Extension Manager (from TYPO3 4.5 to TYPO3 4.7)

Interface of the Extension Manager (from TYPO3 4.5 to TYPO3 4.7) showing all available extensions.

The interface is really easy to use. You just click the +/- icon to the left of an extension in order to install it and follow the instructions.

Creating a new extension

This chapter is not a tutorial about how to create an Extension. It only aims to be a list of steps to perform and key information to remember.

First you have to register an extension key. This is the unique identifier for your extension.

Kickstarting the extension

Although it is possible to write every single line of an extension from scratch, there is tool which makes it easier to start. It is called "Extension builder" (key: "extension_builder") and can be installed from TER.

The Extension Builder comes with its own BE module:

A view from the Extension Builder

The Domain Modeller screen of the Extension Builder. The comfort of building your model with drag and drop.

Note that this tool is not a complete editor. It helps you creating the scaffolding of your extension, generating the necessary files. It's then up to you to fill these with the relevant code.


The Extension Builder has some possibility to preserve code, but it should still be used with care.

After the extension is written to your computer's disk you will be able to install it locally and start using it.

Please refer to the Extension Builder's manual for more information.

Creating a new distribution

This chapter describes the main steps in creating a new distribution. It should not be considered as a full fledge tutorial.

Concept of distributions

Distributions are full TYPO3 CMS websites ready to be unpacked. They provide an easy quickstart for using TYPO3 CMS.

A distribution takes care of the following parts:

  • Deliver initial database data
  • Deliver fileadmin files
  • Deliver configuration for a package
  • Hook into the process after saving configuration to trigger actions dependent on configuration values
  • Deliver dependent extensions (e.g., customized versions or extensions not available through TER)
Kickstarting the distribution

A distribution is a special kind of extension. The first step is thus to create a new extension. Start by registering an extension key, which will be the unique identifier of your distribution.

Next create the Extension declaration file as usual, except for the "category" property which must be set to distribution.

Configuring the distribution display in the EM

You should provide two preview images for your distribution. Provide a small 220x150 pixels for the list in the extension manager as Resources/Public/Images/Distribution.png and a larger 300x400 pixels welcome image as Resources/Public/Images/DistributionWelcome.png. The welcome image is displayed in the distribution detail view inside the extension manager.

Fileadmin files

Create the following folder structure inside your extension:

  • Initialisation
  • Initialisation/Files

All the files inside that second folder will be copied to fileadmin/<extkey> during installation, where "extkey" is the extension key of your distribution.

Database data

The database data is delivered as TYPO3 CMS export data.t3d. Generate this file by exporting your whole installation from the tree root with the import/export module. Make sure to include all tables in the export.

The file has to be name data.t3d and must be located in the Initialisation folder.

Distribution configuration

A distribution is technically handled as an extension. Therefore your can make use of all configuration options as needed.

After saving the configuration, the signal afterExtensionConfigurationWrite is dispatched. You may use this to alter your website configuration (e.g. color scheme) on the fly.

Delivering custom dependencies

Normally extension dependencies are setup in the Extension declaration file.

However sometimes, extensions are not available in the TYPO3 Extension Repository (TER). Therefore, a distribution can act as its own extension repository. Add unpacked extensions to Initialisation/Extensions/ to provide dependencies. Your main extension has to be dependent on these extension as normal dependencies in ext_emconf.php.

Extensions delivered inside an extension have the highest priority when extensions need to be fetched.


This will not overwrite extensions already present in the system.

Test your distribution

To test your distribution, simply copy your extension to an empty TYPO3 CMS installation and try to install it from the Extension Manager.


It is not enough to clean all files and the page tree if you want to try again to install your distribution. Indeed, TYPO3 CMS remembers that it previously imported your distribution and will skip any known files. Make sure to clean the table "sys_registry" if you want to work around that.

More information

Some additional backgrounds can be retrieved from the blueprint for this feature.

Adding documentation

If you plan to upload your extension to the TYPO3 Extension Repository (TER), you should first consider adding a documentation to your extension. A documentation will help users and administrators to quickly install and configure your extension and give it more weight.

The documentation platform https://docs.typo3.org centralizes documentation for every project. It supports three different kind of documentation:

  1. (recommended) A Sphinx project, stored within EXT:extkey/Documentation/
  2. A simple README file stored as EXT:extkey/README.rst as seen on Github
  3. (legacy) An OpenOffice manual, stored as EXT:extkey/doc/manual.sxw
Sphinx project

Sphinx is the official format for official TYPO3 documentation. A Sphinx-based documentation is a set of plain text files making up the chapters or sections of the documentation. It uses a markup language called "reStructuredText" (reST).

Advantages of this new documentation format are numerous:

  • Output formats: Sphinx projects may be automatically rendered as HTML or TYPO3-branded PDF.
  • Cross-references: It is easy to cross-reference other chapters and sections of other manuals (either TYPO3 references or extension manuals).
  • Multilingual: Unlike OpenOffice, Sphinx projects may be easily localized and automatically presented in the most appropriate language to TYPO3 users.
  • Collaboration: As the documentation is plain text, it is easy to work as a team on the same manual or quickly review changes using any versioning system.

Although it is possible to write every single line of a Sphinx-based documentation from scratch, the TYPO3 community provides tools that help write and manage Sphinx projects:

  • The extension "Sphinx" (Sphinx Python Documentation Generator and Viewer) installs a local Sphinx environment to view, edit and compile documentation in the backend of your TYPO3 website. It can be installed from the TYPO3 Extension Repository (TER) like any other extension.
  • The Sphinx extension is able to convert existing OpenOffice manuals (manual.sxw) into Sphinx projects with just one click.
  • An example manual is available on the TYPO3 Documentation Github repository.
  • The Extension Builder provides a skeleton documentation based on the above-mentioned Git repository.
  • A good primer to get started using the reStructuredText markup.

A "README.rst" is a simple text file stored at the root of your extension directory and briefly describing the purpose of your extension. It is best suited when installing or using your extension is straightforward. The format of this file is reStructuredText, as for chapters of a Sphinx project.


In TYPO3 6.2, the system extension "documentation" is using such a simple manual.

OpenOffice manual

The OpenOffice SXW format used to be the official documentation format for TYPO3. Use of this format is highly discouraged since support on docs.typo3.org is about to be dropped. Please use the tools mentioned above to convert your existing SXW file into a Sphinx project instead.

Other resources

Beyond the general overview given in this chapter, other sections in this manual will be of particular interest to extension developers:

TYPO3 API overview

The source is the documentation! (General wisdom)

The TYPO3 APIs are first and foremost documented inside of the source scripts. It would be impossible to maintain documentation at more than one location given the fact that things change and sometimes fast. This chapter describes the most important elements of the API. Some other elements have their own chapter further on.

In general the TYPO3 Active Contributors Team and all contributors aim to produce well-documented source code. As such the best way to look up a particular class or method is to use the online API browser at http://api.typo3.org/.


Since version 6.0, TYPO3 CMS uses PHP namespaces for all classes in the Core.

The general structure of namespaces is the following:


For the Core, the vendor name is TYPO3\CMS and the package name corresponds to a system extension.

All classes must be located inside the Classes folder at the root of the (system) extension. The category name may contain several segments that correspond to the path inside the Classes folder.

Finally the class name is the same as the corresponding file name, without the .php extension.

"UpperCamelCase" is used for all segments.


See the chapter about 'ClassAliasMap.php' in the 6.2 documentation.. It may help you with migrating code from old to new conventions.

Core example

The good old t3lib_div class has been renamed to:


This means that the class is now found in the "core" system extension, in folder Classes/Utility, in a file named GeneralUtility.php.

Usage in extensions

Extension developers are free to use their own vendor name. Important: It may consist of one segment only. Vendor names must start with an uppercase character and are usually written in UpperCamelCase style. In order to avoid problems with different filesystems, only the characters a-z, A-Z, 0-9 and the dash sign "-" are allowed for package names – don't use special characters:

// good vendor name:

// wrong vendor name:


The vendor name TYPO3\CMS is reserved and may not be used by extensions!

The package name corresponds to the extension key. Underscores in the extension key are removed in the namespace and replaced by upper camel-case. So extension key:


would become:


in the namespace.

As mentioned above, all classes must be located in the Classes folder inside your extension. All sub-folders translate to a segment of the category name and the class name is the file name without the .php extension.

Looking at the "examples" extension, class:


corresponds to namespace:


Inside the class, the namespace is declared as:

namespace Documentation\Examples\Controller;

Namespaces in Extbase

When registering components in Extbase, the vendor name must be used on top of the extension key.

For a backend module:

    // ...

For a frontend module:

    // ...


  • Do not forget the dot after the vendor name.
  • Do not use dots inside the vendor name.

Namespaces for test classes

As for ordinary classes, namespaces for test classes start with a vendor name followed by the extension key.

All test classes reside in a Tests folder and thus the third segment of the namespace must be "Tests". Unit tests are located in a Unit folder which is the fourth segment of the namespace. Any further subfolders will be subsequent segments.

So a test class in EXT:foo_bar_baz/Tests/Unit/Bla/ will have as namespace \Vendor\FooBarBaz\Tests\Unit\Bla.

Creating instances

When creating instances using \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance() the leading backslash must be omitted and all other backslashes escaped, even when using single quotes. Thus the following code is correct:

$contentObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);

There is no need to use require() or include() statements. All classes that follow namespace conventions will automatically located and included by the autoloader.


For more information about PHP namespaces in general, you may want to refer to the PHP documentation and in particular the Namespaces FAQ.


The autoloader takes care of finding classes in TYPO3. It is closely related to \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance() which takes care of singleton and XCLASS handling.

As a developer you should always instantiate classes either through \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance() or with the Extbase ObjectManager, which internally uses makeInstance() again.


Since TYPO3 CMS 6.0 and the introduction of namespaces, developers are strongly encouraged to use the namespaces. When using namespaces it is not necessary to explicitely declare classes in an autoloader files. All namespaced classes are found and loaded automatically.

Please refer to the Namespaces chapter for more information.

Naming convention or autoloader file

In TYPO3 every class must reside in its own file, i.e. there should be only one class per PHP file. Extensions must not use require() or include() to load class files, but instead use the TYPO3 core API to automatically require a file upon request of the class.

A developer has two options to help the core find a specific class:

  • Use the class naming convention and file location.
  • Register a class name together with its location in an ext_autoload.php file.

If it's not possible to stick to the class naming and file location conventions - for whatever reasons - or if you don't want to use namespaces, you can add a file to your extension called ext_autoload.php in the base directory. Its task is to inform the autoloader about the location of each class file. The autoloader automatically searches for this file when a class is requested.

The ext_autoload.php file simply has to return a one-dimensional array with the class name as key and the file location as value. No other code is allowed in this file.

Examples for non-namespaced classes

The examples below are related to non-namespaced classes. When using namespaces, autoloading will happen without any extra effort on your part.

Extbase conventions

Consider the following:

  • Extension name: my_extension
  • Extension location: typo3conf/ext/my_extension
  • Class name: Tx_MyExtension_Utility_FooBar
  • Required file location: typo3conf/ext/my_extension/Classes/Utility/FooBar.php

which respects the following rules:

  • The class name must start with Tx_
  • In the extension name underscores are converted to upper camel case, hence MyExtension
  • Every underscore after the extension name in the class name is resolved to a uppercases folder name below the Classes directory, i.e. "_utility" becomes folder "Utility"
  • The last part of the class name resolves to the file name with suffix .php
No conventions

For a file which doesn't follow any particular conventions, an entry must be created in the extension's ext_autoload.php file.

Example taken from an oldish version of extension "news":

$extensionClassesPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('news') . 'Classes/';

$default = array(
   'tx_news_domain_model_dto_emconfiguration' => $extensionClassesPath . 'Domain/Model/Dto/EmConfiguration.php',
   'tx_news_hooks_suggestreceiver' => $extensionClassesPath . 'Hooks/SuggestReceiver.php',
   'tx_news_hooks_suggestreceivercall' => $extensionClassesPath . 'Hooks/SuggestReceiverCall.php',
   'tx_news_utility_compatibility' => $extensionClassesPath . 'Utility/Compatibility.php',
   'tx_news_utility_importjob' => $extensionClassesPath . 'Utility/ImportJob.php',
   'tx_news_utility_emconfiguration' => $extensionClassesPath . 'Utility/EmConfiguration.php',
   'tx_news_service_cacheservice' => $extensionClassesPath . 'Service/CacheService.php',
return $default;


The class names used as keys in the array must be in lower case, until TYPO3 4.7. This limitation was removed in TYPO3 6.0.


TYPO3 CMS has a clean bootstrapping process driven mostly by class \TYPO3\CMS\Core\Core\Bootstrap. This class contains a host of methods each responsible for a little step along the initialization of a full TYPO3 process, be it the backend or other contexts.

Some contexts add their own bootstrap class (like the command line, which additionally requires \TYPO3\CMS\Core\Core\CliBootstrap.


The frontend's bootstrapping process is not yet fully encapsulated in a bootstrap class.


This boostrapping API is internal and may change any time in the near future even in minor updates. It is thus discouraged to use it in third party code. Choose this solution only if other extensbility features such as Hooks, Signals or XCLASS are not enough to reach your goals.

One can see the bootstrapping process in action in file typo3/init.php:

define('TYPO3_MODE', 'BE');

require 'sysext/core/Classes/Core/Bootstrap.php';


Note that most methods of the Bootstrap class must be called in a precise order. It is perfectly possible to define one's own bootstrapping process, but care should be taken about the call order.

Also note that all bootstrapping methods return the instance of the Bootstrap class itself, allowing calls to be chained.

Application Context

Each request, no matter if it runs from the command line or through HTTP, runs in a specific application context. TYPO3 CMS provides exactly three built-in contexts:

  • Production (default) - should be used for a live site
  • Development - used for development
  • Testing - is only used internally when executing TYPO3 core tests. It must not be used otherwise.

The context TYPO3 runs in is specified through the environment variable TYPO3_CONTEXT. It can be set on the command line:

# run the TYPO3 CMS CLI commands in development context
TYPO3_CONTEXT=Development ./typo3/cli_dispatch.phpsh

or be part of the web server configuration:

# In your Apache configuration, you usually use:
SetEnv TYPO3_CONTEXT Development

# Set context with mod_rewrite
# Rules to set ApplicationContext based on hostname
RewriteCond %{HTTP_HOST} ^dev\.example\.com$
RewriteRule .? - [E=TYPO3_CONTEXT:Development]

RewriteCond %{HTTP_HOST} ^staging\.example\.com$
RewriteRule .? - [E=TYPO3_CONTEXT:Production/Staging]

RewriteCond %{HTTP_HOST} ^www\.example\.com$
RewriteRule .? - [E=TYPO3_CONTEXT:Production]
# In your Nginx configuration, you can pass the context as a fastcgi parameter
location ~ \.php$ {
   include         fastcgi_params;
   fastcgi_index   index.php;
   fastcgi_param   TYPO3_CONTEXT  Development/Dev;
   fastcgi_param   SCRIPT_FILENAME  $document_root$fastcgi_script_name;
Custom Contexts

In certain situations, more specific contexts are desirable:

  • a staging system may run in a Production context, but requires a different set of credentials than the production server.
  • developers working on a project may need different application specific settings but prefer to maintain all configuration files in a common Git repository.

By defining custom contexts which inherit from one of the three base contexts, more specific configuration sets can be realized.

While it is not possible to add new "top-level" contexts at the same level like Production and Testing, you can create arbitrary sub-contexts, just by specifying them like <MainContext>/<SubContext>.

For a staging environment a custom context Production/Staging may provide the necessary settings while the Production/Live context is used on the live instance.


This even works recursively, so if you have a multiple-server staging setup, you could use the context Production/Staging/Server1 and Production/Staging/Server2 if both staging servers needed different configuration.


Testing Is reserved for internal use when executing TYPO3 core functional and unit tests It must not be used otherwise. Instead sub-contexts must be used: Production/Testing or Development/Testing

Usage Example

The current Application Context is set very early in the bootstrap process can be accessed through public API for example in the AdditionalConfiguration.php file to automatically set different configuration for different contexts.

In file typo3conf/AdditionalConfiguration.php:

switch (\TYPO3\CMS\Core\Utility\GeneralUtility::getApplicationContext()) {
   case 'Development':
      $GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = 1;
      $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
   case 'Production/Staging':
      $GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = 0;
      $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '192.168.1.*';
      $GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] = 0;
      $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '';

Main classes and methods

There are a number of core classes in TYPO3 which contain general functionality and are available most of or even all the time. These classes are either static or exist as singletons stored in global variables.

This table lists some of the most important classes to know about in TYPO3:

Class name Description Usage

Character Set handling API

Contains native PHP code to handle character set conversion based on charset tables from Unicode.org. You should use this class whenever you need to handle character set conversion or to perform multibyte-safe string operations (like getting the length, cropping, etc.).

In the backend, available as $GLOBALS['LANG']->csConvObj

In the frontend, available as $GLOBALS['TSFE']->csConvObj


General Purpose Functions

A collection of multi-purpose PHP functions. Some are TYPO3 specific but not all.

There are more specific utility classes in EXT:core/Classes/Utility.

Static class, call methods using \TYPO3\CMS\Core\Utility\GeneralUtility::

Backend Specific Functions

Contains functions specific to the TYPO3 backend. You will typically need these when programming backend modules or other backend functionality.

This class is NOT available in the frontend!

Static class, call methods using \TYPO3\CMS\Backend\Utility\BackendUtility::

Extension API functions

Functions for extensions to interface with the core system. Many of these functions are used in ext\_localconf.php and ext\_tables.php files of extensions. They let extensions register their features with the system.

Static class, call methods using \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::

Icons / Part of skinning API

Contains a few functions for getting the right icon for a database table record or the skinned version of any other icon.

This class is NOT available in the frontend!

Static class, call methods using \TYPO3\CMS\Backend\Utility\IconUtility::

Backend Template Class

Contains functions for producing the layout of backend modules, setting up HTML headers, wrapping JavaScript sections correctly for XHTML, etc.

Available as $GLOBALS['TBE_TEMPLATE'], $GLOBALS['SOBE'] or $this->doc (inside of BE modules)

These classes are always included and available in the TYPO3 backend and frontend (except \TYPO3\CMS\Backend\Utility\BackendUtility and \TYPO3\CMS\Backend\Utility\IconUtility).

The next sections highlight a selection of methods from these classes. They were chosen for their general importance with regards to the whole of TYPO3. You should at least acquaint yourself with all these high- priority functions, in order to write code the TYPO3 way. These lists also include some other methods selected for their usefulness.

High priority functions

The functions listed in this table are of high priority. Generally they provide APIs to functionality which TYPO3 should always handle in the same way. This will help you to code TYPO3 applications with less bugs and greater compatibility with various system conditions it will run under.

Remember, this list only serves to point out important functions! The real documentation is found in the source scripts (and the online API). The comments given are only a supplement to that.

Function Comments




Getting values from GET or POST vars

APIs for getting values in GET or POST variables with slashes stripped regardless of PHP environment. Always use these functions instead of direct access to $_GET or $_POST.

\TYPO3\CMS\Core\Utility\GeneralUtility::_GP($varname) will give you the value of either the GET or POST variable with priority to POST if present. This is useful if you don't know whether a parameter is passed as GET or POST. Many scripts will use this function to read variables during initialization:

    // Setting GPvars:
$this->file = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('file');
$this->size = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('size');

\TYPO3\CMS\Core\Utility\GeneralUtility::_GET() will give you GET vars. For security reasons you should use this if you know your parameters are passed as GET variables. This example gives you the whole $_GET array:

$params = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET();

\TYPO3\CMS\Core\Utility\GeneralUtility::_POST() will give you POST variables. Works like \TYPO3\CMS\Core\Utility\GeneralUtility::_GET(). For security reasons you should use this if you know your parameters are passed as POST variables.

This example gives you the content of the POST variable TSFE_ADMIN_PANEL, for instance it could come from a form field like <input name="TSFE_ADMIN_PANEL[command]" ..../>

$input = \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('TSFE_ADMIN_PANEL');

Creating objects

Factory API for creating an instance of an object given a class name. This function makes sure the "XCLASS extension" principle can be used on (almost) any class in TYPO3. You must use this method when creating objects in TYPO3.


   // Making an instance of class "\\TYPO3\\CMS\\Core\\TypoScript\\Parser\\TypoScriptParser":
$parseObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\TypoScript\\Parser\\TypoScriptParser');

   // Make an object with arguments passed to the constructor:
$message = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
   'My message text',
   'Message Header',

Environment-safe server and environment variables.

API function for delivery of system and environment variables on any web-server brand and server OS. Always use this API instead of $_ENV/$_SERVER or getenv() if possible.


if (\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test)...
if (\TYPO3\CMS\Core\Utility\GeneralUtility::cmpIP(\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REMOTE_ADDR'), $pcs[1]))...
$prefix = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
$redirectTo = \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SITE_URL').$redirectTo;
if (!\TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('TYPO3_SSL')) ...






Evaluate files and directories for security reasons

When you allow references to files to be input from users there is always the risk that they try to cheat the system to include something else than intended. These functions makes it easy for you to evaluate filenames for validity before reading, writing or including them.

Here the functions are described in order of importance:

\TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName() - Returns the absolute filename of a relative reference, resolves the "EXT:" prefix (way of referring to files inside extensions) and checks that the file is inside the PATH_site of the TYPO3 installation and implies a check with \TYPO3\CMS\Core\Utility\GeneralUtility::validPathStr(). Returns false if checks failed. Does not check if the file exists.

   // Getting absolute path of a temporary file
$cacheFile = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('typo3temp/tempfile.tmp');
// Include file if it exists:
$file = \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName($fileRef);
if (@is_file($file)) {

\TYPO3\CMS\Core\Utility\GeneralUtility::validPathStr() - Checks for malicious file paths. Returns true if no '//', '..' or '\' is in the $theFile. This should make sure that the path is not pointing 'backwards' and further doesn't contain double/back slashes.

// If the path is true and validates as a valid path string
if ($path && \TYPO3\CMS\Core\Utility\GeneralUtility::validPathStr($path)) {
   // ...

\TYPO3\CMS\Core\Utility\GeneralUtility::isAbsPath() - Checks if the input path is absolute or relative (detecting either '/' or 'x:/' as first part of string) and returns true if so.

   // Returns relative filename for icon:
if (\TYPO3\CMS\Core\Utility\GeneralUtility::isAbsPath($Ifilename)) {
   $Ifilename = '../' . substr($Ifilename, strlen(PATH_site));

\TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedAbsPath() - Returns true if the path is absolute, without backpath '..' and within the PATH_site OR within the lockRootPath. Contrary to \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName() this function can also validate files in filemounts outside the web-root of the installation, but this is rarely used!

if (@file_exists($path) && \TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedAbsPath($path)) {
    $fI = pathinfo($path);

\TYPO3\CMS\Core\Utility\GeneralUtility::fixWindowsFilePath() - Fixes a path for Windows- backslashes and reduces double-slashes to single slashes


Creates directory

One would think that creating directories is one thing you can do directly with PHP. Well, it turns out to be quite error-prone if it should be compatible with Windows servers and safe-mode at the same time. So TYPO3 offers a substitution function you should always use.


$root.=$dirParts . '/';
if (!is_dir($extDirPath . $root))    {
    \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($extDirPath . $root);
    if (!@is_dir($extDirPath . $root))    {
        return 'Error: The directory "' .
                $extDirPath . $root .
                '" could not be created...';




Functions for handling uploads and temporary files

You need to use these functions for managing uploaded files you want to access as well as creating temporary files within the same session. These functions are safe_mode and open_basedir compatible which is the main point of you using them!

\TYPO3\CMS\Core\Utility\GeneralUtility::upload_to_tempfile() - Will move an uploaded file (normally in "/tmp/xxxxx") to a temporary filename in PATH\_site . 'typo3temp/'. Remember to use \TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile() afterwards - otherwise temp-files will build up! They are not automatically deleted in PATH\_site . 'typo3temp/'!

\TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile() - Deletes (unlink) a temporary filename in PATH\_site . 'typo3temp/' given as input. The function will check that the file exists, is in PATH_site . 'typo3temp/' and does not contain back-spaces ("../") so it should be pretty safe. Use this after upload_to_tempfile() or tempnam() from this class!

This example shows how to handle an uploaded file you just want to read and then delete again:

    // Read uploaded file:
$uploadedTempFile = \TYPO3\CMS\Core\Utility\GeneralUtility::upload_to_tempfile(
$fileContent = \TYPO3\CMS\Core\Utility\GeneralUtility::getUrl($uploadedTempFile);

\TYPO3\CMS\Core\Utility\GeneralUtility::tempnam() - Create temporary filename (creates file with unique file name). This function should be used for getting temporary filenames - will make your applications safe for "open_basedir = on". Remember to delete the temporary files after use! This is done by \TYPO3\CMS\Core\Utility\GeneralUtility::unlink_tempfile().

In the following example it is shown how two temporary filenames are created for being processed with an external program (diff) after which they are deleted again:

    // Create file 1 and write string
$file1 = \TYPO3\CMS\Core\Utility\GeneralUtility::tempnam('diff1_');
\TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($file1, $str1);
    // Create file 2 and write string
$file2 = \TYPO3\CMS\Core\Utility\GeneralUtility::tempnam('diff2_');
\TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($file2, $str2);
    // Perform diff.
$cmd = $GLOBALS['TYPO3_CONF_VARS']['BE']['diff_path'].
           ' '.$this->diffOptions . ' ' . $file1 . ' ' . $file2;
exec($cmd, $res);

Truncating a string for visual display, observing the character set (backend only)

This function allows you to truncate a string from e.g. "Hello World" to "Hello Wo..." so for example very long titles of records etc. will not break the visual appearance of your backend modules.

Since text strings cannot be cropped at any byte if the character set is utf-8 or another multibyte charset this function will process the string assuming the character set to be the one used in the backend.

It is recommended to use $BE_USER->uc['titleLen'] for the length parameter.

  // Limits Record title to 30 chars
\TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs($thisRecTitle, 30);
  // Limits string to title-length configured for backend user:
$title = \TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs(

Preparing a string for output between <textarea> tags.

Use this function to prepare content for <textarea> tags. Then you will avoid extra / stripped whitespace when the form is submitted multiple times.

    // Create item:
$item = '
    <textarea>' .
    \TYPO3\CMS\Core\Utility\GeneralUtility::formatForTextarea($value) .

Preparing a URL for a HTTP location-header

Use this to prepare redirection URLs for location-headers. It will convert the URL to be absolute. This is also useful in other cases where an absolute URL must be used, for example when passing a callback URL to some third-party software. Redirection example:

header('Location: ' . \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl($this->retUrl));
Function Comments

Get SQL WHERE-clause filtering "deleted" records

Tables from $TCA might be configured to set an integer flag when deleting a record instead of actually removing it from the database. Records with the deleted-flag set should never be selected in TYPO3 unless you have a specific reason to do so. To make sure you never make that mistake always call this function which will pass you a SQL WHERE-clause like " AND deleted=0" if the table given as argument has been configured with a deleted- field.


In the frontend this is built into the enableFields() method.


$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
                'pid, uid, title, TSconfig, is_siteroot, storage_pid',
                'uid = ' . intval($uid) . ' ' .
                    \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause('pages') . ' ' .



Create "Function menu" in backend modules

Creates a selector box menu or checkbox with states automatically saved in the backend user session. Such a function menu could look like this:

The function menu from the Web > Info module

The function menu from the Web > Info module

The selector box is made by this function call. It sets the ID variable (zero if not available), the GET var name, "SET[mode]", the current value from MOD_SETTINGS and finally the array of menu options, MOD_MENU['mode']:


Prior to making the menu it is required that the MOD_MENU array is set up with an array of options. This could look like this (getting some labels from the "locallang" system). In addition the incoming "SET" GET-variable must be registered in the session which is also done in this listing:

$this->MOD_MENU = array(
    'mode' => array(
        0 => $LANG->getLL('user_overview'),
        'perms' => $LANG->getLL('permissions')
    // Clean up settings:
$this->MOD_SETTINGS = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData(

You can have checkboxes as well:

The function menu from the Web > Info module

The function menu from the Web > Info module

Then the function call looks like this. Notice the fourth argument is gone because a checkbox does not have any information about options like a selector box would have.


For checkboxes you must set the key in the MOD_MENU array as well. Otherwise the values are not registered in the user session:

'own_member_only' => '',

Create onclick-JavaScript code that links to edit form for a record

Use this function to create a link to the "alt_doc.php" core script which can generate editing forms for any $TCA configured record. The actual editing command is passed to "alt_doc.php" through the GET parameter "&edit".

For detailed examples, see Links to edit records.


$params = '&edit[pages][' . $row['uid'] . ']=edit';
$link = '<a href="#" onclick="' .
            htmlspecialchars(\TYPO3\CMS\Backend\Utility\BackendUtility::editOnClick($params, '', -1)).

Create onclick-JavaScript code that opens a page in the frontend

It will detect the correct domain name if needed and provide the link with the right back path. Also it will re-use any window already open.

    // "View page" link is added:
$link = '<a href="#" onclick="' .
        )) . '">View page</a>';

Create icon or short description for Context Sensitive Help (CSH)

You are encouraged to integrate Content Sensitive Help in your backend modules and for your database tables. This will help users to use TYPO3 and your TYPO3 applications more easily. The help appears as icons. Hovering over these reveals the (short) help text.

The CSH displayed in a help bubble


  // Setting "table name" to module name with prefix
$tableIdentifier = '_MOD_' . $this->MCONF['name'];

  // Creating CSH icon and short description (for item "property"):
$HTMLcode .= \TYPO3\CMS\Backend\Utility\BackendUtility::wrapInHelp($tableIdentifier, 'property');
Function Comments

Returns true if an extension is loaded (installed)

Use if you just need to check if an extension is loaded in a TYPO3 installation.


   // If the extension "sys_note" is loaded, then...
if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('sys_note'))    ...

   // Check if the "indexed_search" extension is loaded.
   // If not, an exception will be thrown!
try {
   \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('indexed_search', TRUE);
catch (BadFunctionCallException $e) {
   // ...

      // Assign value "popup" if extension "tsconfig_help" is loaded
$type = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('tsconfig_help') ? 'popup' : '';




Get file path to an extension directory

If you need to get the absolute or relative filepaths to an extension you should use these functions. Extension can be located in three different positions in the filesystem whether they are local, global or system extensions. These functions will always give you the right path.


   // Include a PHP file from the extension "extrep_wizard".
   // \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath() returns the absolute path to the
   // extension directory.
    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('extrep_wizard') .
   // Get relative path (relative to PATH_typo3) to an icon (backend)
$icon = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('tt_rating') . 'rating.gif';
   // Get relative path (relative to PATH_site) to an icon (frontend)
return '<img src="'.
    \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::siteRelPath('indexed_search') . 'pi/res/locked.gif'
    ... />';
Function Comments




Getting correct icons

Always use these functions if you need to get some arbitrary icon (getSpriteIcon()), the correct icon for a record (getSpriteIconForRecord()) or for a file (getSpriteIconForFile()). For records, there needs to be a proper definition in the $TCA.

More information about skinning is found in the TYPO3 CMS Skinning Reference.

   // Getting default icon for the "tt_content" table
$icon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord(

   // Getting an icon where record content may define the look
$icon = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord(

   // Getting a given icon, for example the "new document" action

This class is always accessed via its global instance $GLOBALS['BE_USER'].

Function Comments

Returns true if current backend user is "admin"

Use this if you need to restrict a user from doing something unless he is "admin":

if ($GLOBALS['BE_USER']->isAdmin()) {
   // ...

Return WHERE clause for filtering pages for which the current user has the requested permission

The most typical usage of this is to call the function with the value "1" (= "show"). Then the WHERE clause returned will filter away all pages to which the user has no read-access.

TYPO3 Coding Guidelines

You should also refer to the TYPO3 Core Coding Guidelines (CGL) document which is the authoritative source to know about which coding practices are required for TYPO3 core and extension programming.

Functions typically used and nice to know

These functions are generally just nice to know. They provide functionality which you will often need in TYPO3 applications and therefore they will save you time and make your applications easier for others to understand as well since you use commonly known functions.

Please take time to learn these functions!

Function Comments

Check if an item exists in a comma-separated list of items.

if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList('gif,jpg,png', $ext)) {//...}

Returns true if the first part of input string matches the second argument.

\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($path, PATH_site);

Formats a number of bytes as Kb/Mb/Gb for visual output.

Size formatting supports two keywords additionally to the list of labels:

  • iec: uses the Ki, Mi, etc prefixes and binary base (power of two, 1024)
  • si: uses the k, M, etc prefixes and decimal base (power of ten, 1000)

The default formatting is set to "iec" base size calculations on the same base as before. The fractional part, when present, will be two numbers.

The list of labels is still supported and defaults to using binary base. It is also possible to explicitly choose between binary or decimal base when it is used.

$size = \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize(85123) . 'B';
echo $size; // output:  83.13 KiB

$size = \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize(85123, 'si') . 'B';
echo $size; // output:  85.12 kB

$size = \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize(85123, '| kB| MB| GB| TB| PB| EB| ZB| YB');
echo $size; // output:  83.13 kB

$size = \TYPO3\CMS\Core\Utility\GeneralUtility::formatSize(85123, '| kB| MB| GB| TB| PB| EB| ZB| YB', 1000);
echo $size; // output:  85.12 kB

Evaluates a string as an email address.

if ($email && \TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($email)) {




Various flavors of exploding a string by a token.

\TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode() - Explodes a string by a token and trims the whitespace away around each item. Optionally any zero-length elements are removed. Very often used to explode strings from configuration, user input etc. where whitespace can be expected between values but is insignificant.

array_unique(\TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $rawExtList, 1));
\TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(chr(10), $content);

\TYPO3\CMS\Core\Utility\GeneralUtility::intExplode() - Explodes a by a token and converts each item to an integer value. Very useful to force integer values out of a value list, for instance for an SQL query.

// Make integer list
implode(\TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $row['subgroup']), ',');

\TYPO3\CMS\Core\Utility\GeneralUtility::revExplode() - Reverse explode() which allows you to explode a string into X parts but from the back of the string instead.

$p = \TYPO3\CMS\Core\Utility\GeneralUtility::revExplode('/', $path, 2);



Merging arrays with fixes for "PHP-bugs"

\TYPO3\CMS\Core\Utility\GeneralUtility::array_merge_recursive_overrule() - Merges two arrays recursively and "binary safe" (integer keys are overridden as well), overruling similar the values in the first array ($arr0) with the values of the second array ($arr1). In case of identical keys, i.e. keeping the values of the second.

\TYPO3\CMS\Core\Utility\GeneralUtility::array_merge() - An array_merge function where the keys are NOT renumbered as they happen to be with the real php- array_merge function. It is "binary safe" in the sense that integer keys are overridden as well.



Serialization of PHP variables into XML.

These functions are made to serialize and unserialize PHParrays to XML files. They are used for the FlexForms content in TYPO3, Data Structure definitions etc. The XML output is optimized for readability since associative keys are used as tagnames. This also means that only alphanumeric characters are allowed in the tag names andonly keys not starting with numbers (so watch your usage of keys!). However there are options you can set to avoid this problem. Numeric keys are stored with the default tagname "numIndex" but can be overridden to other formats). The function handles input values from the PHP array in a binary-safe way; All characters below 32 (except 9,10,13) will trigger the content to be converted to a base64-string. The PHP variable type of the data is preserved as long as the types are strings, arrays, integers and booleans. Strings are the default type unless the "type" attribute is set.

\TYPO3\CMS\Core\Utility\GeneralUtility::array2xml_cs() - Converts a PHP array into an XML string:


\TYPO3\CMS\Core\Utility\GeneralUtility::xml2array() - Converts an XML string to a PHP array. This is the reverse function of array2xml_cs():

if ($this->xmlStorage)    {
    $cfgArr = \TYPO3\CMS\Core\Utility\GeneralUtility::xml2array($row[$this->P['field']]);



Reading / Writing files

\TYPO3\CMS\Core\Utility\GeneralUtility::getURL() - Reads the full content of a file or URL. Used throughout the TYPO3 sources. Transparently takes care of Curl configuration, proxy setup, etc.

$templateCode = \TYPO3\CMS\Core\Utility\GeneralUtility::getURL($templateFile);

\TYPO3\CMS\Core\Utility\GeneralUtility::writeFile() - Writes a string into an absolute filename:

\TYPO3\CMS\Core\Utility\GeneralUtility::writeFile($extDirPath . $theFile, $fileData['content']);
split_fileref Splits a reference to a file in 5 parts. Alternative to "path_info" and fixes some "PHP-bugs" which makes page_info() unattractive at times.





Read content of file system directories.

\TYPO3\CMS\Core\Utility\GeneralUtility::get_dirs() - Returns an array with the names of folders in a specific path

if (@is_dir($path))    {
    $directories = \TYPO3\CMS\Core\Utility\GeneralUtility::get_dirs($path);
    if (is_array($directories))    {
        foreach($directories as $dirName)    {
            // ...

\TYPO3\CMS\Core\Utility\GeneralUtility::getFilesInDir() - Returns an array with the names of files in a specific path

$sFiles = \TYPO3\CMS\Core\Utility\GeneralUtility::getFilesInDir(PATH_typo3conf ,'', 1, 1);
$files = \TYPO3\CMS\Core\Utility\GeneralUtility::getFilesInDir($dir, 'png,jpg,gif');

\TYPO3\CMS\Core\Utility\GeneralUtility::getAllFilesAndFoldersInPath() - Recursively gather all files and folders of a path.

\TYPO3\CMS\Core\Utility\GeneralUtility::removePrefixPathFromList() - Removes the absolute part of all files/folders in fileArr (useful for post processing of content from \TYPO3\CMS\Core\Utility\GeneralUtility::getAllFilesAndFoldersInPath())

    // Get all files with absolute paths prefixed:
$fileList_abs =
    \TYPO3\CMS\Core\Utility\GeneralUtility::getAllFilesAndFoldersInPath(array(), $absPath, 'php,inc');

    // Traverse files and remove abs path from each (becomes relative)
$fileList_rel =
    \TYPO3\CMS\Core\Utility\GeneralUtility::removePrefixPathFromList($fileList_abs, $absPath);

Implodes a multidimensional array into GET-parameters (e.g. &param[key][key2]=value2&param[key][key3]=value3)

$pString = \TYPO3\CMS\Core\Utility\GeneralUtility::implodeArrayForUrl('', $params);



Works on HTML tag attributes

\TYPO3\CMS\Core\Utility\GeneralUtility::get_tag_attributes() - Returns an array with all attributes of the input HTML tag as key/value pairs. Attributes are only lowercase a-z

$attribs = \TYPO3\CMS\Core\Utility\GeneralUtility::get_tag_attributes('<' . $subparts[0] . '>');

\TYPO3\CMS\Core\Utility\GeneralUtility::implodeAttributes() - Implodes attributes in the array $arr for an attribute list in e.g. and HTML tag (with quotes)

$tag = '<img ' . \TYPO3\CMS\Core\Utility\GeneralUtility::implodeAttributes($attribs, 1) . ' />';
resolveBackPath Resolves ../ sections in the input path string. For example fileadmin/directory/../other_directory/ will be resolved to fileadmin/other_directory/



General purpose functions for calling user functions (creating hooks).

See the chapter about Creating hooks in this document for detailed description of these functions.

\TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction() - Calls a user-defined function/method in class. Such a function/method should look like this: function proc(&$params, &$ref) {...}

function procItems($items,$iArray,$config,$table,$row,$field) {
    global $TCA;
    $params['items'] = &$items;
    $params['config'] = $config;
    $params['TSconfig'] = $iArray;
    $params['table'] = $table;
    $params['row'] = $row;
    $params['field'] = $field;

    return $items;

\TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj() - Creates and returns reference to a user defined object:

$_procObj = &\TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($_classRef);
$_procObj->pObj = &$this;
$value = $_procObj->transform_rte($value,$this);
linkThisScript Returns the URL to the current script. You can pass an array with associative keys corresponding to the GET-vars you wish to add to the URL. If you set them empty, they will remove existing GET-vars from the current URL.
Function Comments

Forces the input variable (integer) into the boundaries of $min and $max:

\TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($row['priority'], 1, 5);
canBeInterpretedAsInteger Tests if the input is an integer.
Function Comments



Functions for selecting records by uid or field value.

\TYPO3\CMS\Backend\Utility\BackendUtility::getRecord() - Gets record with uid=$uid from $table

     // Getting array with title field from a page:
\TYPO3\CMS\Backend\Utility\BackendUtility::getRecord('pages', intval($row['shortcut']), 'title');

     // Getting a full record with permission WHERE clause
   $pageinfo = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord(
           ($perms_clause ? ' AND ' . $perms_clause : '')

\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField() - Returns records from table, $theTable, where a field ($theField) equals the value, $theValue

    // Checking if the id-parameter is an alias.
if (!\TYPO3\CMS\Core\Utility\GeneralUtility::testInt($id))    {
    list($idPartR) =
        \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField('pages', 'alias', $id);
    $id = intval($idPartR['uid']);

Returns the path (visually) of a page $uid, fx. "/First page/Second page/Another subpage"

$label = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordPath(

Returns a page record (of page with $id) with an extra field _thePath set to the record path if the WHERE clause, $perms_clause, selects the record. Thus is works as an access check that returns a page record if access was granted, otherwise not.

$perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
$pageinfo = \TYPO3\CMS\Backend\Utility\BackendUtility::readPageAccess($id, $perms_clause);




Date/Time formatting functions using date/time format from $TYPO3_CONF_VARS.

\TYPO3\CMS\Backend\Utility\BackendUtility::date() - Returns $tstamp formatted as "ddmmyy" (According to $TYPO3_CONF_VARS['SYS']['ddmmyy'])


\TYPO3\CMS\Backend\Utility\BackendUtility::datetime() - Returns $tstamp formatted as "ddmmyy hhmm" (According to $TYPO3_CONF_VARS['SYS']['ddmmyy'] and $TYPO3_CONF_VARS['SYS']['hhmm'])


\TYPO3\CMS\Backend\Utility\BackendUtility::calcAge() - Returns the "age" in minutes / hours / days / years of the number of $seconds given as input.

$agePrefixes = ' min| hrs| days| yrs';
\TYPO3\CMS\Backend\Utility\BackendUtility::calcAge(time()-$row['crdate'], $agePrefixes);

Returns title attribute information for a page-record informing about id, alias, doktype, hidden, starttime, endtime, fe_group etc.

$out = \TYPO3\CMS\Backend\Utility\BackendUtility::titleAttribForPages($row, '', 0);
$out = \TYPO3\CMS\Backend\Utility\BackendUtility::titleAttribForPages($row, '1=1 ' . $this->clause, 0);



Returns image tags for thumbnails

\TYPO3\CMS\Backend\Utility\BackendUtility::thumbCode() - Returns a linked image-tag for thumbnail(s)/fileicons/truetype-font-previews from a database row with a list of image files in a field. Slightly advanced. It's more likely you will need \TYPO3\CMS\Backend\Utility\BackendUtility::getThumbNail() to do the job.

\TYPO3\CMS\Backend\Utility\BackendUtility::getThumbNail() - Returns single image tag to thumbnail using a thumbnail script (like thumbs.php)

    $this->doc->backPath . 'thumbs.php',
    'hspace="5" vspace="5" border="1"'



Get/Set cache values.

\TYPO3\CMS\Backend\Utility\BackendUtility::storeHash() - Stores the string value $data in the "cache hash" table with the hash key, $hash, and visual/symbolic identification, $ident.

\TYPO3\CMS\Backend\Utility\BackendUtility::getHash() - Retrieves the string content stored with hash key, $hash, in "cache hash".

Example of how both functions are used together; first getHash() to fetch any possible content and if nothing was found how the content is generated and stored in the cache:

    // Parsing the user TS (or getting from cache)
$userTS = implode($TSdataArray,chr(10) . '[GLOBAL]' . chr(10));
$hash = md5('pageTS:' . $userTS);
$cachedContent = \TYPO3\CMS\Backend\Utility\BackendUtility::getHash($hash, 0);
$TSconfig = array();
if (isset($cachedContent))    {
    $TSconfig = unserialize($cachedContent);
} else {
    $parseObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\TypoScript\\Parser\\TypoScriptParser');
    $TSconfig = $parseObj->setup;
    \TYPO3\CMS\Backend\Utility\BackendUtility::storeHash($hash,serialize($TSconfig), 'IDENT');



\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle() - Returns the "title" value from the input records field content.

$line.= \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle('tt_content', $row, 1);

\TYPO3\CMS\Backend\Utility\BackendUtility::getProcessedValue() - Returns a human readable output of a value from a record. For instance a database record relation would be looked up to display the title-value of that record. A checkbox with a "1" value would be "Yes", etc.

$outputValue = nl2br(

Returns the Page TSconfig for page with id, $id.

This example shows how an object path, mod.web_list is extracted from the Page TSconfig for page $id:

$modTSconfig = $GLOBALS['BE_USER']->getTSConfig(
Function Comments

Adding fields to an existing table definition in $TCA

For usage in ext_tables.php or Configuration/TCA/Overrides files.

// tt_address modified
      'module_sys_dmail_category' => array('config' => array('type' => 'passthrough')),
      'module_sys_dmail_html' => array('config' => array('type' => 'passthrough'))

Makes fields visible in the TCEforms by adding them to all or selected "types"-configurations

For usage in ext_tables.php or Configuration/TCA/Overrides files.

   'tx_myext_newfield;;;;1-1-1, tx_myext_another_field'

Add table name to default list of allowed tables on pages (in $PAGES_TYPES)

For usage in ext_tables.php files.



Adds a module (main or sub) to the backend interface.


Extbase-based modules use a different registration API.

For usage in ext_tables.php files.

   \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'mod/'

   \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'mod1/'

Adds a "Function menu module" ("third level module") to an existing function menu for some other backend module

For usage in ext_tables.php files.

   \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) .

Adds an entry to the list of plugins in content elements of type "Insert plugin"


Extbase-based plug-ins use a different registration API.

For usage in ext_tables.php files.

      $_EXTKEY . '_pi1'

Add PlugIn to Static Template #43

When adding a frontend plugin you will have to add both an entry to the TCA definition of tt_content table AND to the TypoScript template which must initiate the rendering. Since the static template with uid 43 is the "content.default" and practically always used for rendering the content elements it's very useful to have this function automatically adding the necessary TypoScript for calling your plugin. It will also work for the extension "css_styled_content"

For usage in ext_localconf.php files.



Adds an option in the page properties to include a page TSconfig file (the same way as TypoScript static templates are included).

Register PageTS config files in Configuration/TCA/Overrides/pages.php of any extension, which will be shown afterwards at the newly introduced field.


The included files from the pages in the rootline are included after the default page TSconfig and before the normal TSconfig from the pages in the rootline.

TYPO3CMSCoreUtilityExtensionManagementUtility::registerPageTSConfigFile('extension_name', 'Configuration/PageTS/myPageTSconfigFile.txt', 'My special config');

Variables and Constants

After TYPO3's bootstrap sequence has completed, a number of global variables, constants and classes available to any script.

The column "Avail. in FE" is an indicator that tells you if the constant, variable or class mentioned is also available to scripts running under the frontend of the "cms" extension.


Constants normally define paths and database information. These values are global and cannot be changed when they are first defined. This is why constants are used for such vital information.

These constants are defined at various points during the bootstrap sequence.


To make the table below a bit more compact, namespaces were left out. Here are the fully qualified class names referred to below:

Table 1: Traditional List
Constant Defined in Description Avail. in FE
TYPO3_MODE init.php Mode of TYPO3: Set to either "FE" or "BE" depending on frontend or backend execution. So in init.php and thumbs.php this value is "BE".


value = "FE"

TYPO3_OS SystemEnvironmentBuilder::getTypo3Os() Operating systen; Windows = "WIN", other = "" (presumed to be some sort of Unix) Yes
PATH_thisScript SystemEnvironmentBuilder::definePaths() Abs. path to current script. Yes
TYPO3_mainDir SystemEnvironmentBuilder::definePaths() This is the directory of the backend administration for the sites of this TYPO3 installation. Hardcoded to typo3/. Must be a subdirectory to the website. See elsewhere for descriptions on how to change the default admin directory, typo3/, to something else. Yes
PATH_typo3 SystemEnvironmentBuilder::definePaths() Abs. path of the TYPO3 admin dir (PATH_site + TYPO3_mainDir). No
PATH_typo3_mod SystemEnvironmentBuilder::definePaths() Relative path (from the PATH_typo3) to a properly configured module. No
PATH_site SystemEnvironmentBuilder::definePaths() Absolute path to directory with the frontend (one directory above PATH_typo3) Yes
PATH_typo3conf SystemEnvironmentBuilder::definePaths() Absolute TYPO3 configuration path (local, not part of source). Yes
TYPO3_db Bootstrap::populateLocalConfiguration() Name of the database, for example "t3_coreinstall". Is defined after the inclusion of typo3conf/LocalConfiguration.php (same for the other TYPO3_* constants below. Yes
TYPO3_db_username Bootstrap::populateLocalConfiguration() Database username Yes
TYPO3_db_password Bootstrap::populateLocalConfiguration() Database password Yes
TYPO3_db_host Bootstrap::populateLocalConfiguration() Database hostname, e.g. "localhost" Yes
TYPO3_extTableDef_script Bootstrap::populateLocalConfiguration()

Name of a php-include script found in "typo3conf/" that contains PHP code that further modifies the table definitions set by the TYPO3 CMS Core.

Deprecated. Make Extensions instead.

TYPO3_DLOG Bootstrap::defineLoggingAndExceptionConstants() If true, calls to \TYPO3\CMS\Core\Utility\GeneralUtility::devLog() can be made in both frontend and backend; This is event logging which can help to track debugging in general. Yes
TYPO3_MOD_PATH [prior to init.php] Path to module relative to PATH_typo3 (as defined in the module configuration). Must be defined prior to init.php.
TYPO3_enterInstallScript [prior to init.php]

If defined and set true the Install Tool is activated and the script exits after that. Used in typo3/install/index.php:


define('TYPO3_enterInstallScript', '1');
TYPO3_PROCEED_IF_NO_USER [prior to init.php]

If defined and set true the bootstrapping process will return to the parent script even if no backend user was authenticated!

For example, this constant is set by the index.php script so it can include init.php and still show the login form:

define('TYPO3_PROCEED_IF_NO_USER', 1);
require ('init.php');

Please be very careful with this feature - use it only when you have total control of what you are doing!

TYPO3_cliMode [prior to init.php]

Initiates CLI (Command Line Interface) mode. This is used when you want a shell executable PHP script to initialize a TYPO3 backend.

For more details see Initializing TYPO3 backend in a PHP shell script in Inside TYPO3.

TYPO3_version SystemEnvironmentBuilder::defineBaseConstants() The TYPO3 version, as a "x.y.z" number. Development versions will be either "x.y.z-dev" for stable versions or "x.y-dev" for the current master. Yes
TYPO3_branch SystemEnvironmentBuilder::defineBaseConstants() The TYPO3 version Branch, as a "x.y" number. Without the patch level. Yes
Table 2: Base Constants

Check \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::defineBaseConstants() for updates.

String constants
Constant Value Description
NUL chr(0) A null
TAB chr(9) A tabulator
LF chr(10) A linefeed
CR chr(13) A carriage return
SUB chr(26) A sub (substitute) character
CRLF CR + LF Carriage return + linefeed pair
Operating system identifier
Constant Value Description
TYPO3_OS self::getTypo3Os()) Either "WIN" or empty string
Service error constants
Constant Value Description
T3_ERR_SV_GENERAL -1 General error - something went wrong
T3_ERR_SV_NOT_AVAIL -2 During execution it showed that the service is not available and should be ignored. The service itself should call $this->setNonAvailable()
T3_ERR_SV_WRONG_SUBTYPE -3 Passed subtype is not possible with this service
T3_ERR_SV_NO_INPUT -4 Passed subtype is not possible with this service
T3_ERR_SV_FILE_NOT_FOUND -20 File not found which the service should process
T3_ERR_SV_FILE_READ -21 File not readable
T3_ERR_SV_FILE_WRITE -22 File not writable
T3_ERR_SV_PROG_NOT_FOUND -40 Passed subtype is not possible with this service
T3_ERR_SV_PROG_FAILED -41 Passed subtype is not possible with this service

Global variables


Variables in italics may be set in a script prior to inclusion of init.php so they are optional.


To make the table below a bit more compact, namespaces were left out. Here are the fully qualified class names referred to below:

  • "SystemEnvironmentBuilder" = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder
  • "Bootstrap" = \TYPO3\CMS\Core\Core\Bootstrap
Global variable Defined in Description Avail. in FE
$TYPO3_CONF_VARS typo3/sysext/core/Configuration/DefaultConfiguration.php TYPO3 configuration array. Please refer to mentioned file where each option is described in detail as comments. The same comments are also available in the Install Tool under the menu "All Configuration". Yes
$TYPO3_LOADED_EXT Bootstrap::populateTypo3LoadedExtGlobal() Array with all loaded extensions listed with a set of paths. You can check if an extension is loaded by the function \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded($key) where $key is the extension key. Yes
$TYPO3_DB Bootstrap::initializeTypo3DbGlobal()

An instance of the TYPO3 DB wrapper class, \TYPO3\CMS\Core\Database\DatabaseConnection. Formerly (before 8.2) this object had to be used for all interaction with the database.


You should NOT use this anymore. Use Doctrine instead!

$EXEC_TIME SystemEnvironmentBuilder::initializeGlobalTimeTrackingVariables() Is set to time() so that the rest of the script has a common value for the script execution time. YES
$SIM_EXEC_TIME SystemEnvironmentBuilder::initializeGlobalTimeTrackingVariables() Is set to $EXEC_TIME but can be altered later in the script if we want to simulate another execution-time when selecting from e.g. a database (used in the frontend for preview of future and past dates) Yes
$PARSETIME_START SystemEnvironmentBuilder::initializeGlobalTimeTrackingVariables() Time in milliseconds right after inclusion of the configuration. No
$TYPO3_AJAX ajax.php Set to true to indicate that an AJAX call is being processed No
$PAGES_TYPES typo3/sysext/core/ext_tables.php See Page types (occasionally)
$TCA Bootstrap::loadExtensionTables() See TCA Reference Yes, partly
$TBE_MODULES typo3/sysext/core/ext_tables.php The backend main/sub-module structure. See section elsewhere plus source code of class \TYPO3\CMS\Backend\Module\ModuleLoader which also includes some examples. (occasionally)
$TBE_STYLES typo3/sysext/core/ext_tables.php Contains information related to BE skinning. (occasionally)
$T3_SERVICES SystemEnvironmentBuilder::initializeGlobalVariables() Global registration of services. Yes
$T3_VAR SystemEnvironmentBuilder::initializeGlobalVariables()

Space for various internal global data storage in TYPO3. Each key in this array is a data space for an application. Keys currently defined for use is:

['callUserFunction'] + ['callUserFunction_classPool']: Used by \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction to store singleton objects.

['getUserObj'] : Used by \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj to store singleton objects.

['RTEobj'] : Used to hold the current RTE object if any. See \TYPO3\CMS\Backend\Utility\BackendUtility.

['ext'][ extension-key ] : Free space for extensions.

$FILEMOUNTS Bootstrap::initializeBackendUserMounts() Array of filepaths on the server to be mounted in the directory tree. (depends)
$BE_USER Bootstrap::initializeBackendUser() Backend user object. See Backend User Object. (depends)
$TBE_MODULES_EXT [In ext_tables.php files of extensions] Used to store information about modules from extensions that should be included in "function menus" of real modules. See the Extension API for details. (occasionally)
$TCA_DESCR [tables.php files] Can be set to contain file references to local lang files containing TCA_DESCR labels. See section about Context Sensitive Help. No
Exploring global variables

Many of the global variables described above can be inspected using the Admin Tools > Configuration module.


This module is always viewed in the BE context. Variables defined only in the FE context will not be visible there.

The Configuration module in Admin Tools

Viewing the $TCA array using the Admin Tools > Configuration module

Backend User Object

The backend user of a session is always available to the backend scripts as the global variable $BE_USER. The object is created in \TYPO3\CMS\Core\Core\Bootstrap::initializeBackendUser() and is an instance of the class \TYPO3\CMS\Core\Authentication\BackendUserAuthentication (which extends \TYPO3\CMS\Core\Authentication\AbstractUserAuthentication).

In addition to $BE_USER one other global variables is of interest - $FILEMOUNTS, holding an array with the File mounts of the $BE_USER.

Checking user access

The $BE_USER object is mostly used to check user access right, but contains other helpful information. This is presented here by way of a few examples:

Checking access to current backend module

$MCONF is module configuration and the key $MCONF['access'] determines the access scope for the module. This function call will check if the $BE_USER is allowed to access the module and if not, the function will exit with an error message.

$BE_USER->modAccess($MCONF, 1);
Checking access to any backend module

If you know the module key you can check if the module is included in the access list by this function call:

$BE_USER->check('modules', 'web_list');

Here access to the module "Web > List" is checked.

Access to tables and fields?

The same function ->check() can actually check all the ->groupLists inside $BE_USER. For instance:

Checking modify access to the table "pages":

$BE_USER->check('tables_modify', 'pages');

Checking read access to the table "tt_content":

$BE_USER->check('tables_select', 'tt_content');

Checking if a table/field pair is allowed explicitly through the "Allowed Excludefields":

$BE_USER->check('non_exclude_fields', $table . ':' . $field);
Is "admin"?

If you want to know if a user is an "admin" user (has complete access), just call this method:

Read access to a page?

This function call will return true if the user has read access to a page (represented by its database record, $pageRec):

$BE_USER->doesUserHaveAccess($pageRec, 1);

Changing the "1" for other values will check other permissions:

  • use "2" for checking if the user may edit the page
  • use "4" for checking if the user may delete the page.
Is a page inside a DB mount?

Access to a page should not be checked only based on page permissions but also if a page is found within a DB mount for ther user. This can be checked by this function call ($id is the page uid):

Selecting readable pages from database?

If you wish to make a SQL statement which selects pages from the database and you want it to be only pages that the user has read access to, you can have a proper WHERE clause returned by this function call:


Again the number "1" represents the "read" permission; "2" is "edit" and "4" is delete permission. The result from the above query could be this string:

((pages.perms_everybody & 1 = 1)OR(pages.perms_userid = 2 AND pages.perms_user & 1 = 1)OR(pages.perms_groupid in (1) AND pages.perms_group & 1 = 1))
Saving module data

This stores the input variable $compareFlags (an array!) with the key "tools_beuser/index.php/compare"

$compareFlags = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('compareFlags');
$BE_USER->pushModuleData('tools_beuser/index.php/compare', $compareFlags);
Getting module data

This gets the module data with the key "tools_beuser/index.php/compare" (lasting only for the session)

$compareFlags = $BE_USER->getModuleData('tools_beuser/index.php/compare', 'ses');
Getting TSconfig

This function can return a value from the "User TSconfig" structure of the user. In this case the value for "options.clipboardNumberPads":

Getting the username

The full "be_users" record of a authenticated user is available in $BE_USER->user as an array. This will return the "username":

Get User Configuration value

The internal ->uc array contains options which are managed by the User Tools > User Settings module (extensions "setup"). These values are accessible in the $BE_USER->uc array. This will return the current state of "Condensed mode" for the user:


TYPO3 Core Engine (TCE)



The TYPO3 Core Engine is the class that handles all *data* writing to database tables configured in $TCA. In addition the class handles commands such as copy, move, delete. It will handle undo/history and versioning of records and everything will be logged to the sys_log. And it will make sure that write permissions are evaluated correctly for the user trying to write to the database. Generally, any processing specific option in the $TCA array is handled by TCE.

Using TCE for manipulation of the database content in the $TCA-configured tables guarantees that the data integrity of TYPO3 is respected. This cannot be safely guaranteed if you write to $TCA-configured database tables directly. It will also manage the relations to files and other records.

TCE requires a backend login to work. This is due to the fact that permissions are observed (of course) and thus TCE needs a backend user to evaluate against. This means you cannot use DataHandler from the frontend scope. Thus writing to tables (such as a guestbook) will have to be done from the frontend without DataHandler.

The features of the $TCA are described in the TCA Reference.


TCE also has a part for handling files. The file operations are normally performed in the File > List module where you can manage a directory on the server by copying, moving, deleting and editing files and directories. The file operations are managed by two core classes, \TYPO3\CMS\Core\Utility\File\BasicFileUtility and \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility.

Database: DataHandler basics (formerly known as TCEmain)

When you are using TCE from your backend applications you need to prepare two arrays of information which contain the instructions to DataHandler (\TYPO3\CMS\Core\DataHandling\DataHandler) of what actions to perform. They fall into two categories: data and commands.

"Data" is when you want to write information to a database table or create a new record.

"Commands" is when you want to move, copy or delete a record in the system.

The data and commands are created as multidimensional arrays and to understand the API of DataHandler you simply need to understand the hierarchy of these two arrays.

Commands Array


$cmd[ tablename ][ uid ][ command ] = value

Description of keywords in syntax:

Key Data type Description
tablename string Name of the database table. Must be configured in $TCA array, otherwise it cannot be processed.
uid integer The UID of the record that is manipulated. This is always an integer.
command string (command keyword)

The command type you want to execute.


Only one command can be executed at a time for each record! The first command in the array will be taken.

See table below for :ref:`command keywords and values <tce-command-keywords>`

value mixed

The value for the command

See table below for :ref:`command keywords and values <tce-command-keywords>`

Command keywords and values
Command Data type Value
copy integer

The significance of the value depends on whether it is positive or negative:

  • Positive value: The value points to a page UID. A copy of the record (and possibly child elements/tree below) will be inserted inside that page as the first element.
  • Negative value: The (absolute) value points to another record from the same table as the record being copied. The new record will be inserted on the same page as that record and if $TCA[...]['ctrl']['sortby'] is set, then it will be positioned after.
  • Zero value: Record is inserted on tree root level.
move integer Works like "copy" but moves the record instead of making a copy.
delete 1

Value should always be "1"

This action will delete the record (or mark the record "deleted" if configured in $TCA).

undelete 1

Value should always be "1".

This action will set the deleted-flag back to 0.

localize integer

Pointer to a sys_language uid to localize the record into. Basically a localization of a record is making a copy of the record (possibly excluding certain fields defined with l10n_mode) but changing relevant fields to point to the right sys language / original language record.

Requirements for a successful localization is this:

  • [ctrl] options "languageField" and "transOrigPointerField" must be defined for the table
  • A sys_language record with the given sys_language_uid must exist.
  • The record to be localized by currently be set to "Default" language and not have any value set for the transOrigPointerField either.
  • There cannot exist another localization to the given language for the record (looking in the original record PID).

Apart from this, ordinary permissions apply as if the user wants to make a copy of the record on the same page.

version array

Versioning action.


  • [action] : Keyword determining the versioning action. Options are:
    • "new": Indicates that a new version of the record should be created.Additional keys, specific for "new" action:
      • [treeLevels]: (Only pages) Integer, -1 to 4, indicating the number of levels of the page tree to version together with a page. This is also referred to as the versioning type:-1 ("element") means only the page record gets versioned (default)0 ("page") means the page + content tables (defined by ctrl-flag versioning_followPages )>0 ("branch") means the the whole branch is versioned ( full copy of all tables), down to the level indicated by the value (1= 1 level down, 2= 2 levels down, etc.)The treeLevel is recorded in the field t3ver_swapmode and will be observed when the record is swapped during publishing.
      • [label]: Indicates the version label to apply. If not given, a standard label including version number and date is added.
    • "swap": Indicates that the current online version should be swapped with another.Additional keys, specific for "swap" action:
      • [swapWith]: Indicates the uid of the record to swap current version with!
      • [swapIntoWS]: Boolean, indicates that when a version is published it should be swapped into the workspace of the offline record.
    • "clearWSID": Indicates that the workspace of the record should be set to zero (0). This removes versions out of workspaces without publishing them.
    • "flush": Completely deletes a version without publishing it.
    • "setStage": Sets the stage of an element. Special feature: The id- key in the array can be a comma list of ids in order to perform the stageChange over a number of records. Also, the internal variable ->generalComment (also available through :file:`tce_db.php` as "&generalComment") can be used to set a default comment for all stage changes of an instance of tcemain. Additional keys for this action is:
      • [stageId]: Values are: -1 (rejected), 0 (editing, default), 1 (review), 10 (publish)
      • [comment]: Comment string that goes into the log.
Examples of commands:
$cmd['tt_content'][54]['delete'] = 1;    // Deletes tt_content record with uid=54
$cmd['pages'][1203]['copy'] = -303;   //Copies page id=1203 to the position after page 303
$cmd['pages'][1203]['move'] = 303;  // Moves page id=1203 to the first position in page 303
Data Array


$data[tablename][uid][fieldname] = value

Description of keywords in syntax:

Key Data type Description
tablename string Name of the database table. Must be configured in $TCA array, otherwise it cannot be processed.
uid mixed The UID of the record that is modified. If the record already exists, this is an integer. If you're creating new records, use a random string prefixed with "NEW", e.g. "NEW7342abc5e6d".
fieldname string Name of the database field you want to set a value for. Must be configure in $TCA[ tablename ]['columns']
value string

Value for "fieldname".


Always make sure $this->stripslashes_values is false before using DataHandler.)


For FlexForms the data array of the FlexForm field is deeper than three levels. The number of possible levels for FlexForms is infinite and defined by the data structure of the FlexForm. But FlexForm fields always end with a "regular value" of course.

Examples of Data submission

This creates a new page titled "The page title" as the first page inside page id 45:

$data['pages']['NEW9823be87'] = array(
    'title' => 'The page title',
    'subtitle' => 'Other title stuff',
    'pid' => '45'

This creates a new page titled "The page title" right after page id 45 in the tree:

$data['pages']['NEW9823be87'] = array(
    'title' => 'The page title',
    'subtitle' => 'Other title stuff',
    'pid' => '-45'

This creates two new pages right after each other, located right after the page id 45:

$data['pages']['NEW9823be87'] = array(
    'title' => 'Page 1',
    'pid' => '-45'
$data['pages']['NEWbe68s587'] = array(
    'title' => 'Page 2',
    'pid' => '-NEW9823be87'

Notice how the second "pid" value points to the "NEW..." id placeholder of the first record. This works because the new id of the first record can be accessed by the second record. However it works only when the order in the array is as above since the processing happens in that order!

This updates the page with uid=9834 to a new title, "New title for this page", and no_cache checked:

$data['pages'][9834] = array(
    'title' => 'New title for this page',
    'no_cache' => '1'
Clear cache

TCE also has an API for clearing the cache tables of TYPO3:


$cacheCmd values Description
[integer] Clear the cache for the page id given.

Clears all cache tables (cache_pages, cache_pagesection, cache_hash).

Only available for admin-users unless explicitly allowed by User TSconfig "options.clearCache.all".


Clears all pages from cache_pages.

Only available for admin-users unless explicitly allowed by User TSconfig "options.clearCache.pages".

"temp_CACHED" Clears the temp_CACHED files in typo3conf/.
Hook for cache post-processing

You can configure cache post-processing with a user defined PHP function. Configuration of the hook can be done from ext_localconf.php. An example might look like:

$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'][] = 'myext_cacheProc->proc';
require_once(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('myext') . 'class.myext_cacheProc.php');
Flags in DataHandler

There are a few internal variables you can set prior to executing commands or data submission. These are the most significant:

Internal variable Data type Description
->deleteTree Boolean

Sets whether a page tree branch can be recursively deleted.

If this is set, then a page is deleted by deleting the whole branch under it (user must have delete permissions to it all). If not set, then the page is deleted only if it has no branch.

Default is false.

->copyTree Integer

Sets the number of branches on a page tree to copy.

If 0 then branch is not copied. If 1 then pages on the 1st level is copied. If 2 then pages on the second level is copied, and so on.

Default is zero.

->reverseOrder Boolean

If set, the data array is reversed in the order, which is a nice thing if you're creating a whole bunch of new records.

Default is zero.

->copyWhichTables list of strings (tables)

This list of tables decides which tables will be copied. If empty then none will. If "*" then all will (that the user has permission to of course).

Default is "*".

->stripslashes_values boolean

If set, then all values will be passed through stripslashes(). This has been the default since the birth of TYPO3 in times when input from POST forms were always escaped an needed to be unescaped. Today this is deprecated and values should be passed around without escaped characters.


It is highly recommended to set this value to zero every time the class is used!

If you set this value to false you can pass values as-is to the class and it is most like that this is what you want. Otherwise you would have to pass all values through addslashes() first.

Default is (currently) "1" (true) but might be changed in the future!

Using DataHandler in scripts

It's really easy to use the class \TYPO3\CMS\Core\DataHandling\DataHandler in your own scripts. All you need to do is include the class, build a $data/$cmd array you want to pass to the class and call a few methods.


Mind that these scripts have to be run in the backend scope! There must be a global $BE_USER object.

In your script you simply insert this line to include the class:

What follows are a few code listings with comments which will provide you with enough knowledge to get started. It is assumed that you have populated the $data and $cmd arrays correctly prior to these chunks of code. The syntax for these two arrays is explained in the previous chapter.

DataHandler examples
Submitting data

This is the most basic example of how to submit data into the database. It is four lines. Line 1 instantiates the class, line 2 defines that values will be provided without escaped characters (recommended!), line 3 registers the $data array inside the class and initializes the class internally! Finally line 4 will execute the data submission.

$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
$tce->stripslashes_values = 0;
$tce->start($data, array());
Executing commands

The most basic way of executing commands. Line 1 creates the object, line 2 defines that values will be provided without escaped characters (recommended), line 3 registers the $cmd array inside the class and initializes the class internally! Finally line 4 will execute the commands.

$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
$tce->stripslashes_values = 0;
$tce->start(array(), $cmd);
Clearing cache

In this example the cache clearing API is used. No data is submitted, no commands executed. Still you will have to initialize the class by calling the start() method (which will initialize internal variables).


Clearing a given cache is possible only for users that are "admin" or have specific permissions to do so.

$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
$tce->start(array(), array());

Since TYPO3 CMS 6.2, caches are organized in groups. Clearing "all" caches will actually clear caches from the "all" group and not really all caches. Check the caching framework architecture section for more details about available caches and groups.

Complex data submission

Imagine the $data array something like this:

$data = array(
    'pages' => array(
        'NEW_1' => array(
            'pid' => 456,
            'title' => 'Title for page 1',
        'NEW_2' => array(
            'pid' => 456,
            'title' => 'Title for page 2',

This aims to create two new pages in the page with uid "456". In the follow code this is submitted to the database. Notice how line 3 reverses the order of the array. This is done because otherwise "page 1" is created first, then "page 2" in the same PID meaning that "page 2" will end up above "page 1" in the order. Reversing the array will create "page 2" first and then "page 1" so the "expected order" is preserved.

To insert a record after a given record, set the other record's negative uid as pid in the new record you're setting as data.

Apart from this line 6 will send a "signal" that the page tree should be updated at the earliest occasion possible. Finally, the cache for all pages is cleared in line 7.

$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
$tce->stripslashes_values = 0;
$tce->reverseOrder = 1;
$tce->start($data, array());
Both data and commands executed with alternative user object

In this case it is shown how you can use the same object instance to submit both data and execute commands if you like. The order will depend on the order of line 4 and 5.

In line 3 the start() method is called, but this time with the third possible argument which is an alternative $BE_USER object. This allows you to force another backend user account to create stuff in the database. This may be useful in certain special cases. Normally you should not set this argument since you want TCE to use the global $BE_USER.

$tce = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\DataHandling\\DataHandler');
$tce->stripslashes_values = 0;
$tce->start($data, $cmd, $alternative_BE_USER);

The "tce_db.php" API

This script is a gateway for POST forms to class \TYPO3\CMS\Core\DataHandling\DataHandler. It has historically been the script to which data was posted when you wanted to update something in the database.

Today it is used for editing by only a few scripts, actually only the "Quick Edit" module in "Web>Page" (frontend). The standard forms you find in TYPO3 are normally rendered and handled by alt_doc.php which includes \TYPO3\CMS\Core\DataHandling\DataHandler on its own.

For commands it is still used from various locations.

You can send data to this file either as GET or POST vars where POST takes precedence. The variable names you can use are:

GP var name Data type Description
data array

Data array on the form [tablename][uid][fieldname] = value.

Typically it comes from a POST form which submits a form field like <input name="data[tt_content][123][header]" value="This is the headline" />.

cmd array

Command array on the form [tablename][uid][command] = value. This array may get additional data set internally based on clipboard commands send in CB var!

Typically this comes from GET vars passed to the script like &cmd[tt\_content][123][delete]=1 which will delete Content Element with UID 123.

cacheCmd string Cache command sent to ->clear_cacheCmd
redirect string Redirect URL. Script will redirect to this location after performing operations (unless errors has occurred)
flags array Accepts options to be set in TCE object. Currently it supports "reverseOrder" (boolean).
mirror array Example: [mirror][table][11] = '22,33' will look for content in [data][table][11] and copy it to [data][table][22] and [data][table][33].
prErr boolean If set, errors will be printed on screen instead of redirection. Should always be used, otherwise you will see no errors if they happen.
CB array Clipboard command array. May trigger changes in "cmd".
vC string Verification code
uPT string Update Page Tree Trigger. If set and the manipulated records are pages then the update page tree signal will be set.

File functions basics

File operations in the TCE are handled by the class \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility which extends \TYPO3\CMS\Core\Utility\File\BasicFileUtility. The instructions for file manipulation are passed to this class as a multidimensional array.

Files Array


$file[ command ][ index ][ key ] = value

Description of keywords in syntax:

Key Data type Description
command string (command keyword)

The command type you want to execute.

See table below for :ref:`command keywords, keys and values<tce-file-keywords>`

index integer Integer index in the array which separates multiple commands of the same type.
key string

Depending on the command type. The keys will carry the information needed to perform the action. Typically a "target" key is used to point to the target directory or file while a "data" key carries the data.

See table below for :ref:`command keywords, keys and values<tce-file-keywords>`

value string

The value for the command

See table below for :ref:`command keywords, keys and values<tce-file-keywords>`

Command keywords and values
Command Keys Value
delete "data" "data" = Absolute path to the file/folder to delete




"data" = Absolute path to the file/folder to copy

"target" = Absolute path to the folder to copy to (destination)

"altName" = (boolean): If set, a new filename is made by appending numbers/unique-string in case the target already exists.





(Exactly like copy, just replace the word "copy" with "move")



"data" = New name, max 30 characters alphanumeric

"target" = Absolute path to the target file/folder




"data" = Folder name, max 30 characters alphanumeric

"target" = Absolute path to the folder where to create it




"data" = New filename

"target" = Absolute path to the folder where to create it




"data" = The new content

"target" = Absolute path to the target file





"data" = ID-number (points to the global var that holds the filename- ref ($_FILES["upload_" . $id]["name"]).

"target" = Absolute path to the target folder (destination)

upload_$id = File reference. $id must equal value of file[upload][...][data]!

See \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility::func_upload().




"data" = Absolute path to the zip-file. (file extension must be "zip")

"target" = The absolute path to the target folder (destination) (if not set, default is the same as the zip-file)

It is unlikely that you will need to use this internally in your scripts like you will need \TYPO3\CMS\Core\DataHandling\DataHandler. It is fairly uncommon to need the file manipulations in own scripts unless you make a special application. Therefore the most typical usage of this API is from tce_file.php and the core scripts that are activated by the "File > List" module.

However, if you need it this is an example (taken from tce_file.php) of how to initialize the usage.

    // Initializing:
$this->fileProcessor = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\File\\ExtendedFileUtility');
$this->fileProcessor->init($FILEMOUNTS, $TYPO3_CONF_VARS['BE']['fileExtensions']);


Line 2 makes an instance of the class and line 3 initializes the object with the filemounts of the current user and the array of allow/deny file extensions in web-space and ftp-space (see below). Then the file operation permissions are loaded from the user object in line 4. Finally, the file command array is loaded in line 6 (and internally additional configuration takes place from $TYPO3_CONF_VARS!). In line 7 the command map is executed.

Web-space, FTP-space and $TYPO3_CONF_VARS['BE']['fileExtensions']

The control of file extensions goes in two categories. Webspace and ftpspace. Webspace is folders accessible from a web browser (below TYPO3_DOCUMENT_ROOT) and ftpspace is everything else.

The control is done like this: if an extension matches 'allow' then the check returns true. If not and an extension matches 'deny' then the check return false. If no match at all, returns true.

You list extensions comma-separated. If the value is a '*' every extension is matched. If no file extension, true is returned if 'allow' is '*', false if 'deny' is '*' and true if none of these matches. This (default) configuration below accepts everything in ftpspace and everything in webspace except php files:

$TYPO3_CONF_VARS['BE']['fileExtensions'] = array (
    'webspace' => array('allow' => '', 'deny' => 'php'),
    'ftpspace' => array('allow' => '*', 'deny' => '')

The "tce_file.php" API

This script serves as the file administration part of the TYPO3 Core Engine. It's a gateway for TCE (TYPO3 Core Engine) file-handling through POST forms. It uses \TYPO3\CMS\Core\Utility\File\ExtendedFileUtility for the manipulation of the files.

This script is used from the File > List module where you can rename, create, delete etc. files and directories on the server.

You can send data to this file either as GET or POST vars where POST takes precedence. The variable names you can use are:

GP var name Data type Description
file array

Array of file operations. See previous information about basic file functions.

This could typically be a GET var like &file[delete][0][data]=[absolute file path] or a POST form field like:

"<input type="text" name="file[newfolder][0][data]" value=""/>
<input type="hidden" name="file[newfolder][0][target]"
value="[absolute path to folder to create in]"/>"
redirect string Redirect URL. Script will redirect to this location after performing operations.
CB array Clipboard command array. May trigger changes in "file"
vC string Verification code
overwriteExistingFiles boolean If existing files should be overridden.

Programming with workspaces in mind

The concept of workspaces needs attention from extension programmers. The implementation of workspaces is however made so that no critical problems can appear with old extensions;

  • First of all the "Live workspace" is no different from how TYPO3 has been working for years so that will be supported out of the box (except placeholder records must be filtered out in the frontend with t3ver_state != , see below).
  • Secondly, all permission related issues are implemented in DataHandler so the worst your users can experience is an error message.

However, you probably want to update your extension so that in the backend the current workspace is reflected in the records shown and the preview of content in the frontend works as well. Therefore this chapter has been written with instructions and insight into the issues you are facing.

Frontend challenges in general

For the frontend the challenges are mostly related to creating correct previews of content in workspaces. For most extensions this will work transparently as long as they use the API functions in TYPO3 to request records from the system.

The most basic form of a preview is when a live record is selected and you lookup a future version of that record belonging to the current workspace of the logged in backend user. This is very easy as long as a record is selected based on its "uid" or "pid" fields which are not subject to versioning; You simply call sys_page->versionOL() after record selection.

However, when other fields are involved in the where clause it gets dirty. This happens all the time! For instance, all records displayed in the frontend must be selected with respect to "enableFields" configuration! What if the future version is hidden and the live version is not? Since the live version is selected first (not hidden) and then overlaid with the content of the future version (hidden) the effect of the hidden field we wanted to preview is lost unless we also check the overlaid record for its hidden field (->versionOL() actually does this). But what about the opposite; if the live record was hidden and the future version not? Since the live version is never selected the future version will never have a chance to display itself! So we must first select the live records with no regard to the hidden state, then overlay the future version and eventually check if it is hidden and if so exclude it. The same problem applies to all other "enableFields", future versions with "delete" flags and current versions which are invisible placeholders for future records. Anyway, all that is handled by the \TYPO3\CMS\Frontend\Page\PageRepository class which includes functions for "enableFields" and "deleted" so it will work out of the box for you. But as soon as you do selection based on other fields like email, username, alias etc. it will fail.


Challenge: How to preview elements which are disabled by "enableFields" in the live version but not necessarily in the offline version. Also, how to filter out new live records with t3ver_state set to 1 (placeholder for new elements) but only when not previewed.

Solution: Disable check for enableFields/where_del_hidden on live records and check for them in versionOL on input record.

Frontend implementation guidelines

  • Any place where enableFields() are not used for selecting in the frontend you must at least check that t3ver_state != 1 so placeholders for new records are not displayed.
  • Make sure never to select any record with pid = -1! (offline records - related to versioning).
  • If you need to detect preview mode for versioning and workspaces you can read these variables:
    • $GLOBALS['TSFE']->sys_page->versioningPreview: If true, you are allowed to display previews of other record versions.
    • $GLOBALS['TSFE']->sys_page->versioningWorkspaceId: Will tell you the id of the workspace of the current backend user. Used for preview of workspaces.
  • Use these API functions for support of version previews in the frontend:
Function Description
$GLOBALS['TSFE']->sys_page->versionOL($table, &$row, $unsetMovePointers=FALSE)

Versioning Preview Overlay.

Generally ALWAYS used when records are selected based on uid or pid. If records are selected on other fields than uid or pid (e.g. "email = ....") then usage might produce undesired results and that should be evaluated on individual basis.

Principle: Record online! => Find offline?


This is how simple it is to use this record in your frontend plugins when you do queries directly (not using API functions already using them):

$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(...);
while (($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {

   if (is_array($row)) {
      // ...
   // ...

When the live record is selected, call ->versionOL() and make sure to check if the input row (passed by reference) is still an array.

The third argument, $unsetMovePointers = FALSE, can be set to TRUE when selecting records for display ordered by their position in the page tree. Difficult to explain easily, so only use this option if you don't get a correct preview of records that has been moved in a workspace (only for "element" type versioning)


Finding online PID for offline version record.

Will look if the "pid" value of the input record is -1 (it is an offline version) and if the table supports versioning; if so, it will translate the -1 PID into the PID of the original record

Used whenever you are tracking something back, like making the root line. In fact, it is currently only used by the root line function and chances are that you will not need this function often.

Principle: Record offline! => Find online?

Frontend scenarios impossible to preview

These issues are not planned to be supported for preview:

  • Lookups and searching for records based on other fields than uid, pid or "enableFields" will never reflect workspace content since overlays happen to online records after they are selected.

    • This problem can largely be avoided for versions of new records because versions of a "New"-placeholder can mirror certain fields down onto the placeholder record. For the tt\_content table this is configured as

      shadowColumnsForNewPlaceholders'=> 'sys\_language\_uid,l18n\_parent,colPos,header'

      so that these fields used for column position, language and header title are also updated in the placeholder thus creating a correct preview in the frontend.

    • For versions of existing records the problem is in reality reduced a lot because normally you don't change the column or language fields after the record is first created anyway! But in theory the preview can fail.

    • When changing the type of a page (e.g. from "Standard" to "External URL") the preview might fail in cases where a look up is done on the :code`doktype` field of the live record.

      • Page shortcuts might not work properly in preview.
      • Mount Points might not work properly in preview.
  • It is impossible to preview the value of count(*) selections since we would have to traverse all records and pass them through ->versionOL() before we would have a reliable result!

  • In \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::getPageShortcut(), sys_page->getMenu() is called with an additional WHERE clause which will not respect if those fields are changed for a future version. This could be the case other places where getmenu() is used (but a search shows it is not a big problem). In this case we will for now accept that a wrong shortcut destination can be experienced during previews.

Backend challenges

The main challenge in the backend is to reflect how the system will look when the workspace gets published. To create a transparent experience for backend users we have to overlay almost every selected record with any possible new version it might have. Also when we are tracking records back to the page tree root point we will have to correct pid-values. All issues related to selecting on fields other than pid and uid also relates to the backend as they did for the frontend.

Backend module access

You can restrict access to backend modules by using $MCONF['workspaces'] in the conf.php files. The variable is a list of keywords defining where the module is available:

$MCONF['workspaces'] = online,offline,custom

You can also restrict function menu items to certain workspaces if you like. This is done by an argument sent to the function \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction(). See that file for more details.

Detecting current workspace

You can always check what the current workspace of the backend user is by reading $GLOBALS['BE_USER']->workspace. If the workspace is a custom workspace you will find its record loaded in $GLOBALS['BE_USER']->workspaceRec.

The values for workspaces is either 0 (online/live) or the uid of the corresponding entry in the sys_workspace table.

Using DataHandler with workspaces

Since admin users are also restricted by the workspace it is not possible to save any live records when in a workspace. However for very special occasions you might need to bypass this and to do so, you can set the instance variable \TYPO3\CMS\Core\DataHandling\DataHandler::bypassWorkspaceRestrictions to TRUE. An example of this is when users are updating their user profile using the "User Tool > User Settings" module; that actually allows them to save to a live record (their user record) while in a draft workspace.

Moving in workspaces

TYPO3 4.2 and beyond supports moving for "Element" type versions in workspaces. Technically this works by creating a new online placeholder record (like for new elements in a workspace) in the target location with t3ver_state = 3 (move-to placeholder) and a field, t3ver_move_id, holding the uid of the record to move (source record) upon publishing. In addition, a new version of the source record is made and has t3ver_state = 4 (move-to pointer). This version is simply necessary in order for the versioning system to have something to publish for the move operation.

So in summary, two records are created for a move operation in a workspace: The placeholder (online, with t3ver_state = 3 and t3ver_move_id set) and a new version (t3ver_state = 4) of the online source record (the one being moved).

When the version of the source is published a look up will be made to see if a placeholder exists for a move operation and if so the record will take over the pid / "sortby" value upon publishing.

Preview of move operations is almost fully functional through the \TYPO3\CMS\Frontend\Page\PageRepository::versionOL() and \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL() functions. When the online placeholder is selected it simply looks up the source record, overlays any version on top and displays it. When the source record is selected it should simply be discarded in case shown in context where ordering or position matters (like in menus or column based page content). This is done in the appropriate places.

Using the system log


A new logging API was introduced in TYPO3 CMS 6.0. It is far more flexible than the old one described here, but is not yet in use in the Core. Thus this section remains valid if you want to write to the "sys_log" table. Otherwise please consider using the new API.

Writing to the system log is done using the backend user object, which writes to the "sys_log" table:

$this->BE_USER->writelog($type, $action, $error, $details_nr, $details, $data, $table, $recuid, $recpid,$event_pid, $NEWid);

Here is a description of the arguments to this function call, and corresponding database fields in table "sys_log":

Field Type Var Description
type tinyint $type

Value telling which module in TYPO3 set the log entry. The type values are paired with an action-integer which is telling in more detail what the event was. Here type and action values are arranged hierarchically (type on first level, action on second level):

  • 1 : \TYPO3\CMS\Core\DataHandling\DataHandler ("TYPO3 Core Engine" where database records are manipulated)
    • Action values are:
      • 0 = no category
      • 1 = new record
      • 2 = update record
      • 3 = delete record
      • 4 = move record
      • 5 = check/evaluate
  • 2 : "tce_file" (File handling in fileadmin/ and absolute filemounts)
    • Action values are for various file handling types like upload, rename, edit etc.
  • 3 : System (e.g. sys_history save)
  • 4 : Modules: This is the mode you may use for extensions having backend module functionality. Probably you would like to use BE_USER->simplelog() for your extensions.
  • 254 : Personal settings changed
  • 255 : Login or Logout action
    • 1 = login
    • 2 = logout
    • 3 = failed login (+ errorcode 3)
    • 4 = failure_warning_email sent
action tinyint $action

See "type" above

When not available, use value "0"

error tinyint $error

Error level:

  • 0 = message, a notice of an action that happened.
  • 1 = error, typically a permission problem for the user
  • 2 = System Error, something which should not happen for technical reasons.
  • 3 = Security notice, like login failures
details_nr tinyint $details_nr

Number of "detail" message. This number should be unique for the combination of type/action

-1 is a temporary detail number you can use while developing and error messages are not fixed yet.

0 is a value that means the message is not supposed to be translated

>= 1 means the message is fixed and ready for translation.

details tinytext $details

The log message text (in english). By identification through type/action/details_nr this can be translated through the localization system.

If you insert "%s" markers in the details message and set $data to an array the first 5 entries (keys 0-4) from $data will substitute the markers sequentially (using sprintf).

log_data tinyblob $data Data that follows the log entry. Can be an array. See "details" for more info.
tablename varchar(40) $table Table name. Special field used by tce_main.php.
recuid int $recuid Record UID. Special field used by tce_main.php.
recpid int $recpid Record PID. Special field used by tce_main.php. [OBSOLETE; not used anymore.]
event_pid int $event_pid The page ID (pid) where the event occurred. Used to select log-content for specific pages.
NEWid varchar(20) $NEWid Special field used by tce_main.php. NEWid string of newly created records.
tstamp int - EXEC_TIME of event, UNIX time in seconds.
uid int - Unique ID for log entry, automatically inserted
userid int - User ID of backend user, automatically set for you
IP varchar(39) - REMOTE_ADDR of client
workspace int - Workspace ID

Making logging simple

While it is nice to have log message categorized and numbered during development and sometimes beyond that point a simpler logging API is necessary. Therefore you can also call this function:

BE_USER->simplelog($message, $extKey='', $error=0);

All you need is to set $message to store a log message. If you call it from an extension it is good practice to also supply the extension key. Finally you can add the error number (according to the table above) if you need to signal an error.

Logging with TYPO3

TYPO3 Logging consists of the following components:

  • A Logger that receives the log message and related details, like a severity
  • A LogRecord model which encapsulates the data
  • Configuration of the logging system
  • Writers which write the log records to different targets (like file, database, rsyslog server, etc.)
  • Processors which add more detailed information to the log record.

Quick Usage

Instantiate a logger for the current class:

/** @var $logger \TYPO3\CMS\Core\Log\Logger */
$logger = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager')->getLogger(__CLASS__);

Log a simple message:

$logger->info('Everything went fine.');
$logger->warning('Something went awry, check your configuration!');

Provide additional information with the log message:

  'This was not a good idea',
    'foo' => $bar,
    'bar' => $foo,

$logger->warning() etc. are only shorthands - you can also call $logger->log() directly and pass the severity level:

   'This is an utter failure!'

By default the log entries are written to file typo3temp/logs/typo3.log. A sample output looks like this:

Fri, 08 Mar 2013 09:45:00 +0100 [INFO] request="5139a50bee3a1" component="TYPO3.Examples.Controller.DefaultController": Everything went fine.
Fri, 08 Mar 2013 09:45:00 +0100 [WARNING] request="5139a50bee3a1" component="TYPO3.Examples.Controller.DefaultController": Something went awry, check your configuration!
Fri, 08 Mar 2013 09:45:00 +0100 [ERROR] request="5139a50bee3a1" component="TYPO3.Examples.Controller.DefaultController": This was not a good idea - {"foo":"bar","bar":{}}
Fri, 08 Mar 2013 09:45:00 +0100 [CRITICAL] request="5139a50bee3a1" component="TYPO3.Examples.Controller.DefaultController": This is an utter failure!

The LogManager enables an auto-configured usage of loggers in your PHP code by reading the logging configuration and setting the minimum severity level of the Logger accordingly.

/** @var $logger \TYPO3\CMS\Core\Log\Logger */
$logger = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Core\Log\LogManager')->getLogger(__CLASS__);

Using __CLASS__ as name for the logger is recommended to enable logging configuration based on the class hierarchy.

Log() method

\TYPO3\CMS\Core\Log\Logger provides a central point for submitting log messages, the log() method:

$logger->log($level, $message, $data);

which takes three parameters:

Parameter Type Description
$level Type integer

One of either:

  • \TYPO3\CMS\Core\Log\LogLevel::EMERGENCY
  • \TYPO3\CMS\Core\Log\LogLevel::ALERT
  • \TYPO3\CMS\Core\Log\LogLevel::CRITICAL
  • \TYPO3\CMS\Core\Log\LogLevel::ERROR
  • \TYPO3\CMS\Core\Log\LogLevel::WARNING
  • \TYPO3\CMS\Core\Log\LogLevel::NOTICE
  • \TYPO3\CMS\Core\Log\LogLevel::INFO
  • \TYPO3\CMS\Core\Log\LogLevel::DEBUG
$message Type string The log message itself.
$data Type array Optional parameter, can contain additional data, which is added to the log record in the form of an array.

An early return in the log() method prevents unneeded computation work to be done. So you are safe to call $logger->debug() frequently without slowing down your code too much. The Logger will know by its configuration, what the most explicit severity level is.

As next step, all registered Processors are notified. They can modify the log records or add extra information.

The Logger then forwards the log records to all of its configured Writers, which will then persist the log record.

Shorthand methods

For each of the severity levels mentioned above, a shorthand method exists in \TYPO3\CMS\Core\Log\Logger, like

  • $logger->debug($message, array $data = array());
  • $logger->info($message, array $data = array());
  • $logger->notice($message, array $data = array());
  • etc.
Configuration of the Logging system

Instantiation of Loggers is configuration-free, as the LogManager automatically applies its configuration.

The Logger configuration is read from $GLOBALS['TYPO3_CONF_VARS']['LOG'], which contains an array reflecting the namespace and class hierarchy of your TYPO3 project.


To apply a configuration for all Loggers within the \TYPO3\CMS\Core\Cache namespace, the configuration is read from $GLOBALS['TYPO3_CONF_VARS']['LOG']['TYPO3']['CMS']['Core']['Cache']. So every logger requested for classes like \TYPO3\CMS\Core\Cache\CacheFactory, \TYPO3\CMS\Core\Cache\Backend\NullBackend, etc. will get this configuration applied. The same holds for the old pseudo-namespaces with underscore separator which are still common in extensions.

Configuring Logging for extensions works the same. If an extension uses namespaces, the syntax for the configuration is as above.

For older extensions, configuration is searched for in $GLOBALS['TYPO3_CONF_VARS']['LOG']['tx'] or $GLOBALS['TYPO3_CONF_VARS']['LOG']['Tx'] to differentiate extension classes from Core classes (as extension class names start with tx or Tx).

Writer configuration

The Log Writer configuration is read from the subkey writerConfiguration of the configuration array:

$GLOBALS['TYPO3_CONF_VARS']['LOG']['writerConfiguration'] = array(
    // configuration for ERROR level log entries
  \TYPO3\CMS\Core\Log\LogLevel::ERROR => array(
      // add a FileWriter
    'TYPO3\\CMS\\Core\\Log\\Writer\\FileWriter' => array(
        // configuration for the writer
      'logFile' => 'typo3temp/logs/typo3_7ac500bce5.log'

The above configuration applies to all log entries of level "ERROR" or above.

To apply a special configuration for the controllers of the examples extension, use the following configuration:

$GLOBALS['TYPO3_CONF_VARS']['LOG']['Documentation']['Examples']['Controller']['writerConfiguration'] = array(
   // configuration for WARNING severity, including all
   // levels with higher severity (ERROR, CRITICAL, EMERGENCY)
    \TYPO3\CMS\Core\Log\LogLevel::WARNING => array(
     // add a SyslogWriter
        'TYPO3\\CMS\\Core\\Log\\Writer\\SyslogWriter' => array(),

This overwrites the default configuration shown in the first example for classes located in the namespace \Documentation\Examples\Controller.

For extension "foo" with key "tx_foo" (not using namespaces), the configuration would be located at:

$GLOBALS['TYPO3_CONF_VARS']['LOG']['Tx']['Foo']['writerConfiguration'] = array(
   // ...

An arbitrary number of writers can be added for every severity level (INFO, WARNING, ERROR, ...). The configuration based on severity levels is applied to log entries of the particular severity level plus all levels with a higher severity. Thus, a log messages created with $logger->warning() will be affected by a writerConfiguration for \TYPO3\CMS\Core\Log\LogLevel::DEBUG, \TYPO3\CMS\Core\Log\LogLevel::INFO, \TYPO3\CMS\Core\Log\LogLevel::NOTICE and \TYPO3\CMS\Core\Log\LogLevel::WARNING. For the above example code that means:

  • Calling $logger->warning($msg); will result in $msg being written to the computer's syslog on top of the default configuration.
  • Calling $logger->debug($msg); will result in $msg being written only to the default log file (typo3temp/logs/typo3.log).

For a list of writers shipped with the TYPO3 Core see the section about Log Writers.

Processor configuration

Similar to the writer configuration, log record processors can be configured on a per-class and per-namespace basis from the subkey processorConfiguration

$GLOBALS['TYPO3_CONF_VARS']['LOG']['Documentation']['Examples']['Controller']['processorConfiguration'] = array(
    // configuration for ERROR level log entries
  \TYPO3\CMS\Core\Log\LogLevel::ERROR => array(
      // add a MemoyUsageProcessor
    'TYPO3\\CMS\\Core\\Log\\Processor\\MemoryUsage' => array(
      'formatSize' => TRUE

For a list of processors shipped with the TYPO3 Core, see the section about Log Processors.

The LogRecord model

All logging data is modeled using \TYPO3\CMS\Core\Log\LogRecord.

This model has the following properties:

A unique identifier for each request which is created by the TYPO3 bootstrap.
The micro-timestamp when the record is created.
The name of the logger which created the LogRecord, usually the fully qualified class name where the Logger has been instanciated.
An integer severity level from \TYPO3\CMS\Core\Log\LogLevel.
The log message string.
Any additional data, encapsulated within an array.

The API to create a new instance of LogRecord is \TYPO3\CMS\Core\Log\Logger:log() or one of the shorthand methods.

LogRecord implements the ArrayAccess interface so that the properties can be accessed like a native array, for example: $logRecord['requestId']. It also implements a __toString() method for your convenience, which returns the log records as a simplified string.

A LogRecord can be processed using LogProcessors or LogWriters. LogProcessors are meant to add values to the data property of LogRecord. For example, if you would like to add a stack trace, use \TYPO3\CMS\Core\Log\Processor\IntrospectionProcessor.

LogWriters are used to write a LogRecord to a particular target, for example a log file.

Log Writers

The purpose of a log writer is (usually) to save all log records into a persistent storage, like a log file, a database table, or to a remote syslog server.

Different log writers offer possibilities to log into different targets. Custom log writers can extend the functionality shipped with TYPO3 core.

Built-in Log Writers

This section describes the log writers shipped with the TYPO3 core. Some writers have options to allow customization of the particular writer. See the Configuration section for how to use these options.


The database writer logs into a database table. This table has to reside in the database used by TYPO3 and is not automatically created.

Option Mandatory Description Default
logTable no Database table sys_log


The Admin Tools > Log module is not adapted to the records written by the DatabaseWriter into the sys_log table. If you write such records there, you will not be able to see them using that module.

Tip: There's a tool for viewing such records in the TYPO3 backend at github.com/vertexvaar.

Example of a CREATE TABLE statement for logTable:

# Table structure for table 'tx_myextname_log'
# The KEY on request_id is optional
CREATE TABLE tx_myextname_log (
        request_id varchar(13) DEFAULT '' NOT NULL,
        time_micro double(16,4) NOT NULL default '0.0000',
        component varchar(255) DEFAULT '' NOT NULL,
        level tinyint(1) unsigned DEFAULT '0' NOT NULL,
        message text,
        data text,

        KEY request (request_id)

The file writer logs into a log file, one log record per line. If the log file does not exist, it will be created (including parent directories, if needed). Please make sure that your web server has write-permissions to that path and it is below the root directory of your web site (defined by PATH_site). The filename is appended with a hash, that depends on the encryption key. If $GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess'] is set, an .htaccess file is added to the directory. It protects your log files from being accessed from the web.

Option Mandatory Description Default
logFile no Path to log file typo3temp/logs/typo3_7ac500bce5.log

Logs into the PHP error log using error_log()


Logs into the syslog (Unix only).

Option Mandatory Description Default
facility no Syslog Facility to log into. USER
Custom Log Writers

Custom log writers can be added through extensions. Every log writer has to implement the interface \TYPO3\CMS\Core\Log\Writer\WriterInterface. It is suggested to extend the abstract class \TYPO3\CMS\Core\Log\Writer\AbstractWriter which allows you use configuration options by adding the corresponding properties and setter methods.

Please keep in mind that TYPO3 will silently continue operating, in case a log writer is throwing an exception while executing the writeLog() method. Only in the case that all registered writers fail, the log entry plus additional information will be added to the configured fallback logger (which defaults to the PhpErrorLog writer).

Log Processors

The purpose of a log processor is (usually) to modify a log record or add more detailed information to it.

Log processors allow to manipulate log records without changing the code where the log method actually is called (inversion of control). This enables you to add any information from outside the scope of the actual calling function, for example webserver environment variables. The TYPO3 core ships some basic log processors, but more can be added with extensions.

Built-in Log Processors

This section describes the log processors shipped with the TYPO3 core. Some processors have options to allow customization of the particular processor. See the Configuration section for how to use these options.


The introspection processor adds backtrace data about where the log event was triggered.

By default the following parameters from the original function call are added:

absolute path to the file.
line number.
class name.
function name.

If appendFullBackTrace is set, the full backstrace stack is added instead.

Option Mandatory Description Default
appendFullBackTrace no Adds a full backtrace stack to the log. TRUE
shiftBackTraceLevel no Removes the given number of entries from the top of the backtrace stack. 0

The memory usage processor adds the amount of used memory to the log record (result from memory_get_usage()).

Option Mandatory Description Default
realMemoryUsage no Use real size of memory allocated from system instead of emalloc() value. TRUE
formatSize no Whether the size is formatted with GeneralUtility::formatSize() TRUE

The memory peak usage processor adds the peak amount of used memory to the log record (result from memory_get_peak_usage()).

Option Mandatory Description Default
realMemoryUsage no Use real size of memory allocated from system instead of emalloc() value. TRUE
formatSize no Whether the size is formatted with GeneralUtility::formatSize() TRUE

The web processor adds selected webserver environment variables to the log record, i.e. all possible values from \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('_ARRAY').

Custom Log Processors

Custom log processors can be added through extensions. Every log processor has to implement the interface \TYPO3\CMS\Core\Log\Processor\ProcessorInterface. It is suggested to extend the abstract class \TYPO3\CMS\Core\Log\Processor\AbstractProcessor which allows you use configuration options by adding the corresponding properties and setter methods.

Please keep in mind that TYPO3 will silently continue operating, in case a log processor is throwing an exception while executing the processLogRecord() method.

Using the system registry

The purpose of the registry (introduced in TYPO3 4.3) is to hold key- value pairs of information. You can actually think of it being an equivalent to the Windows registry (just not as complicated).

You might use the registry to store information that your script needs to store across sessions or request.

An example would be a setting that needs to be altered by a PHP script, which currently is not possible with TypoScript.

Another example: The scheduler system extension stores when it ran the last time. The reports system extension then checks that value, in case it determines that the scheduler hasn't run for a while it issues a warning. While this might not be of great use to anyone with an actual cron job set up for the scheduler, it is of use for users that have to run the scheduler tasks by hand due to missing access to a cron job.

The registry is not meant to store things that are supposed to go into a session or a cache, use the appropriate API for these instead.

The registry table (sys_registry)

Here's a description of the fields found in the sys_registry table:

Field Type Description
uid int Primary key, needed for replication and also useful as an index.
entry_namespace varchar (128)

Represents an entry's namespace. In general the namespace is an extension key starting with "tx_", a user script's prefix "user_", or "core" for entries that belong to the core.

The point of namespaces is that entries with the same key can exist inside different namespaces.

entry_key varchar (255) The entry's key. Together with the namespace the key is unique for the whole table. The key can be any string to identify the entry. It's recommended to use dots as dividers if necessary. This way the naming is similar to the already known syntax in TypoScript.
entry_value blob The entry's actual value. The value is stored as a serialized string, thus you can even store arrays or objects in a registry entry – it's not recommended though. Using phpMyAdmin's Show BLOB option you can check the value in that field although being stored as a binary.

The registry API

To use the registry, there's an easy to use API. Simply use the code below to retrieve an instance of the registry. The instance returned will always be the same as the registry is a singleton:

$registry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');

After retrieving an instance of the registry you can access the registry values through its get() method. The get() method offers an interesting third parameter to specify a default value, that value is returned in case the requested entry was not found in the registry. That happens when accessing an entry for the first time for example. Setting a value is easy as well using the set() method.

Method Parameters Description

$namespace : namespace in which to set the value

$key : the key of the value to set

$value : the value to store

Represents an entry's namespace. In general the namespace is an extension key starting with "tx_", a user script's prefix "user_", or "core" for entries that belong to the core.

$namespace : namespace to get the value from

$key : the key of the value to retrieve

$defaultValue : a default value if the key was not found in the given namespace

Used to get a value from the registry.

$namespace : namespace to remove the value from

$key : the key of the value to remove

Remove an entry from a given namespace.
removeAllByNamespace $namespace : namespace to empty Deletes all value for a given namespace.

Note that you should not store binary data into the registry, it's not designed to do that. Use the filesystem instead, if you have such needs.


Here's an example taken from the Scheduler system extension:

$registry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');
$runInformation = array('start' => $GLOBALS['EXEC_TIME'], 'end' => time(), 'type' => $type);
$registry->set('tx_scheduler', 'lastRun', $runInformation);

It is retrieved later using:

$registry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Registry');
$lastRun = $registry->get('tx_scheduler', 'lastRun');

Mail API

Since version 4.5 TYPO3 CMS provides a RFC compliant mailing solution, based on SwiftMailer.


Several settings are available in the Install Tool ("All Configuration") affecting the sending process. The most important one is $GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'], which can take the following values:

$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'mail';
Default and backwards compatible setting. This is the most unreliable option. If you are serious about sending mails, consider using "smtp" or "sendmail".
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'smtp';
Sends messages over SMTP. It can deal with encryption and authentication. Works exactly the same on Windows, Unix and MacOS. Requires a mail server and the following additional settings:
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = '<server:port>';
Mailserver name and port to connect to. Port defaults to "25".
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_encrypt'] = '<transport protocol>';
Connect to the server using the specified transport protocol. Requires openssl library. Usually available: ssl, sslv2, sslv3, tls. Check stream_get_transports().
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_username] = '<username>';
If your SMTP server requires authentication, the username.
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_password] = '<password>';
If your SMTP server requires authentication, the password.


$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'smtp';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_server'] = 'localhost';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_encrypt'] = 'ssl'; // ssl, sslv3, tls
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_username'] = 'johndoe';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_smtp_password'] = 'cooLSecret';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'sendmail';
Sends messages by communicating with a locally installed MTA - such as sendmail. This may require setting the additional option:
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_sendmail_command'] = '<command>';

The command to call to send a mail locally. The default works on most modern UNIX based mail servers (sendmail, postfix, exim).


$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'sendmail';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_sendmail_command'] = '/usr/sbin/sendmail -bs';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = 'mbox';
This doesn't send any mail out, but instead will write every outgoing mail to a file adhering to the RFC 4155 mbox format, which is a simple text file where the mails are concatenated. Useful for debugging the mail sending process and on development machines which cannot send mails to the outside. The file to write to is defined by:
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport_mbox_file'] = '<abs/path/to/mbox/file>';
The file where to write the mails into. Path must be absolute.
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['transport'] = '<classname>';
Custom class which implements Swift_Transport. The constructor receives all settings from the MAIL section to make it possible to add custom settings.

How to create and send mails

This shows how to generate and send a mail in TYPO3:

// Create the message
$mail = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage');

// Prepare and send the message

   // Give the message a subject
   ->setSubject('Your subject')

   // Set the From address with an associative array
   ->setFrom(array('john@doe.com' => 'John Doe'))

   // Set the To addresses with an associative array
   ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))

   // Give it a body
   ->setBody('Here is the message itself')

   // And optionally an alternative body
   ->addPart('<q>Here is the message itself</q>', 'text/html')

   // Optionally add any attachments

   // And finally do send it

Or if you prefer, don't concatenate the calls:

$mail = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage');
$mail->setSubject('Your subject');
$mail->setFrom(array('john@doe.com' => 'John Doe'));
$mail->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'));
$mail->setBody('Here is the message itself');
$mail->addPart('<q>Here is the message itself</q>', 'text/html');

How to add attachments

Here is a code sample for attaching a file to mail:

// Create the attachment, the content-type parameter is optional
$attachment = \Swift_Attachment::fromPath('</path/to/image.jpg>', 'image/jpeg');

// Set the filename (optional)

// Attach attachment to message

How to add inline media

Here is how to add some inline media like images in a mail:

// Attach the message with a "cid"
$cid = $mail->embed(\Swift_Image::fromPath('<path/to/image.png>'));

// Create HTML body refering to it
   '<html><head></head><body>' .
   '  Here is an image <img src="' . $cid . '" alt="Image" />' .
   '  Rest of message' .
   ' </body></html>',
   'text/html' //Mark the content-type as HTML

How to set and use a default sender

It is possible to define a default email sender ("From:") in the Install Tool:

$GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'] = 'john@doe.com';
$GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'] = 'John Doe';

This is how you can use these defaults:

$from = \TYPO3\CMS\Core\Utility\MailUtility::getSystemFrom();
$mail = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
// ...

SwiftMailer documentation

Please refer to the SwiftMailer documentation for more information about available methods,

Form protection tool

Since TYPO3 4.5, the TYPO3 Core provides a generic way of protecting forms against Cross-Site Request Forgery (CSRF).

Usage in the backend

For each form in the BE (or link that changes some data), create a token and insert is as a hidden form element. The name of the form element does not matter; you only need it to get the form token for verifying it.

$formToken = TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()
   ->generateToken('BE user setup', 'edit')
$this->content .= '<input type="hidden" name="formToken" value="' . $formToken . '" />';

The three parameters $formName, $action and $formInstanceName can be arbitrary strings, but they should make the form token as specific as possible. For different forms (e.g. BE user setup and editing a tt_content record) or different records (with different UIDs) from the same table, those values should be different.

For editing a tt_content record, the call could look like this:

$formToken = TYPO3\CMS\Core\FormProtection\FormProtectionFactory::get()->generateToken('tt_content', 'edit', $uid);

At the end of the form, you need to persist the tokens. This makes sure that generated tokens get saved, and also that removed tokens stay removed:


In BE lists, it might be necessary to generate hundreds of tokens. So the tokens do not get automatically persisted after creation for performance reasons.

When processing the data that has been submitted by the form, you can check that the form token is valid like this:

  if ($dataHasBeenSubmitted &&
        (string) \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('formToken'),
        'BE user setup', 'edit'
     ) ) {
     // processes the data
  } else {
     // no need to do anything here as the BE form protection will create a
     // flash message for an invalid token

Note that validateToken invalidates the token with the token ID. So calling the validation with the same parameters twice in a row will always return FALSE for the second call.

It is important that the tokens get validated before the tokens are persisted. This makes sure that the tokens that get invalidated by validateToken cannot be used again.

Usage in the Install Tool

For each form in the install tool (or link that changes some data), create a token and insert is as a hidden form element. The name of the form element does not matter; you only need it to get the form token for verifying it.

$formToken = $this->formProtection->generateToken('installToolPassword', 'change');
// then puts the generated form token in a hidden field in the template

The three parameters $formName, $action and $formInstanceName can be arbitrary strings, but they should make the form token as specific as possible. For different forms (e.g. the password change and editing a the configuration), those values should be different.

At the end of the form, you need to persist the tokens. This makes sure that generated tokens get saved, and also that removed tokens stay removed:


When processing the data that has been submitted by the form, you can check that the form token is valid like this:

if ($dataHasBeenSubmitted &&
      (string) $_POST['formToken'],
) {
   // processes the data
} else {
   // no need to do anything here as the install tool form protection will
   // create an error message for an invalid token

Note that validateToken invalidates the token with the token ID. So calling the validation with the same parameters twice in a row will always return FALSE for the second call.

It is important that the tokens get validated before the tokens are persisted. This makes sure that the tokens that get invalidated by validateToken cannot be used again.

Flash messages

There exists a generic system to show users that an action was performed successfully, or more importantly, failed. This system is known as "flash messages". The screenshot below shows the various severity levels of messages that can be emitted.

All levels of flash messages

The "examples" BE module shows one of each type of flash message

The different severity levels are described below:

  • Notifications are used to show very low severity information. Such information usually is so unimportant that it can be left out, unless running in some kind of debug mode.
  • Information messages are to give the user some information that might be good to know.
  • OK messages are to signal a user about a successfully executed action.
  • Warning messages show a user that some action might be dangerous, cause trouble or might have partially failed.
  • Error messages are to signal failed actions, security issues, errors and the like.

Flash messages API

Creating a flash message is achieved by simply instantiating an object of class \TYPO3\CMS\Core\Messaging\FlashMessage:

$message = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage',
   'My message text',
   'Message Header', // the header is optional
   \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING, // the severity is optional as well and defaults to \TYPO3\CMS\Core\Messaging\FlashMessage::OK
   TRUE // optional, whether the message should be stored in the session or only in the \TYPO3\CMS\Core\Messaging\FlashMessageQueue object (default is FALSE)

The severity is defined by using class constants provided by \TYPO3\CMS\Core\Messaging\FlashMessage:

  • \TYPO3\CMS\Core\Messaging\FlashMessage::NOTICE for notifications
  • \TYPO3\CMS\Core\Messaging\FlashMessage::INFO for information messages
  • \TYPO3\CMS\Core\Messaging\FlashMessage::OK for success messages
  • \TYPO3\CMS\Core\Messaging\FlashMessage::WARNING for warnings
  • \TYPO3\CMS\Core\Messaging\FlashMessage::ERROR for errors

The fourth parameter passed to the constructor is a flag that indicates whether the message should be stored in the session or not (the default is not). Storage in the session should be used if you need the message to be still present after a redirection.

In backend modules you can then make that message appear on top of the module after a page refresh or the rendering of the next page request or render it on your own where ever you want.

This example adds the flash message at the top of modules when rendering the next request:

$flashMessageService = $this->objectManager->get(
$messageQueue = $flashMessageService->getMessageQueueByIdentifier();

The message is added to the queue and then the template class calls \TYPO3\CMS\Core\Messaging\FlashMessageQueue::renderFlashMessages() which renders all messages from the queue. Here's how such a message looks like in a module:

A flash message in action

A typical (success) message shown at the top of a module

By default flash messages are shown atop the content of a module. However, if needed, you can change where the messages are shown by manipulating a module's template and inserting the ###FLASHMESSAGES### marker. Messages will then replace that marker instead of appearing at the top of the module.

It is also possible to render a single message directly instead of adding it to the queue. This makes it possible to display flash messages absolutely anywhere. Here's how this is achieved:


Flash messages in Extbase

In Extbase the standard way of issuing flash messages is to add them in the controller. Code from the "examples" extension:

$this->addFlashMessage('This is a simple success message');

The full API of this function is:

   $messageTitle = '',
   $severity = \TYPO3\CMS\Core\Messaging\AbstractMessage::OK,
   $storeInSession = TRUE

The messages are then displayed by Fluid with the relevant View Helper as shown in this excerpt of EXT:examples/Resources/Private/Layouts/Module.html:

<div id="typo3-docbody">
   <div id="typo3-inner-docbody">
      <f:flashMessages renderMode="div" />
      <f:render section="main" />

Where to display the flash messages in an Extbase-based BE module is as simple as moving the View Helper around.

JavaScript-based flash messages

Flash messages can also be fired up from JavaScript, coming up as small pop up windows. Here is sample code taken from the "examples" extension:


The last parameter is the duration (in seconds) after which the message should fade out.

System categories

Since version 6.0, TYPO3 CMS provides a generic categorization system. Categories can be created in the backend like any other type of record. Any table can be made categorizable and thus be attached to system categories.

Since version 6.2, pages, content elements and files are categorizable by default.

Using categories

Managing categories

System categories are defined just like any other record. Each category can have a parent, making for a tree-like structure.

Editing a category

A category with a parent defined

The "items" tab shows all related records, i.e. all records that have been marked as belonging to this category.

Making a table categorizable

There are two ways to activate categories on a given table. The first one is to use the global setting $GLOBALS['TYPO3_CONF_VARS']['SYS']['defaultCategorizedTables']. It is a comma-separated list of tables for which categories should be activated. The default value is pages,tt_content,sys_file_metadata.


It is recommended to avoid changing this setting. You should rather use the API described just below so as to avoid overriding a default which may change in future versions of TYPO3 CMS. The API is also more powerful.

The second way is to call \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::makeCategorizable(). This method adds a new entry into the registry managed by \TYPO3\CMS\Core\Category\CategoryRegistry. The registry will take care of adding the relevant $TCA definition to create a field for making relations to the system categories. The call to makeCategorizable() must be located in an extension's ext_tables.php file or (since TYPO3 CMS 6.2.1) in a file from the Configuration/TCA/Overrides folder.

The default $GLOBALS['TCA'] structure provided by the registry can be overridden by an array options passed to makeCategorizable(). The example below illustrates how this is done:

// Add an extra categories selection field to the pages table
   // Do not use the default field name ("categories"), which is already used
      // Set a custom label
      'label' => 'LLL:EXT:examples/Resources/Private/Language/locallang.xlf:additional_categories',
      // This field should not be an exclude-field
      'exclude' => FALSE,
      // Override generic configuration, e.g. sort by title rather than by sorting
      'fieldConfiguration' => array(
         'foreign_table_where' => ' AND sys_category.sys_language_uid IN (-1, 0) ORDER BY sys_category.title ASC',
      // string (keyword), see TCA reference for details
      'l10n_mode' => 'exclude',
      // list of keywords, see TCA reference for details
      'l10n_display' => 'hideDiff',

The above code will add a categories field to the "pages" table, which will be called tx_examples_cats. The fieldConfiguration part of the options array is the one which overrides the base $TCA structure. In this case we would like categories to be listed alphabetically instead of using the "sorting" field.

If no label part is set in the options array, the field will be labelled "Categories".

By default, the field will be an exclude-field. The exclude part can be used to override this.

This is the result of the above code:

The new categories-relation field

The newly added field to define relations to categories (on top of the default one)

Using categories in flexforms

It is possible to create relations to categories also in Flexforms, although this has to be done manually since no API exists for this.

The code will look something like:

      <foreign_table_where> AND sys_category.sys_language_uid IN (-1, 0) ORDER BY sys_category.sorting ASC</foreign_table_where>

Properties tablenames and fieldname would need to be adjusted.

System categories API

Beyond makeCategorizable(), class \TYPO3\CMS\Core\Category\CategoryRegistry has many other methods related to the management of categorized table. The best way to discover is to follow the link above and explore the methods provided by this class. They are all quite specialized and should not be needed most of the time.

Category collections

The \TYPO3\CMS\Core\Category\Collection\CategoryCollection classe provides the API for retrieving records related to a given category. Since TYPO3 CMS 6.2, it is extended by class \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection which does the same job but in the frontend, i.e. respecting all enable fields and performing version and language overlays.

The main method is load() which will return a traversable list of items related to the given category. Here is an example usage, taken from the RECORDS content object:

$collection = \TYPO3\CMS\Frontend\Category\Collection\CategoryCollection::load(
if ($collection->count() > 0) {
   // Add items to the collection of records for the current table
   foreach ($collection as $item) {
      $tableRecords[$item['uid']] = $item;
      // Keep track of all categories a given item belongs to
      if (!isset($categoriesPerRecord[$item['uid']])) {
         $categoriesPerRecord[$item['uid']] = array();
      $categoriesPerRecord[$item['uid']][] = $aCategory;

As all collection classes in the TYPO3 CMS Core implement the Iterator interface, it is also possible to use expected methods like next(), rewind(), etc. Note that methods such as add() will only add items to the collection temporarily. The relations are not persisted in the database.

Usage with TypoScript

(since TYPO3 CMS 6.2)

In the frontend, it is possible to get collections of categorized records loaded into a RECORDS content object for rendering. Check out the categories property.

The HMENU object also has a "categories" special type, to display a menu based on categorized pages.

System collections

Since TYPO3 CMS 4.7 there exists a generic interface for managing collections of records. The File Abstraction Layer itself extends this mechanism for managing file collections.

Record collections

Record collections are managed via the TYPO3 backend. They can only be created in Folders.

A record collection

A collection of news items


In the above image, the third record in the collection is actually a page. This will be ignored in all process using the Collections API, because the chosen table prevails.

Record collections are stored in the "sys_collection" table. The selected records are stored in the "sys_collection_entries" table.

Record collections can also have a type. Only the "static" type exits so far. Others might be added in the future.

File collections

File collections are similar in principle, but reference files. They are used by the "File links" (download) content element.

A file links content element

A "File links" content element referencing a file collection

File collections are stored in the "sys_file_collection" table. The selected files are stored in the "sys_file_reference" table.

Note that a file collection may also reference a folder, in which case all files inside the folder will be returned when calling that collection.

A folder collection

A file collection referencing a folder

Collections API

The TYPO3 CMS Core provides an API to enable usage of collections inside extensions. The most important classes are:

Used to retrieve collections. It's not exactly an Extbase repository but functions in a similar way. The default "find" methods refer to the "sys_collection" table and will fetch "static"-type collections. Method findByTypeAndTableName() makes it possible to fetch collections from other tables and of a different type.
This class models the static record collection. It is important to note that collections returned by the repository (described above) are "empty". If you need to access their records, you need to load them first, using method loadContents(). On top of some specific API methods, this class includes all setters and getters that you may need to access the collection's data. For accessing the selected records, just loop on the collection (see example).
Similar to the record collection repository, but for file collections.
Similar to the static record collection, but for files.
Again similar, but for file collections based on a folder.


The "examples" extension provides a simple frontend plugin to demonstrate usage of collections. Here is what happens in the controller:

 * Renders the list of all existing collections and their content
 * @return void
public function indexAction() {
   // Get all existing collections
   /** @var \TYPO3\CMS\Core\Collection\AbstractRecordCollection $collections */
   $collections = $this->collectionRepository->findAll();

   // Load the records in each collection
   /** @var \TYPO3\CMS\Core\Collection\StaticRecordCollection $aCollection */
   foreach ($collections as $aCollection) {

   // Assign the "loaded" collections to the view
   $this->view->assign('collections', $collections);

The base is code is quite simple: all collections are fetched and passed to the view. The one specific step is the loop over all collections to load their referenced records. Remember that a collection is otherwise "empty".

In the view we can then either use collection member variables as usual (like their title) or put them directly in a loop to iterate over the record selection:

<f:section name="main">
      <f:for each="{collections}" as="collection">
            {collection.title} (Records from <code>{collection.itemTableName}</code>)
               <f:for each="{collection}" as="record">

Note that the above code is a bit rough as it assumes that each record has a "title" property. This may of course not be the case and proper checks should be made.

Here is what the result may look like (the exact result will obviously depend on the content of the selection):

Collections plugin output

Typical output from the "Collections" plugin of extension "examples"


Since version 6.2 the TYPO3 CMS core includes an enumeration implementation. An enumeration should be used if you have a fixed list of values and it should always be prefered to just pure constants. That is because constants cannot be deprecated but values of an enumeration can.

How To Use Enumerations

Create an Enumeration

To create a new enumeration you have to extend the class TYPO3\CMS\Core\Type\Enumeration. Values are defined as constants in your implementation. The names of the constants must be given in uppercase.

A special, optional constant __default represents the default value of your enumeration if it is present. In that case the enumeration can be instantiated without a value and will be set to the default.


class LikeWildcard extends \TYPO3\CMS\Core\Type\Enumeration
   const __default = self::BOTH;

   /** @var int Do not use any wildcard */
   const NONE = 0;

   /** @var int Use wildcard on left side */
   const LEFT = 1;

   /** @var int Use wildcard on right side */
   const RIGHT = 2;

   /** @var int Use wildcard on both sides */
   const BOTH = 3;
Use an Enumeration

You always have to use the Enumeration::cast() method for instantiation.

((The ::cast() itself calls the constructor of the enumeration if it's not always an instance of the enum.))((make sentence clearer!))

That allows to deprecate enumeration values or do special value casts before finding a suitable value in the enumeration.


$likeWildcardLeft = LikeWildcard::cast(LikeWildcard::LEFT);

$valueFromDatabase = 1;

// will cast the value automatically to an enumeration.
// Result is true.

$enumerationWithValueFromDb = LikeWildcard::cast($valueFromDatabase);

// Remember to always use ::cast and never use the constant directly

If the enumeration is instantiated with an invalid value an TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException is thrown. This exception must be catched and you have to decide what the appropriate behavior should be.


Always be prepared to handle exceptions when instantiating enumerations from user defined values!


try {
   $foo = LikeWildcard::cast($valueFromPageTs);
} catch (\TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException) {
   $foo = LikeWildcard::cast(LikeWildcard::NONE);
Implement custom logic

Sometimes it not only makes sense to validate a value but to also have custom logic as well..

For example, the TYPO3\CMS\Core\Versioning\VersionState enumeration contains values of version states. Some of the values indicate that the state is a "placeholder". This logic can be implemented by a custom method:

class VersionState extends \TYPO3\CMS\Core\Type\Enumeration
   const __default = self::DEFAULT_STATE;
   const DEFAULT_STATE = 0;
   const NEW_PLACEHOLDER = 1;
   const MOVE_PLACEHOLDER = 3;
   const MOVE_POINTER = 4;

    * @return bool
   public function indicatesPlaceholder()
      return (int)$this->__toString() > self::DEFAULT_STATE;

$myVersionState = VersionState::cast($versionStateValue);
if ($myVersionState->indicatesPlaceholder()) {
   echo 'The state indicates that this is a placeholder';

HTTP request library

Since TYPO3 CMS 4.6, a library for easily making HTTP requests is available. It is actually a wrapper around the HTTP_Request2 PEAR package, which is shipped with the Core.

Basic usage

The basic usage is as simple as it gets:

$request = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
$result = $request->send();
$content = $result->getBody();

The above example will read the content of the "typo3.org" home page.


This example is taken from the "linkvalidator" system extension.

$config = array(
   'follow_redirects' => TRUE,
   'strict_redirects' => TRUE
/** @var \TYPO3\CMS\Core\Http\HttpRequest|\HTTP_Request2 $request */
$request = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
// Observe cookies
try {
   /** @var \HTTP_Request2_Response $response */
   $response = $request->send();
   // HEAD was not allowed, now trying GET
   if (isset($response) && $response->getStatus() === 405) {
      $request->setHeader('Range', 'bytes = 0 - 4048');
      /** @var \HTTP_Request2_Response $response */
      $response = $request->send();
} catch (\Exception $e) {
   $isValidUrl = FALSE;
   // A redirect loop occurred
   if ($e->getCode() === 40) {
      // Parse the exception for more information
      $trace = $e->getTrace();
      $traceUrl = $trace[0]['args'][0]->getUrl()->getUrl();
      $traceCode = $trace[0]['args'][1]->getStatus();
      $errorParams['errorType'] = 'loop';
      $errorParams['location'] = $traceUrl;
      $errorParams['errorCode'] = $traceCode;
   } else {
      $errorParams['errorType'] = 'exception';
   $errorParams['message'] = $e->getMessage();

This is the code that checks external links. To keep the traffic low, it first checks only with HEAD. In case the server does not allow this, the check is retried with GET. In such a case, we get only the first 4 kilobytes, as we don't really care about the content itself.

It may happen that the check gets stuck in a redirect loop, in which case an exception is thrown. This particular case it tested for in the catch block above to better inform the user about what happened.


The concept of "hooks"

Hooks are basically places in the source code where a user function will be called for processing if such has been configured. Hooks provide an easy way to extend the functionality of TYPO3 and its extensions without blocking others to do the same.

Hooks vs. XCLASS extensions

Hooks are the recommended way of extending TYPO3 compared to extending PHP classes with a child class (see "XCLASS extensions"). Because only one extension of a PHP class can exist at a time while hooks may allow many different user-designed processor functions to be executed. However, hooks have to be implemented in the TYPO3 core before you can use them, while extending a PHP class via the XCLASS method allows you to extend any class you like.

Proposing hooks

If you need to extend something which has no hook yet, then you should suggest implementing a hook. Normally that is rather easily done by the author of the source you want to extend.

Using hooks

The two lines of code below are an example of how a hook is used for clear-cache post-processing. The objective of this could be to perform additional actions whenever the cache is cleared for a specific page.

$TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'][] = 'myext_cacheProc->proc';

This registers the class/method name to a hook inside of \TYPO3\CMS\Core\DataHandling\DataHandler. The hook will call the user function after the clear-cache command has been executed. The user function will receive parameters which allows it to see what clear-cache action was performed and typically also an object reference to the parent object. Then the user function can take additional actions as needed.

The class has to be declared with the TYPO3 autoloader.

If we take a look inside of \TYPO3\CMS\Core\DataHandling\DataHandler we find the hook to be activated like this:

    // Call post processing function for clear-cache:
if (is_array($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'])) {
    $_params = array('cacheCmd' => $cacheCmd);
    foreach($TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'] as $_funcRef) {
        \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($_funcRef, $_params, $this);

This is how hooks are typically constructed. The main action happens in line 5 where the function \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction() is called. The user function is called with two arguments, an array with variable parameters and the parent object.

In line 3 the contents of the parameter array is prepared. This is of high interest to you because this is where you see what data is passed to you and what data might possibly be passed by reference and thereby possible to manipulate from your hook function.

Finally, notice how the array $TYPO3_CONF_VARS['SC_OPTIONS']['t3lib/class.t3lib\_tcemain.php']['clearCachePostProc'] is traversed and for each entry the value is expected to be a function reference which will be called. This allows many hooks to be called at the same place. The hooks can even rearrange the calling order if they dare.

The syntax of a function reference (or object reference if \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj is used in the hook instead) can be seen in the API documentation of \TYPO3\CMS\Core\Utility\GeneralUtility.


The example hook shown above refers to old class names. All these old class names were left in hooks, for obvious reasons of backwards-compatibility.

Hook configuration

There is no complete index of hooks in the core. But they are easy to search for and find. And typically it comes quite naturally since you will find the hooks in the code you want to extend - if they exist.

This index will list the main variable spaces for configuration of hooks. By the names of these you can easily scan the source code to find which hooks are available or might be interesting for you.

The index below also includes some variable spaces which do not only carry hook configuration but might be used for other purposes as well.


Configuration space for extensions.

This will contain all kinds of configuration options for specific extensions including possible hooks in them! What options are available to you will depend on a search in the documentation for that particular extension.

$TYPO3_CONF_VARS['EXTCONF'][ extension_key ][ sub_key ] = value
  • extension_key : The unique extension key
  • sub_key : Whatever the script defines. Typically it identifies the context of the hook
  • value : It is up to the extension what the values mean, if they are mere configuration options or hooks or whatever and how deep the arrays go. Read the source code where the options are implemented to see. Or the documentation of the extension, if available.


$TYPO3_CONF_VARS['EXTCONF'] is the recommended place to put hook configuration that are available inside your extensions!

This example shows hooks used in the "linkvalidator" system extension. The code looks inside the $TYPO3_CONF_VARS['EXTCONF'] array for items listed under the "checkLinks" key of the "linkvalidator" extension itself. All found classes are stored in an array, to be instantiated and used at a later point.

 * Fill hookObjectsArr with different link types and possible XClasses.
public function __construct() {
      // Hook to handle own checks
   if (is_array($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'])) {
      foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['linkvalidator']['checkLinks'] as $key => $classRef) {
         $this->hookObjectsArr[$key] = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($classRef);

Configuration space for core scripts.

This array is created as an ad hoc space for creating hooks from any script. This will typically be used from the core scripts of TYPO3 which do not have a natural identifier like extensions have their extension keys.

$TYPO3_CONF_VARS['SC_OPTIONS'][ main_key ][ sub_key ][ index ] = function_reference
  • main_key : The relative path of a script (for output scripts it should be the "script ID" as found in a comment in the HTML header )
  • sub_key : Whatever the script defines. Typically it identifies the context of the hook.
  • index : Integer index typically. Can be unique string if you have a reason to use that. Normally it has no greater significance since the value of the key is not used. The hooks normally traverse over the array and uses only the value (function reference)
  • function_reference : A function reference using the syntax of \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction() or \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj() depending on implementation of the hook.

The above syntax is how a hook is typically defined but it might differ and it might not be a hook at all, but just configuration. Depends on implementation in any case.

The following example shows a hook from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController. In this case the function \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj() is used for the hook. The function_reference is referring to the class name only since the function returns an object instance of that class. The method name to call is predefined by the hook, in this case sendFormmail_preProcessVariables(). This method allows to pass any number of variables along instead of the limited $params and $pObj variables from \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction().

    // Hook for preprocessing of the content for formmails:
if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['sendFormmail-PreProcClass'])) {
    foreach($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['sendFormmail-PreProcClass'] as $_classRef) {
        $_procObj = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($_classRef);
        $EMAIL_VARS = $_procObj->sendFormmail_preProcessVariables($EMAIL_VARS, $this);

In this example we are looking at a special hook, namely the one for RTE transformations. It is not a "hook" in the strict sense, but the same principles are used. In this case the "index" key is defined to be the transformation key name, not a random integer since we do not iterate over the array as usual. \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj() is also used.

if ($_classRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_parsehtml_proc.php']['transformation'][$cmd]) {
    $_procObj = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($_classRef);
    $_procObj->pObj = &$this;
    $_procObj->transformationKey = $cmd;
    $value = $_procObj->transform_db($value, $this);

A classic hook also from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController. This one is based on \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction() and it passes a reference to $this along to the function via $_params. In the user-defined function $_params['pObj']->content is meant to be manipulated in some way. The return value is insignificant - everything works by the reference to the parent object.

    // Hook for post-processing of page content cached/non-cached:
if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'])) {
    $_params = array('pObj' => &$this);
    foreach($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['contentPostProc-all'] as $_funcRef) {
        \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($_funcRef, $_params, $this);

** Configuration space for backend modules.**

Among these configuration options you might find entry points for hooks in the backend. This somehow overlaps the intention of SC_OPTIONS above but this array is an older invention and slightly outdated.

$TBE_MODULES_EXT[ backend_module_key ][ sub_key ] = value
  • backend_module_key : The backend module key for which the configuration is used.
  • sub_key : Whatever the backend module defines.
  • value : Whatever the backend module defines.

The following example shows TBE_MODULES_EXT being used for adding items to the Context Sensitive Menus (Clickmenu) in the backend. The hook value is an array with a key pointing to a file reference to class file to include. Later each class is instantiated and a fixed method inside is called to do processing on the array of menu items. This kind of hook is non-standard in the way it is made.


The API for registering context-sensitive menus was changed completely in TYPO3 4.5.

    // Setting internal array of classes for extending the clickmenu:
$this->extClassArray = $GLOBALS['TBE_MODULES_EXT']['xMOD_alt_clickmenu']['extendCMclasses'];

    // Traversing that array and setting files for inclusion:
if (is_array($this->extClassArray)) {
    foreach($this->extClassArray as $extClassConf) {
        if ($extClassConf['path'])    $this->include_once[]=$extClassConf['path'];

The following code listings works in the same way. First, a list of class files to include is registered. Then in the second code listing the same array is traversed and each class is instantiated and a fixed function name is called for processing.

    // Setting class files to include:
if (is_array($TBE_MODULES_EXT['xMOD_db_new_content_el']['addElClasses'])) {
    $this->include_once = array_merge($this->include_once,$TBE_MODULES_EXT['xMOD_db_new_content_el']['addElClasses']);

    // PLUG-INS:
if (is_array($TBE_MODULES_EXT['xMOD_db_new_content_el']['addElClasses'])) {
    while(list($class,$path)=each($TBE_MODULES_EXT['xMOD_db_new_content_el']['addElClasses'])) {
        $modObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance($class);
        $wizardItems = $modObj->proc($wizardItems);

Creating hooks

You are encouraged to create hooks in your extensions if they seem meaningful. Typically someone would request a hook somewhere. Before you implement it, consider if it is the right place to put it etc. On the one hand we want to have many hooks but not more than needed. Redundant hooks or hooks which are implemented in the wrong context is just confusing. So put a little thought into it first, but be generous.

There are two main methods of calling a user defined function in TYPO3.

  • \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction() - The classic way. Takes a file/class/method reference as value and calls that function. The argument list is fixed to a parameter array and a parent object. So this is the limitation. The freedom is that the reference defines the function name to call. This method is mostly useful for small-scale hooks in the sources.
  • \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObject() - Create an object from a user defined file/class. The method called in the object is fixed by the hook, so this is the non-flexible part. But it is cleaner in other ways, in particular that you can even call many methods in the object and you can pass an arbitrary argument list which makes the API more beautiful. You can also define the objects to be singletons, instantiated only once in the global scope.

Here follows some examples.

Using TYPO3CMSCoreUtilityGeneralUtility::getUserObj()
    // Hook for pre-processing of the content for formmails:
if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['sendFormmail-PreProcClass'])) {
    foreach($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['sendFormmail-PreProcClass'] as $_classRef) {
        $_procObj = \TYPO3\CMS\Core\Utility\GeneralUtility::getUserObj($_classRef);
        $EMAIL_VARS = $_procObj->sendFormmail_preProcessVariables($EMAIL_VARS, $this);
Using with TYPO3CMSCoreUtilityGeneralUtility::callUserFunction()
    // Call post-processing function for constructor:
if (is_array($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'])) {
    $_params = array('pObj' => &$this);
    foreach($this->TYPO3_CONF_VARS['SC_OPTIONS']['tslib/class.tslib_fe.php']['tslib_fe-PostProc'] as $_funcRef) {
        \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($_funcRef,$_params, $this);

Extending classes (XCLASSes)


XCLASSing is a mechanism in TYPO3 CMS to extend classes or overwrite methods from the Core or extensions with one's own code. This enables a developer to easily change a given functionality, if other options like hooks, signals or the extbase dependency injection mechanisms do not work or do not exist.

However there are several limitations.


If you need a hook or a signal that does not exist, feel free to submit a feature request and - even better - a patch. Consult the TYPO3 Contribution Guide about how to do this.

How does it work?

In general every class instance in the Core and in extensions that stick to the recommended TYPO3 coding guidelines is created with the API call \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(). The methods takes care of singletons and also searches for existing XCLASSes. If there is an XCLASS registered for the specific class that should be instantiated, an instance of that XCLASS is returned instead of an instance of the original class.


  • Using XCLASSes is risky: neither the core, nor extensions authors can guarantee that XCLASSes will not break if the underlying code changes (for example during upgrades). Be aware that your XCLASS can easily break and has to be maintained and fixed if the underlying code changes. If possible, you should use a hook instead of an XCLASS.
  • XCLASSes do not work for static classes, static methods or final classes.
  • There can be only one XCLASS per base class, but an XCLASS can be XCLASSed again. Be aware that such a construct is even more risky and definitely not advisable.
  • A small number of Core classes are required very early during bootstrap before configuration and other things are loaded. XCLASSing those classes will fail if they are singletons or might have unexpected side-effects.


The $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'] global array acts as a registry of overloaded (XCLASSed) classes.

The syntax is as follows and is commonly located in an extension's ext_localconf.php file:

$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['TYPO3\\CMS\\Backend\\Controller\\NewRecordController'] = array(
   'className' => 'Documentation\\Examples\\Xclass\\NewRecordController'

In this example, we declare that the \TYPO3\CMS\Backend\Controller\NewRecordController class will be overridden by the \Documentation\Examples\Xclass\NewRecordController class, the latter being part of the "examples" extension.


In the above declaration, namespaced classes are entered without the leading backslash.

When XCLASSing a class that does not use namespaces, simply use that class' name in the declaration.


To be compatible with older versions of TYPO3 CMS, you need to also add old-style XCLASS declarations. Please refer to older versions of this document for more information.

Coding practices

The recommended way of writing an XCLASS is to extend the original class and overwrite only the methods where a change is needed. This lowers the chances of the XCLASS breaking after a code update.


You're even safer if you can do your changes before or after the parent method and just call the latter with parent::.

The example below extends the new record wizard screen. It first calls the original method and then adds its own content:

class NewRecordController extends \TYPO3\CMS\Backend\Controller\NewRecordController {
   function regularNew() {
      $this->code .= $this->doc->section(

The result can be seen here:

Adding an element to the new record wizard

A help section is added at the bottom of the new record wizard.

Various examples

This chapter presents some examples of how you can use the APIs of Core libraries. They are not meant to be exhaustive, ultimately the source code is the best documentation. These examples are here to get you started.

The Core itself along with its many system extensions provide another whole lot of examples.


Before diving into the topic here are a few hints about debugging in TYPO3 CMS.

The TYPO3 Core provides a simple debug() (defined in EXT:core/Classes/Core/GlobalDebugFunctions.php). It wraps around \TYPO3\CMS\Core\Utility\DebugUtility::debug() and will output debug information only if it matches a set of IP addresses (defined in $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']).

For example, the following code:

debug($_COOKIE, 'cookie');

will produce such an output:

Debug output

Typical TYPO3 debug output


The debug() function allows for adding your own debugging object. Put an instance of your class in $GLOBALS['error'] and its debug() method, receiving the same parameters as the original debug() function.

In general, look at class \TYPO3\CMS\Core\Utility\DebugUtility for useful debugging tools.


Rendering page trees

In your backend modules you might like to show information or perform processing for a part of the page tree. There is a whole family of libraries in the core for making trees from records, static page trees or page trees that can be browsed (open/close nodes).

This simple example demonstrates how to produce the HTML for a static page tree. The result looks like:

A page tree

A static page tree in TYPO3 Backend

The tree object itself is prepared this way (taken from EXT:examples/Classes/Controller/DefaultController.php):

public function treeAction() {
   // Get page record for tree starting point
   $startingPoint = 1;
   $pageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord(

   // Create and initialize the tree object
   /** @var $tree \TYPO3\CMS\Backend\Tree\View\PageTreeView */
   $tree = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Tree\\View\\PageTreeView');
   $tree->init('AND ' . $GLOBALS['BE_USER']->getPagePermsClause(1));

   // Creating the icon for the current page and add it to the tree
   $html = \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord(
         'title' => $pageRecord['title']
   $tree->tree[] = array(
       'row' => $pageRecord,
       'HTML' => $html

   // Create the page tree, from the starting point, 2 levels deep
   $depth = 2;

   // Pass the tree to the view
  • At the top of the code we define the starting page and get the corresponding page record using \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord().
  • Next we create an instance of \TYPO3\CMS\Backend\Tree\View\PageTreeView, which we use for generating the tree. Notice how the BE_USER object is called to get a SQL where clause that will ensure that only pages that are accessible for the user will be shown in the tree!
  • As a next step we manually add the starting page to the page tree. This is not done automatically because it is not always a desirable behavior. Note the use of \TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconForRecord() to fetch the right icon for the page.
  • Finally we get the tree to prepare itself, up to a certain depth. Internally this will - in particular - generate a HTML part containing the tree elements and the page icon itself.
  • The rendered page tree is stored in a data array inside of the tree object. We need to traverse the tree data to create the tree in HTML. This gives us the chance to organize the tree in a table for instance. It is this part that we pass on to the view.

The result is rendered with a very simple Fluid template:

<f:for each="{tree}" as="page">
   <tr class="db_list_normal">
      <td>{page.depthData -> f:format.raw()}<f:format.raw>{page.HTML}</f:format.raw> {page.row.title}</td>

We do a simple loop on the tree array of pages and display the relevant elements.

Accessing the clipboard

You can easily access the internal clipboard in TYPO3 from your backend modules:

/** @var $clipboard \TYPO3\CMS\Backend\Clipboard\Clipboard */
$clipboard = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Clipboard\\Clipboard');
// Read the clipboard content from the user session

In this simple piece of code we instantiate a clipboard object and make it load its content. We then simply dump this content into the BE module's debug window, with the following result:

Clipboard dump

A dump of the clipboard in the debug window

This tells us what objects are registered on the default tab ("normal") (a content element with id 216 in "copy" mode) and the numeric tabs (which can each contain more than one element). It also tells us that the current tab is number 2. We can compare with the BE view of the clipboard:

Clipboard content

The clipboard as seen in the BE

which indeed contains two files.

Clipboard content should not be accessed directly, but using the elFromTable() method of the clipboard object:

// Access files and pages content of current pad
$currentPad = array(
   'files' => $clipboard->elFromTable('_FILE'),
   'pages' => $clipboard->elFromTable('pages'),

// Switch to normal pad and retrieve files and pages content
$normalPad = array(
   'files' => $clipboard->elFromTable('_FILE'),
   'pages' => $clipboard->elFromTable('pages'),

Here we first try to get all files and then all page records on the current pad (which is pad 2). Then we change to the "Normal" pad, call the elFromTable() method again.

In the "examples" extension, this data is passed to a BE module view for display, which is really just information:

Clipboard items

Display of information about individual clipboard items

Putting elements into the clipboard

This is too complicated to describe in detail. The following codelisting is from the Web > List module where selections for the clipboard are posted from a form and registered:

// Clipboard is initialized:
// Start clipboard
$dblist->clipObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Clipboard\\Clipboard');
// Initialize - reads the clipboard content from the user session
// Clipboard actions are handled:
// CB is the clipboard command array
$CB = \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('CB');
if ($this->cmd == 'setCB') {
   // CBH is all the fields selected for the clipboard, CBC is the checkbox fields which were checked.
   // By merging we get a full array of checked/unchecked elements
   // This is set to the 'el' array of the CB after being parsed so only the table in question is registered.
   $CB['el'] = $dblist->clipObj->cleanUpCBC(array_merge((array) \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('CBH'), (array) \TYPO3\CMS\Core\Utility\GeneralUtility::_POST('CBC')), $this->cmd_table);
if (!$this->MOD_SETTINGS['clipBoard']) {
   // If the clipboard is NOT shown, set the pad to 'normal'.
   $CB['setP'] = 'normal';
// Execute commands.
// Clean up pad
// Save the clipboard content
Context-Sensitive Menus


The context-sensitive menu for the page tree is built using a different approach.

Adding menu items

The context-sensitive menu (CSM) is made of a list of menu items. Before that list is actually rendered into a HTML pop-up menu, it is passed to external processing scripts, which can be configured in the following global array:


Each script can manipulate the list of menu items, in particular add or remove some. Registration example (taken from the ext_tables.php file of the "examples" extension):

$GLOBALS['TBE_MODULES_EXT']['xMOD_alt_clickmenu']['extendCMclasses'][] = array(
   'name' => 'Documentation\\Examples\\Service\\ContextMenuOptions'

The class (\Documentation\Examples\Service\ContextMenuOptions) looks like this:

class ContextMenuOptions {
    * Adds a sample item to the CSM
    * @param \TYPO3\CMS\Backend\ClickMenu\ClickMenu $parentObject Back-reference to the calling object
    * @param array $menuItems Current list of menu items
    * @param string $table Name of the table the clicked on item belongs to
    * @param integer $uid Id of the clicked on item
    * @return array Modified list of menu items
   public function main(\TYPO3\CMS\Backend\ClickMenu\ClickMenu $parentObject, $menuItems, $table, $uid) {
      // Activate the menu item only for "pages" table
      if ($table == 'pages') {
         // URL for the menu item. Point to the page tree example module, passing the page id.
         $baseUrl = 'mod.php?M=tools_ExamplesExamples&tx_examples_tools_examplesexamples%5Baction%5D=tree&tx_examples_tools_examplesexamples%5Bcontroller%5D=Default';
         $baseUrl .= '&tx_examples_tools_examplesexamples%5Bpage%5D=' . $uid;

         // Add new menu item with the following parameters:
         // 1) Label
         // 2) Icon
         // 3) URL
         // 4) = 1 disable item in docheader
         $menuItems[] = $parentObject->linkItem(
      return $menuItems;

Since TYPO3 CMS 6.2, a dedicated API must be used to assemble the destination URL of the context menu item. This adds CSRF protection to the link.

$baseUrl = \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleUrl(
      'tx_examples_tools_examplesexamples[action]' => 'tree',
      'tx_examples_tools_examplesexamples[controller]' => 'Default',
      'tx_examples_tools_examplesexamples[page]' => $uid

The important points to notice are what properties are used for defining a new menu item and what API calls are used (namely \TYPO3\CMS\Backend\ClickMenu\ClickMenu\linkItem() and \TYPO3\CMS\Backend\ClickMenu\ClickMenu\urlRefForCM()). The new menu item now appears when clicking a "pages" record icon in the TYPO3 CMS backend.

Context-sensitive menu in mode Web > List

The new context-sensitive menu item

As an example action, our item points to the "Page tree" demo screen of the "examples" BE module, transferring the id of the page that was clicked. The effect is that this page is used as tree root in the module's display.

Page tree view

The examples Page Tree module, using the selected page as root


In the example class above, a test is performed early on to add the menu item only for a record from the "pages" table. When an element representing a file is clicked on, the "table" parameter will be filled with the file path. The best way to check whether it is a file or not is to simply call is_file() on it.

Implementing a Context Sensitive Menu

Implement a CSM in your own backend modules is quite straightforward. The examples below are taken from the "beuser" system extension and assume that the module is Extbase-based. For old style modules, refer to older versions of this manual.

The first step is to include the needed JavaScript using an argument of the standard BE container Fluid view helper. Actually the CSM-related JavaScript is included by default, so just using:

   // ...

in your Layout is sufficient (see typo3/sysext/beuser/Resources/Private/Layouts/Default.html). Looking under the hood (in class \TYPO3\CMS\Fluid\ViewHelpers\Be\ContainerViewHelper), the relevant JS inclusion is:

if ($enableClickMenu) {

The second step is to activate the click menu on the icons. This kind of markup is required (taken from typo3/sysext/beuser/Resources/Private/Templates/BackendUser/Index.html):

<td class="col-icon">
   <a href="#" onClick="TYPO3.Clickmenu.show('be_users', '{backendUser.uid}', '1', '', '', ''); return false;" title="id={backendUser.uid}">
      <bu:spriteIconForRecord table="be_users" object="{backendUser}" />

the relevant line being highlighted.

The same code works for files. In this case however, the first argument to TYPO3.Clickmenu.show() should be the absolute file path (instead of the table) and the second parameter (the uid) should be left blank.

Parsing HTML

TYPO3 CMS provides its own HTML parsing class: \TYPO3\CMS\Core\Html\HtmlParser. This chapter shows some example uses.

Extracting blocks from an HTML document

The first example shows how to extract parts of a document. Consider the following code:

$testHTML = '
      <IMG src="welcome.gif">
      <p>Line 1</p>
      <p>Line <B class="test">2</B></p>
      <p>Line <b><i>3</i></p>
      <img src="test.gif" />
            <td>Another line here</td>
   <B>Text outside div tag</B>
         <td>Another line here</td>

   // Splitting HTML into blocks defined by <div> and <table> tags
$parseObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Html\\HtmlParser');
$result = $parseObj->splitIntoBlock('div,table', $testHTML);

After loading some dummy HTML code into a variable, we create an instance of \TYPO3\CMS\Core\Html\HtmlParser and ask it to split the HTML structure on "div" and "table" tags. A debug output of the result shows the following:

Debug output of HTML parsing

The HTML parsed into several blocks

As you can see the HTML source has been divided so the "div" section and the "table" section are found in key 1 and 3. Odd key always correspond to the extracted content and even keys to the content outside of the extracted parts.

Notice that the table inside of the "div" section was not "found". When you split content like this you get only elements on the same block-level in the source. You have to traverse the content recursively to find all tables - or just split on <table> only (which will not give you tables nested inside of tables though).

Note also how the HTML parser does not care for case (upper or lower, all tags were found).

Extracting single tags

It is also possible to split by non-block tags, for example "img" and "br":

$result = $parseObj->splitTags('img,br', $testHTML);

with the following result:

Debug output of HTML parsing

The HTML split along some tags

Again, all the odd keys in the array contain the tags that were found. Note how the parser handled transparently simple tags or self-closing tags.

Cleaning HTML content

The HTML parsing class also provides a tool for manipulating HTML with the HTMLcleaner() method. The cleanup configuration is quite extensive. Please refer to the phpDoc comments of the HTMLcleaner() method for more details.

Here is a sample usage:

$tagCfg = array(
   'b' => array(
      'nesting' => 1,
      'remap' => 'strong',
      'allowedAttribs' => 0
   'img' => array(),
   'div' => array(),
   'br' => array(),
   'p' => array(
      'fixAttrib' => array(
         'class' => array(
            'set' => 'bodytext'
$result = $parseObj->HTMLcleaner(
   array('xhtml' => 1)

We first define our cleanup/transformation configuration. We define that only five tags should be kept ("b", "img", "div", "br" and "p"). All others are removed (HTMLcleaner() can be configured to keep all possible tags).

Additionally we indicate that "b" tags should be changed to "strong" and that correct nesting is required (otherwise the tag is removed). Also no attributed are allowed on "b" tags.

For "p" tags we indicate that the "attribute" should be added with value "bodytext".

Lastly - in the call to HTMLcleaner() itself, we request "xhtml" cleanup.

This is the result:

Debug output of cleaned up HTML

The cleaned up HTML code

Advanced processing

There's much more that can be achieved with \TYPO3\CMS\Core\Html\HtmlParser in particular more advanced processing using callback methods that can perform additional work on each parsed element, including calling the HTML parser recursively.

This is too extensive to cover here.

Support for custom tables in the Page module

In the Web > Page module you can have listings of other records than Content Elements. Any table can be displayed by adding to the array $TYPO3_CONF_VARS['EXTCONF']['cms'].

The TYPO3 CMS Core itself defines such a listing for the "fe_users" table:

'EXTCONF' => array(
   'cms' => array(
      'db_layout' => array(
         'addTables' => array(
            'fe_users' => array(
               0 => array(
                  'MENU' => '',
                  'fList' => 'username,usergroup,name,email,telephone,address,zip,city',
                  'icon' => TRUE

as found in typo3/sysext/core/Configuration/DefaultConfiguration.php.

The "fList" key value is a list of field names separated first by comma and then ";" (semi-colon). The comma separates table columns while the semi-colon allows you to list more than one field to be displayed inside a single column.

So placing yourself on a page containing frontend users in the Web > Page module, you will see the following:

Page module with records list

List of FE users in the Web > Page module

Adding elements to the Content Element Wizard

The content element wizard can be fully configured using TSConfig. The API described here should not be used for the purpose of customizing this wizard.

However for extension authors, it provides a way of registering their plugin with the new content element wizard.

Basically it is about adding a class reference to the $GLOBALS['TBE_MODULES_EXT']['xMOD_db_new_content_el']['addElClasses'] global array. The keys in this array are class names and the values are the absolute paths to these class. The class must have a proc() method.

Here is some code from the "examples" extension, to register the "pierror" plugin with the wizard. First of all, the class is declared:

// Add "pierror" plugin to new element wizard
if (TYPO3_MODE == 'BE') {
   $TBE_MODULES_EXT['xMOD_db_new_content_el']['addElClasses']['tx_examples_pierror_wizicon'] = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'pierror/class.tx_examples_pierror_wizicon.php';

The EXT:examples/pierror/class.tx_examples_pierror_wizicon.php file looks like:

class tx_examples_pierror_wizicon {

    * Processing the wizard items array
    * @param array $wizardItems The wizard items
    * @return array Modified array with wizard items
   function proc($wizardItems)   {
      $wizardItems['plugins_tx_examples_pierror'] = array(
         'icon' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('examples') . 'Resources/Public/Images/PiErrorWizard.png',
         'title' => $GLOBALS['LANG']->sL('LLL:EXT:examples/locallang.xlf:pierror_wizard_title'),
         'description' => $GLOBALS['LANG']->sL('LLL:EXT:examples/locallang.xlf:pierror_wizard_description'),
         'params' => '&defVals[tt_content][CType]=list&&defVals[tt_content][list_type]=examples_pierror'

      return $wizardItems;

The proc() method receives the list of existing items in the wizard and adds a new one to it. The first three properties are quite easy to understand. The "params" property defines the default values to be added to the new record link so that the right type of content element (and plugin in this case) is already selected. This uses the syntax demonstrated in the Links to edit records chapter.

The result can be seen in the new content element wizard:

The modified content element wizard

The entry added in the plugin list of the new content element wizard

Using custom permission options

TYPO3 allows extension developers to register their own permission options, managed automatically by the built-in user group access lists. The options can be grouped in categories. A custom permission option is always a checkbox (on/off).

The scope of such options is the backend only.


Options are configured in the global variable $TYPO3_CONF_VARS['BE']['customPermOptions']. The syntax is demonstrated in the following example, which adds three options under a given header:

$GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] = array(
   'tx_examples_cat1' => array(
      'header' => 'LLL:EXT:examples/Resources/Private/Language/locallang.xlf:permissions_header',
      'items' => array(
         'key1' => array(
         'key2' => array('LLL:EXT:examples/Resources/Private/Language/locallang.xlf:permissions_option2'),
         'key3' => array('LLL:EXT:examples/Resources/Private/Language/locallang.xlf:permissions_option3'),

The result is that these options appear in the group access lists like this:

Custom permissions

The custom permissions appear in the Access List tab of backend user groups

As you can see it is possible to add both an icon and a description text, that will be displayed as context-sensitive help.


As this is a rather old API the icon is actually pointed to using a reference to an actual file rather than using a sprite class.


To check if a custom permission option is set simply call the API function from the user object:

$BE_USER->check('custom_options', $catKey . ':' . $itemKey);

$catKey is the category in which the option resides. From the example above this would be tx_examples_cat1.

$itemKey is the key of the item in the category you are evaluating. From the example above this could be key1, key2 or key3 depending on which one of them you want to evaluate.

The function returns true if the option is set, otherwise false.

Keys for options

It is good practice to use the extension keys prefixed with tx_ on the first level of the array to avoid potential conflicts with other custom options.


Never pick a key containing any of the characters ",:\|". They are reserved delimiter characters.

Database Access


Database queries in TYPO3 are done with an API based on doctrine-dbal. The API is provided by the system extension core which is always loaded and thus always available.

Extension authors can use this low-level API to manage query operations directly on the configured DBMS.

Doctrine-dbal is feature rich. Drivers for various target systems enable TYPO3 to run on a long list of ANSI SQL compatible DBMS. If used properly, queries created with this API are translated to the specific database engine by doctrine without an extension developer taking care of that specifically.

The API provided by the core is basically a pretty small and lightweight facade in front of doctrine-dbal that adds some convenient methods as well as some TYPO3 CMS specific sugar. The facade additionally provides methods to retrieve specific connection objects per configured database connection based on the table that is queried. This enables instance administrators to configure different database engines for different tables while this is transparent for extension developers.

doctrine-dbal has been introduced with TYPO3 CMS version 8 and substitutes the old API based on $GLOBALS['TYPO3_DB']. Extension authors are encouraged to switch away from TYPO3_DB to the new API. A dedicated chapter helps with typical migration questions. With database abstraction being built in doctrine-dbal the old and optional extensions dbal and adodb are obsolete.

This document does not outline each and every single method the API provides. It sticks to those that are commonly used in extensions and some parts like the rewritten schema migrator are left out since they are usually of little to no interest for extensions.

Understanding Doctrine-Dbal and Doctrine-Orm

Doctrine is a two-fold project with doctrine-dbal being the low-level database abstraction and query building interface to specific database engines, while doctrine-orm is a high-level object relational mapping on top of doctrine-dbal.

The TYPO3 CMS core - only - implements the dbal part. doctrine-orm is neither required nor implemented nor used at the time of this writing.

Low-level and high-level database calls

This documentation is about low-level database calls. In many cases it is better to use higher level API's like the DataHandler or extbase repositories and to let the framework handle persistence details internally.


Always remember the high-level database calls and use them when appropriate!


Implementing the doctrine-dbal API into TYPO3 has been a huge project in 2016. Special thanks goes to awesome Mr. Morton Jonuschat for the initial design, integration and support and to more than 40 different people who actively contributed to migrate more than 1700 calls from TYPO3_DB-style to Doctrine within half ayear. This was a huge community achievement, thanks everyone involved!

Main Contents


Configuring doctrine-dbal for TYPO3 CMS is all about specifying the single database endpoints and handing over connection credentials. The frameworks supports the parallel usage of multiple database connections, a specific connection is mapped depending on its table name. The table space can be seen as a transparent layer that determines which specific connection is chosen for a query to a single or a group of tables: It allows "swapping-out" single tables from the Default connection to point them to a different database endpoint.

As with other central configuration options, the database endpoint and mapping configuration happens within typo3conf/LocalConfiguration.php and ends up in $GLOBALS['TYPO3_CONF_VARS'] after core bootstrap. The specific sub-array is $GLOBALS['TYPO3_CONF_VARS']['DB'].

A typical, basic example using only the Default connection with a single database endpoint:

// LocalConfiguration.php
// [...]
'DB' => [
   'Connections' => [
      'Default' => [
         'charset' => 'utf8',
         'dbname' => 'theDatabaseName',
         'driver' => 'mysqli',
         'host' => 'theHost',
         'password' => 'theConnectionPassword',
         'port' => 3306,
         'user' => 'theUser',
// [...]


  • The Default connection must be configured, this can not be left out or renamed.
  • For mysqli, if the host is set to localhost and if the default PHP options in this area are not changed, the connection will be socket based. This saves a little overhead. To force a TCP/IP based connection even for localhost, the IPv4 or IPv6 address and ::1/128 respectively must be used as host value.
  • The connect options are hand over to doctrine-dbal without much manipulation from TYPO3 CMS side. Please refer to the doctrine connection docs for a full overview of settings.
  • If charset option is not specified it defaults to utf8.
  • The option wrapperClass is used by the TYPO3 CMS framework to "hang in" the extended Connection class TYPO3\CMS\Database\Connection as main facade around doctrine-dbal.

A slightly more complex example with two connections, mapping the sys_log table to a different endpoint:

// LocalConfiguration.php
// [...]
'DB' => [
   'Connections' => [
      'Default' => [
         'charset' => 'utf8',
         'dbname' => 'default_dbname',
         'driver' => 'mysqli',
         'host' => 'default_host',
         'password' => '***',
         'port' => 3306,
         'user' => 'default_user',
      'Syslog' => [
         'charset' => 'utf8',
         'dbname' => 'syslog_dbname',
         'driver' => 'mysqli',
         'host' => 'syslog_host',
         'password' => '***',
         'port' => 3306,
         'user' => 'syslog_user',
   'TableMapping' => [
      'sys_log' => 'Syslog'
// [...]


  • The array key Syslog is just a name, it can be different but it's good practice to give it a useful speaking name.
  • It is possible to map multiple tables to a different endpoint by adding further table name / connection name pairs to TableMapping.
  • Mind this "connection per table" approach is limited: If in the above example join query that spans over different connections is fired, an exception is raised. It is up to the administrator to group affected tables to the same connection in those cases, or a developer should implement some fallback logic to suppress the join().


At the time of this writing (TYPO3 CMS version 8.3), there are still some known issues with connections to databases other than mysql or mariadb. Core internal tests to postgresql and mssql still fail. This however should improve with younger versions.

Furthermore, the TYPO3 CMS installer supports only a single mysql or mariadb connection at the moment and the connection details can not be properly edited within the All configuration section of the install tool.

The core team hopes this situation settles until the final release of TYPO3 v8 LTS.

Basic CRUD

A list of basic usage examples of the query API. This is just a kickstart. Details on the single methods are found in the following chapters, especially QueryBuilder and Connection.


The examples use the shorthand syntax for class names. Please refer to Class overview for the full namespace.

INSERT a row

A straight insert to a table:

            'pid' => (int)42,
            'bodytext' => 'bernd',
INSERT INTO `tt_content` (`pid`, `bodytext`) VALUES ('42', 'bernd')
SELECT a single row

Straight fetch of a single row from tt_content table:

$uid = 4
$row = GeneralUtility::makeInstance(ConnectionPool::class)
        ['uid', 'pid', 'bodytext'], // fields to select
        'tt_content', // from
        [ 'uid' => (int)$uid ] // where

Result in $row:

array(3 items)
   uid => 4 (integer)
   pid => 35 (integer)
   bodytext => 'some content' (12 chars)

The engine quotes field names, adds default TCA restrictions like "deleted=0", and prepares a query executed with this final statement:

SELECT `uid`, `pid`, `bodytext`
    FROM `tt_content`
    WHERE (`uid` = '4')
        AND ((`tt_content`.`deleted` = 0)
        AND (`tt_content`.`hidden` = 0)
        AND (`tt_content`.`starttime` <= 1473447660)
        AND ((`tt_content`.`endtime` = 0) OR (`tt_content`.`endtime` > 1473447660)))


Default restrictions deleted, hidden, startime and endtime based on TCA setting of a table are only applied to select() calls, they are not added for delete() or other query types.

SELECT multiple rows with some WHERE magic

Advanced query using the QueryBuilder and manipulating the default restrictions:

$uid = 4;
// Get a query builder for a query on table "tt_content"
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
// Remove all default restrictions (delete, hidden, starttime, stoptime), but add DeletedRestriction again
// Execute a query with "bodytext=klaus OR uid=4" and proper quoting
$rows = $queryBuilder
    ->select('uid', 'pid', 'bodytext')
            $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus')),
            $queryBuilder->expr()->eq('uid', (int)$uid)

Result in $rows:

array(2 items)
   0 => array(3 items)
      uid => 4 (integer)
      pid => 35 (integer)
      bodytext => 'bernd' (5 chars)
   1 => array(3 items)
      uid => 366 (integer)
      pid => 13 (integer)
      bodytext => 'klaus' (5 chars)

The executed query looks like:

SELECT `uid`, `pid`, `bodytext`
    FROM `tt_content`
    WHERE ((`bodytext` = 'klaus') OR (`uid` = 4))
        AND (`tt_content`.`deleted` = 0)
UPDATE multiple rows
        [ 'bodytext' => 'bernd' ], // set
        [ 'bodytext' => 'klaus' ] // where
UPDATE `tt_content` SET `bodytext` = 'bernd' WHERE `bodytext` = 'klaus'
DELETE a row
        'tt_content', // from
        [ 'uid' => (int)4711 ] // where
DELETE FROM `tt_content` WHERE `uid` = '4711'

Class overview

Doctrine provides a set of php objects to represent, create and handle SQL queries and their results. The basic class structure was slightly enriched by TYPO3 to add CMS specific features. Extension authors will typically interact with these classes and objects:

TYPO3\CMS\Core\Database\Connection: Object representing a specific connection to one connected database. Provides "shortcut" methods for simple standard queries like SELECT or UPDATE. An instance of the QueryBuilder can be retrieved to build more complex queries.
TYPO3\CMS\Core\Database\ConnectionPool: Main entry point for extensions to retrieve a specific connection a query should be executed on. Typically used to return a Connection or a QueryBuilder object.
TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder: Object to model complex expressions. Mainly used for WHERE and JOIN conditions.
TYPO3\CMS\Core\Database\Query\QueryBuilder: Object to create all sort of complex queries executed on a specific connection. Provides the main CRUD methods for select(), delete() and friends.
TYPO3\CMS\Core\Database\Query\QueryHelper: Set of static helper methods that can simplify the transition from old TYPO3_DB based code to the doctrine base API.
Restriction ...
TYPO3\CMS\Core\Database\Query\Restriction\...: Set of classes that add expressions like "deleted=0" to a query based on TCA settings of a table. This automatically adds TYPO3 specific restrictions like starttime and endtime, as well as deleted and hidden flags. Further restrictions for language overlays and workspaces are available. This documentation refers to these classes as the RestrictionBuilder.
Doctrine\DBAL\Driver\Statement: Result object retrieved if a SELECT or COUNT query has been executed. Single rows are returned as array by calling ->fetch() until the method returns false.


TYPO3's interface to execute queries via doctrine-dbal typically starts by asking the ConnectionPool for a QueryBuilder or a Connection object, handing over the table name to be queried:

// Get a query builder for a table
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_myext_comments');
// or
// Get a connection for a table
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_myext_comments');

The QueryBuilder is the default workhorse object used by extension authors to express complex queries, while a Connection instance can be used as shortcut to deal with some simple query cases and little written down code.


TYPO3 can handle multiple connections to different database endpoints at the same time. This can be configured on a per-table basis in $TYPO3_CONF_VARS. It allows running tables on different databases, without an extension developer taking care of that.

The ConnectionPool implements this feature: It looks up a configured table-to-database mapping and can return a Connection or a QueryBuilder instance for that specific connection. Those objects internally know which target connection they are dealing with and will for instance quote field names accordingly.

The transparency of tables to different database endpoints is limited, though:

Executing a table JOIN between two tables that point to different connections will throw an exception. This restriction may in practice create implicit "groups" of tables that need to point to one connection at once if an extension or the TYPO3 core joins those tables.

This can turn out as a headache if multiple different extensions use for instance the core category or collection API with their mm table joins between core internal tables and their extension's counterparts.

That situation is not easy to deal with. At the time of this writing the core development will eventually implement some non-join fallbacks for typical cases that would be good to decouple, though.


In case joins cannot be decoupled but still affected tables must run on different databases, and if the code can not be easily adapted, some DBMS like PostgreSQL allow executing those queries by having own connection handlers to different other endpoints on its own.


The QueryBuilder is a rather huge class that takes care of the main query dealing.

An instance can get hold of by calling the ConnectionPool->getQueryBuilderForTable() and handing over the table. Never instantiate and initialize the QueryBuilder directly via makeInstance()!

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('aTable');

This documentation does not mention every single available method but sticks to those used in casual queries and normal code flow. There are a couple of not mentioned methods, most of them are either very seldom used or marked as internal. Extension authors typically don't have to deal with anything not mentioned here.


From security point of view, the documentation of ->createNamedParameter() and ->quoteIdentifier() are an absolute must read and follow section. Make very sure this is understood and use this for each and every query to prevent SQL injections!

The QueryBuilder comes with a happy little list of small methods:

  • Set type of query: ->select(), ->count(), ->update(), ->insert() and delete()
  • Prepare WHERE conditions
  • Manipulate default WHERE restrictions added by TYPO3 for ->select()
  • Add LIMIT, GROUP BY and other SQL stuff
  • ->execute() a query and retrieve a Statement (a query result) object

Most methods of the QueryBuilder return $this and can be chained:



The QueryBuilder holds internal state and should not be re-used for different queries: Use one query builder per query. Get a fresh one by calling $connection->createQueryBuilder() if the same table is affected, or use $connectionPool->getQueryBuilderForTable() for a query on to a different table. Don't worry, creating those object instances is rather quick.

select() and andSelect()

Create a SELECT query.

Select all fields:


->select() and a number of other methods of the QueryBuilder are variadic and can handle any number of arguments. For ->select(), every argument is interpreted as a single field name to select:

// SELECT `uid`, `pid`, `aField`
$queryBuilder->select('uid', 'pid', 'aField');

Argument unpacking can be used if the list of fields is available as array already:

$fields = ['uid', 'pid', 'aField', 'anotherField'];

->select() supports AS and quotes identifiers automatically. This can become especially handy in join() operations:

// SELECT `tt_content`.`bodytext` AS `t1`.`text`
$queryBuilder->select('tt_content.bodytext AS t1.text')

->select() sets the list of fields that should be selected and ->addSelect() can add further items to an existing list.

Mind that ->select() resets any formerly registered list and does not append. Thus, it usually doesn't make much sense to call select() twice in a code flow, or to call it after an ->addSelect(). The methods ->where() and ->andWhere() share the same behavior.

A useful combination of ->select() and ->addSelect() can be:

if ($needAdditionalFields) {

Calling ->execute() on a ->select() query returns a Statement object. To receive single rows a ->fetch() loop on that object is used, or ->fetchAll() to return a single array with all rows. A typical code flow of a SELECT query looks like:

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$statement = $queryBuilder
   ->select('uid', 'header', 'bodytext')
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus'))
while ($row = $statement->fetch()) {
   // Do something with that single row


->select() and ->count() queries trigger TYPO3 CMS magic that adds further default where clauses if the queried table is also registered via $GLOBALS['TCA']. See the RestrictionBuilder section for details on that topic.


Create a COUNT query, a typical usage:

// SELECT COUNT(`uid`) FROM `tt_content` WHERE (`bodytext` = 'klaus')
//     AND ((`tt_content`.`deleted` = 0) AND (`tt_content`.`hidden` = 0)
//     AND (`tt_content`.`starttime` <= 1475580240)
//     AND ((`tt_content`.`endtime` = 0) OR (`tt_content`.`endtime` > 1475580240)))
$count = $queryBuilder
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus'))


  • Similar to the ->select() query type, ->count() automatically triggers RestrictionBuilder magic that adds default deleted, hidden, starttime and endtime restrictions if that is defined in TCA.
  • Similar to ->select() query types, ->execute() with ->count() returns a Statement object. To fetch the number of rows directly, use ->fetchColumn(0).
  • First argument to ->count() is required, typically ->count(*) or ->count('uid') is used, the field name is automatically quoted.
  • There is no support for DISTINCT, a ->groupBy() has to be used instead.

Create a DELETE FROM query. The method requires the table name to drop data from. Classic usage:

// DELETE FROM `tt_content` WHERE `bodytext` = 'klaus'
$affectedRows = $queryBuilder
      $queryBuilder->expr()->eq('bodytext', $this->createNamedParameter('klaus'))


  • For simple cases, it is often easier to write and read if using the ->delete() method of the Connection object.
  • In contrast to ->select(), ->delete() does not add WHERE restrictions like AND `deleted` = 0 automatically.
  • ->delete() does not magically transform a DELETE FROM `tt_content` WHERE `uid` = 4711 to something like UPDATE `tt_content` SET `deleted` = 1 WHERE `uid` = 4711 internally. A soft-delete must be handled on application level code with a dedicated lookup in $GLOBALS['TCA']['theTable']['ctrl']['deleted'] to check if a specific table can handle the soft-delete, together with an ->update() instead.
  • Multi-table delete is not supported: DELETE FROM `table1`, `table2` can not be created.
  • ->delete() ignores ->join()
  • ->delete() ignores setMaxResults(): DELETE with LIMIT does not work.
update() and set()

Create an UPDATE query. Typical usage:

// UPDATE `tt_content` SET `bodytext` = 'peter' WHERE `bodytext` = 'klaus'
      $queryBuilder->expr()->eq('bodytext', $this->createNamedParameter('klaus')
   ->set('bodytext', 'peter')

->update() requires the table to update as first argument and a table alias as optional second argument. The table alias can then be used in ->set() and ->where() expressions:

// UPDATE `tt_content` `t` SET `t`.`bodytext` = 'peter' WHERE `u`.`bodytext` = 'klaus'
   ->update('tt_content', 'u')
      $queryBuilder->expr()->eq('u.bodytext', $this->createNamedParameter('klaus')
   ->set('u.bodytext', 'peter')

->set() requires a field name as first argument and automatically quotes it internally. The second mandatory argument is the value a field should be set to, the value is automatically transformed to a named parameter of a prepared statement. This way, ->set() key/value pairs are automatically SQL injection save by default.

If a field should be set to the value of another field from the row, the quoting needs to be turned off and ->quoteIdentifier() has to be used:

// UPDATE `tt_content` SET `bodytext` = `header` WHERE `bodytext` = 'klaus'
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus'))
   ->set('bodytext', $queryBuilder->quoteIdentifier('header'), false)


  • For simple cases, it is often easier to use the ->update() method of the Connection object.
  • ->set() can be called multiple times if multiple fields should be updated.
  • ->set() requires a field name as first argument and automatically quotes it internally.
  • ->set() requires the value a field should be set to as second parameter.
  • ->update() ignores ->join() and ->setMaxResults().
  • The API does not magically add delete = 0 or other restrictions magically.
insert() and values()

Create an INSERT query. Typical usage:

$affectedRows = $queryBuilder
      'bodytext' => 'klaus',
      'header' => 'peter',


  • It is often easier to use ->insert() or ->bulkInsert() of the Connection object.
  • ->values() expects an array of key/value pairs. Both keys (field names / identifiers) and values are automatically quoted. In rare cases, quoting of values can be turned off by setting the second argument to false. In those cases the quoting has to be done manually, typically by using ->createNamedParameter() on the values, use with care ...
  • ->execute() after ->insert() returns the number of inserted rows, which is typically 1.
  • QueryBuilder does not contain a method to insert multiple rows at once, use ->bulkInsert() of Connection object instead to achieve that.

->from() is a must have call for ->select() and ->count() query types. ->from() needs a table name and an optional alias name. The method is typically called once per query build and the table name is typically the same as what was given to ->getQueryBuilderForTable(). If the query joins multiple tables, the argument should be the name of the first table within the ->join() chain:

// FROM `myTable`

// FROM `myTable` AS `anAlias`
$queryBuilder->from('myTable', 'anAlias');

->from() can be called multiple times and will create the cartesian product of tables if not restricted by an according ->where() or ->andWhere() expression. In general, it is a good idea to use ->from() only once per query and model multi-table selection with an explicit ->join() instead.

where(), andWhere() and orWhere()

The three methods are used to create WHERE restrictions for SELECT, COUNT, UPDATE and DELETE query types. Each argument is typically an ExpressionBuilder object that will be cast to a string on ->execute():

// SELECT `uid`, `header`, `bodytext`
// FROM `tt_content`
//    (
//       ((`bodytext` = 'klaus') AND (`header` = 'a name'))
//       OR (`bodytext` = 'peter') OR (`bodytext` = 'hans')
//    )
//    AND (`pid` = 42)
//    AND ... RestrictionBuilder TCA restrictions ...
$statement = $queryBuilder
   ->select('uid', 'header', 'bodytext')
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus')),
      $queryBuilder->expr()->eq('header', $queryBuilder->createNamedParameter('a name'))
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('peter')),
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('hans'))
      $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT))

Note the parenthesis of the above example: ->andWhere() encapsulates both ->where() and ->orWhere() with an additional restriction.

Argument unpacking can become handy with these methods:

$whereExpressions = [
   $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus')),
   $queryBuilder->expr()->eq('header', $queryBuilder->createNamedParameter('a name'))
if ($needsAdditionalExpression) {
   $whereExpressions[] = $someAdditionalExpression;


  • The three methods are variadic. They can handle any number of arguments. If for instance ->where() receives four arguments, they are handled as single expressions, all of them combined with AND.
  • ->where() should be called only once per query and it resets any previously set ->where(), ->andWhere() and ->orWhere() expression. Having a ->where() call after a previous ->where(), ->andWhere() or ->orWhere() typically indicates a bug or a rather weird code flow. Doing so is discouraged.
  • While creating complex WHERE restrictions, ->getSQL() is a helpful debugging friend to verify parenthesis and single query parts.
  • If using only ->eq() expressions, it is often easier to switch to the according Connection object method to simplify quoting and increase readability.
  • It is possible to feed the methods with strings directly, but that is discouraged and typically only used in rare cases where expression strings are created at a different place that can not be resolved easily. In the core, those places are usually combined with QueryHelper::stripLogicalOperatorPrefix() to remove leading AND or OR parts. Using this gives an additional risk of missing or wrong quoting and is a potential security issue. Use with care if ever.
join(), innerJoin(), rightJoin() and leftJoin()

Joining multiple tables in a ->select() or ->count() query is done with one of these methods. Multiple joins are supported by calling the methods more than once. All methods require four arguments: The name of the left side table (or its alias), the name of the right side table, an alias for the right side table name and the join restriction as fourth argument:

// SELECT `sys_language`.`uid`, `sys_language`.`title`
// FROM `sys_language`
// INNER JOIN `pages_language_overlay` `overlay`
//     ON `overlay`.`sys_language_uid` = `sys_language`.`uid`
//     (`overlay`.`pid` = 42)
//     AND (
//          (`overlay`.`deleted` = 0)
//          AND (
//              (`sys_language`.`hidden` = 0) AND (`overlay`.`hidden` = 0)
//          )
//          AND (`overlay`.`starttime` <= 1475591280)
//          AND ((`overlay`.`endtime` = 0) OR (`overlay`.`endtime` > 1475591280))
//     )
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
$statement = $queryBuilder
   ->select('sys_language.uid', 'sys_language.title')
      $queryBuilder->expr()->eq('overlay.sys_language_uid', $queryBuilder->quoteIdentifier('sys_language.uid'))
      $queryBuilder->expr()->eq('overlay.pid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT))

Notes to the above example:

  • The query operates on table sys_language as main table, this table name is given to getQueryBuilderForTable().
  • The query joins table pages_language_overlay as INNER JOIN, giving it the alias overlay.
  • The join condition is `overlay`.`sys_language_uid` = `sys_language`.`uid`. It would have been identical to swap the expression arguments of the fourth ->join() argument ->eq('sys_language.uid', $queryBuilder->quoteIdentifier('overlay.sys_language_uid')).
  • The second argument of the join expression instructs the ExpressionBuilder to quote the value as a field identifier (a field name, here a table/field name combination). Using createNamedParameter() would lead to a quoting as value (' instead of ` in mysql) and the query would fail.
  • The alias overlay - the third argument of the ->join() call - does not necessarily have to be set to a different name than the table name itself here. Using pages_language_overlay as third argument and not specifying a different name would do. Aliases are mostly useful if a join to the same table is needed: SELECT `something` FROM `tt_content` JOIN `tt_content` `content2` ON .... Aliases additionally become handy to increase readability of ->where() expressions.
  • The RestrictionBuilder added additional WHERE conditions for both involved tables! Table sys_language obviously only specifies a 'disabled' => 'hidden' as enableColumns in its TCA ctrl section, while table pages_language_overlay specifies deleted, hidden, starttime and stoptime fields.

A more complex example with two joins. The first join points to the first table again using an alias to resolve a language overlay scenario. The second join uses the alias name of the first join target as left side:

// SELECT `tt_content_orig`.`sys_language_uid`
// FROM `tt_content`
// INNER JOIN `tt_content` `tt_content_orig` ON `tt_content`.`t3_origuid` = `tt_content_orig`.`uid`
// INNER JOIN `sys_language` `sys_language` ON `tt_content_orig`.`sys_language_uid` = `sys_language`.`uid`
//     (`tt_content`.`colPos` = 1)
//     AND (`tt_content`.`pid` = 42)
//     AND (`tt_content`.`sys_language_uid` = 2)
//     AND ... RestrictionBuilder TCA restrictions for tables tt_content and sys_language ...
// GROUP BY `tt_content_orig`.`sys_language_uid`
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$constraints = [
   $queryBuilder->expr()->eq('tt_content.colPos', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
   $queryBuilder->expr()->eq('tt_content.pid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT)),
   $queryBuilder->expr()->eq('tt_content.sys_language_uid', $queryBuilder->createNamedParameter(2, \PDO::PARAM_INT)),

Further remarks:

  • ->join() and innerJoin are identical. They create an INNER JOIN query, this is identical to a JOIN query.
  • ->leftJoin() creates a LEFT JOIN query, this is identical to a LEFT OUTER JOIN query.
  • ->rightJoin() creates a RIGHT JOIN query, this is identical to a RIGT OUTER JOIN query.
  • Calls on join() methods are only considered for ->select() and ->count() type queries. ->delete(), ->insert() and update() do not support joins, those query parts are ignored and do not end up in the final statement.
  • The argument of ->getQueryBuilderForTable() should be the left most main table.
  • A join of two tables that are configured to different connections will throw an exception. This restricts which tables can be configured to different database endpoints. It is possible to test the connection objects of involved tables for equality and implement a fallback logic in PHP if they are different.
orderBy() and addOrderBy()

Add ORDER BY to a ->select() statement. Both ->orderBy() and ->addOrderBy() require a field name as first argument:

// SELECT * FROM `sys_language` ORDER BY `sorting` ASC
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
$languageRecords = $queryBuilder


  • ->orderBy() resets any previously specified orders. It doesn't make sense to call it after a previous ->orderBy() or ->addOrderBy() again.
  • Both methods need a field name or a table.fieldName or a tableAlias.fieldName as first argument, in the above example calling ->orderBy('sys_language.sorting') would have been identical. All identifiers are quoted automatically.
  • The second, optional argument of both methods specifies the sorting order. The two allowed values are ASC and DESC where ASC is default and can be omited.
  • To create a chain of orders, use ->orderBy() and then multiple ->addOrderBy() calls. Calling ->orderBy('header')->addOrderBy('bodytext')->addOrderBy('uid', DESC') creates ORDER BY `header` ASC, `bodytext` ASC, `uid` DESC
groupBy() and addGroupBy()

Add GROUP BY to a ->select() statement. Each argument to the methods is a single identifier:

// GROUP BY `pages_language_overlay`.`sys_language_uid`, `sys_language`.`uid`
->groupBy('pages_language_overlay.sys_language_uid', 'sys_language.uid');


  • Similar to ->select() and ->where() both methods are variadic and take any number of arguments, argument unpacking is supported: ->groupBy(...$myGroupArray)
  • Each argument is either a direct field name GROUP BY `bodytext`, a table.fieldName or a tableAlias.fieldName and will be properly quoted.
  • ->groupBy() resets any previously set group specification and should be called only once per statement.
setMaxResults() and setFirstResult()

Add LIMIT to restrict number of records and OFFSET for pagination query parts. Both methods should be called only once per statement:

// SELECT * FROM `sys_language` LIMIT 2 OFFSET 4


  • It's allowed to call ->setMaxResults() but not to call ->setFirstResult().
  • It is possible to call ->setFirstResult() without calling setMaxResults(): This equals to "Fetch everything, but leave out the first n records". Internally, LIMIT will be added by doctrine-dbal and set to a very high value.

Method ->getSQL() returns the created query statement as string. It is incredible useful during development to verify the final statement is executed just as a developer expects it:

$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
$statement = $queryBuilder->execute();


  • This is debugging code. Take proper actions to ensure those calls do not end up in production!
  • The method is typically called directly in front ->execute() to output the final statement.
  • Casting a QueryBuilder object to (string) has the same effect as calling ->getSQL(), the explicit call using the method should be preferred to simplify a search operation for this kind of debugging statements, though.
  • The method is a simple way to see which restrictions the RestrictionBuilder added.
  • doctrine-dbal always creates prepared statements: Any value that added via ->createNamedParameter() creates a placeholder that is later substituted if the real query is fired via ->execute(). ->getSQL() does not show those values, instead the placeholder names are displayed, usually with a string like :dcValue1. There is no simple solution to show the fully replaced query from within the framework.

Compile and fire the final query statement. This is usually the last call on a QueryBuilder object. The method has two possible return values: On success, it either returns a Statement object representing the result set of ->select() and ->count() queries, or it returns an integer representing the number of affected rows for ->insert(), ->update() and ->delete() queries.

If the query fails for whatever reason (for instance if the database connection was lost or if the query contains a syntax error), a \Doctrine\DBAL\DBALException is thrown. It is most often bad habit to catch and suppress this exception since it indicates a runtime or a program error. Both should bubble up. See the coding guidelines for more information on proper exception handling.


Return an instance of the ExpressionBuilder. This object is used to create complex WHERE query parts and JOIN expressions:

// SELECT `uid` FROM `tt_content` WHERE (`uid` > 42)
      $queryBuilder->expr()->gt('uid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT))


  • This object is stateless and can be called and worked on as often as needed. It however bound to the specific connection a statement is created for and is thus only available through the QueryBuilder which is specific for one connection, too.
  • Never re-use the ExpressionBuilder, especially not between multiple QueryBuilder objects, always get an instance of the ExpressionBuilder by calling ->expr().

Create a placeholder for a prepared statement field value. Always use that when dealing with user input in expressions to make the statement SQL injection safe:

// SELECT * FROM `tt_content` WHERE (`bodytext` = 'kl\'aus')
$searchWord = "kl'aus"; // $searchWord = GeneralUtility::_GP('searchword');
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter($searchWord))

The above example shows the importance of using ->createNamedParameter(): The search word kl'aus is "tainted" and would break the query if not channeled through ->createNamedParameter() which quotes the backtick and makes the value SQL injection safe.

Not convinced? Suppose the code would look like this:

$_POST['searchword'] = "'foo' UNION SELECT username FROM be_users";
$searchWord = GeneralUtility::_GP('searchword');
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
   this fails with syntax error to prevent copy and paste
      // MASSIVE SECURITY ISSUE DEMONSTRATED HERE, USE ->createNamedParameter() ON $searchWord!
      $queryBuilder->expr()->eq('bodytext', $searchWord)

Mind the missing ->createNamedParameter() in the ->eq() expression on given value! This code would happily execute the statement SELECT uid FROM `tt_content` WHERE `bodytext` = 'foo' UNION SELECT username FROM be_users; returning a list of backend user names!


  • Always use ->createNamedParameter() around any input, no matter where it comes from.

  • The second argument of ->expr() is always either a call to ->createNamedParameter() or ->quoteIdentifier().

  • The second argument of ->createNamedParameter() specifies the type of input. For string, this can be omitted, but it is good practice to add \PDO::PARAM_INT for integers or similar for other field types. This is currently no strict rule, but following this will reduces headaches in the future, especially for DBMS that are not as relaxed as mysql when it comes to field types.

  • Keep the ->createNamedParameter() as close as possible to the expression. Do not structure your code in a way that it first quotes something and only later stuffs the already prepared names into the expression. Having ->createNamedParameter() directly within the created expression is much less error prone and easier to review. This is a general rule: Sanitizing input must be as close as possible to the "sink" where a value is submitted to a lower part of the framework. This paradigm should be followed for other quote operations like htmlspecialchars() or GeneralUtility::quoteJSvalue(), too. Sanitizing should be directly obvious at the very place where it is important:

    // DO
    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
           $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter($searchWord))
    // DON'T DO, this is much harder to track:
    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
    $myValue = $queryBuilder->createNamedParameter($searchWord);
    // Imagine much more code here
           $queryBuilder->expr()->eq('bodytext', $myValue)
quoteIdentifier() and quoteIdentifiers()

->quoteIdentifier() must be used if not a value is handled, but a field name. The quoting is different in those cases and typically ends up with backticks ` instead of ticks ':

// SELECT `uid` FROM `tt_content` WHERE (`header` = `bodytext`)
// Return list of rows where header and bodytext values are identical
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
      $queryBuilder->expr()->eq('header', $queryBuilder->quoteIdentifier('bodytext'))

The method quotes single field names or combinations of table names or table aliases with field names:

// Single field name: `bodytext`
// Table name and field name: `tt_content`.`bodytext`
// Table alias and field name: `foo`.`bodytext`
->from('tt_content', 'foo')->quoteIdentifier('foo.bodytext')


  • Similar to ->createNamedParameter() this method is crucial to prevent SQL injections. The same rules apply here.
  • Method ->set() for UPDATE statements expects their second argument to be a field value by default and quotes them using ->createNamedParameter() internally. In case a field should be set to the value of another field, this quoting can be turned off and an explicit call to ->quoteIdentifier() must be added.
  • Internally, ->quoteIdentifier() is automatically called on all method arguments that must be a field name. For instance, ->quoteIdentifier() is called on all arguments given to ->select().
  • ->quoteIdentifiers() (mind the plural) can be used to quote multiple field names at once. While that method is 'public` and thus exposed as API method, this is mostly useful internally only.

Helper method to quote % characters within a search string. This is helpful in ->like() and ->notLike() expressions:

// SELECT `uid` FROM `tt_content` WHERE (`bodytext` LIKE '%kl\\%aus%')
$searchWord = 'kl%aus';
         $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($searchWord) . '%')


Even with using ->escapeLikeWildcards(), the value must again be encapsulated in a ->createNamedParameter() call. Only calling ->escapeLikeWildcards() does not make the value SQL injection safe!

getRestrictions(), setRestrictions(), resetRestrictions()

API methods to deal with the RestrictionBuilder.


An instance of class TYPO3\CMS\Core\Database\Connection is retrieved from the ConnectionPool by calling ->getConnectionForTable() and handing over the table name a query should executed on.

The class extends the basic doctrine-dbal Doctrine\DBAL\Connection class and is mainly used internally within the TYPO3 CMS framework to establish, maintain and terminate connections to single database endpoints. Those internal methods are not scope of this documentation since an extension developer usually doesn't have to deal with that.

For an extension developer however, the class provides a list of "short-hand" methods that allow dealing with "simple" query cases, without the complexity of the QueryBuilder. Using those methods typically ends up in rather short and easily readable code. The methods have in common that they support only "equal" comparisons in WHERE conditions, that all fields and values are fully quoted automatically and the created queries are executed right away.


The Connection object is designed to work on a single table only. If queries to multiple tables should be performed, the object must not be re-used. Instead, a single Connection instance should be retrieved via ConnectionPool per target table. However, it is allowed to use one Connection object for muliple queries to the same table.


Creates and executes an INSERT INTO statement. A (slightly simplified) example from the Registry API:

// INSERT INTO `sys_registry` (`entry_namespace`, `entry_key`, `entry_value`) VALUES ('aoeu', 'aoeu', 's:3:\"bar\";')
         'entry_namespace' => $namespace,
         'entry_key' => $key,
         'entry_value' => serialize($value)

Well, that should be rather obvious: First argument is the table name to insert a row into, second argument is an array of key/value pairs. All keys are quoted to field names and all values are quoted to string values.

It is possible to add another array as third argument to specify how single values are quoted. This is useful if date or numbers or similar should be inserted. The example below quotes the first value to an integer and the second one to a string:

// INSERT INTO `sys_log` (`userid`, `details`) VALUES (42, 'klaus')
         'userid' => (int)$userId,
         'details' => (string)$details,

insert() returns the number of affected rows. Guess what? That's the number 1 ... In case something goes wrong a \Doctrine\DBAL\DBALException is raised.


A list of allowed field types for proper quoting can be found in the TYPO3\CMS\Core\Database\Connection class and its base class \Doctrine\DBAL\Connection


INSERT multiple rows at once. An example from the test suite:

      ['aField' => 'aValue'],
      ['aField' => 'anotherValue']

First argument is the table to insert table into, second argument is an array of rows, third argument is the list of field names. Similar to ->insert() it is optionally possible to add another argument to specify quoting details, if omitted, everything will be quoted to strings.


mysql is rather forgiving when it comes to insufficient field quoting: Inserting a string to an int field will not raise an error and mysql will adapt internally. However, other dbms are not that relaxed and may raise errors. It is good practice to specify field types for each field, especially if they are not strings. Doing so right away will reduce the number of raised bugs if people run your extension an anything else than mysql.


Create and execute an UPDATE statement. The example from FAL's ResourceStorage sets a storage to offline:

// UPDATE `sys_file_storage` SET `is_online` = 0 WHERE `uid` = '42'
      ['is_online' => 0],
      ['uid' => (int)$this->getUid()],

First argument is the table an update should be executed on, the second argument is an array of key/value pairs to set, the third argument is an array of "equal" where statements that are combined with AND, the (optional) fourth argument specifies the type of values to be updated similar to ->insert() and bulkInsert().

Note the third argument WHERE `foo` = 'bar' only supports equal =. For more complex stuff the QueryBuilder has to be used.

The method returns the number of affected rows.


Execute a DELETE query using equal conditions in WHERE, example from BackendUtility to mark rows as no longer locked by a user:

// DELETE FROM `sys_lockedrecords` WHERE `userid` = 42
      ['userid' => (int)42],

First argument is the table name, second argument is a list of AND combined WHERE conditions as array, third argument specifies the quoting of WHERE values. There is a pattern ;)


TYPO3 CMS uses a "soft delete" approach for many tables. Instead of directly deleting a rows in the database, a field - often called deleted - is set from 0 to 1. Executing a DELETE query circumvents this and really removes rows from a table. For most tables, it is better to use the DataHandler API to handle deletes instead of executing such low level queries directly.


Empty a table, removing all rows. Usually much quicker than a ->delete() of all rows. This typically resets "auto increment primary keys" to zero. Use with care:

// TRUNCATE `cache_treelist`

A COUNT query. Again, this methods becomes handy if very simple COUNT statements are to be executed, the example returns tha number of active rows from table tt_content that have their bodytext field set to klaus:

// FROM `tt_content`
//     (`bodytext` = 'klaus')
//     AND (
//         (`tt_content`.`deleted` = 0)
//         AND (`tt_content`.`hidden` = 0)
//         AND (`tt_content`.`starttime` <= 1475621940)
//         AND ((`tt_content`.`endtime` = 0) OR (`tt_content`.`endtime` > 1475621940))
//     )
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
$rowCount = $connection->count(
   ['bodytext' => 'klaus']

First argument is the field to count on, usually * or uid. Second argument is the table name, third argument is an array of WHERE equal conditions combined with AND.


  • ->count() of Connection returns the number directly as integer, in contrast to the method of the QueryBuilder, there is no need to call ->fetchColumns(0) or similar.
  • The third argument expects all WHERE values to be strings, each single expression is combined with AND.
  • The RestrictionBuilder kicks in and adds additional WHERE conditions based on TCA settings.
  • Field names and values are quoted automatically.
  • If anything more complex than a simple equal condition on WHERE is needed, the QueryBuilder methods are a better choice: Next to ->select(), the ->count() query is often the least useful method of the Connection object.

Creates and executes a simple SELECT query based on equal conditions. Its usage is limited, the RestrictionBuilder kicks in and key/value pairs are automatically quoted:

// SELECT `entry_key`, `entry_value` FROM `sys_registry` WHERE `entry_namespace` = 'my_extension'
$resultRows = GeneralUtility::makeInstance(ConnectionPool::class)
      ['entry_key', 'entry_value'],
      ['entry_namespace' => 'my_extension']


  • In contrast to the other short-hand methods, ->select() returns a Statement object ready to ->fetch() single rows or to ->fetchAll()
  • The method accepts a series of further arguments to specify GROUP BY, ORDER BY, LIMIT and OFFSET query parts.
  • For non-trivial SELECT queries, it is often better to switch to the according method of the QueryBuilder object.
  • The RestrictionBuilder adds default WHERE restrictions. If those restrictions do not apply to the query needs, it is required to switch to the QueryBuilder->select() method for fine-grained WHERE manipulation.

The QueryBuilder should not be re-used for multiple different queries. However, it sometimes becomes handy to first fetch a Connection object for a specific table and to execute a simple query, and to create a QueryBuilder for a more complex query from this connection object later. The methods usefulness is limited however and no good example within the core can be found at the time of this writing.

The method can be helpful in loops to save some precious code characters, too:

$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($myTable);
foreach ($someList as $aListValue) {
   $myResult = $connection->createQueryBuilder


The ExpressionBuilder class is responsible to dynamically create SQL query parts for WHERE and JOIN ON conditions, functions like ->min() may also be used in SELECT parts.

It takes care of building query conditions while ensuring table and column names are quoted within the created expressions / SQL fragments. It is a facade to the actual doctrine-dbal ExpressionBuilder.

The ExpressionBuilder is used within the context of the QueryBuilder to ensure queries are being build based on the requirements of the database platform in use.

An instance of the ExpressionBuilder is retrieved from the QueryBuilder object:

$expressionBuilder = $queryBuilder->expr();

It is good practice to not assign an instance of the ExpressionBuilder to a variable but to use it within the code flow of the QueryBuilder context directly:

$rows = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content')
   ->select('uid', 'header', 'bodytext')
      // `bodytext` = 'klaus' AND `header` = 'peter'
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus')),
      $queryBuilder->expr()->eq('header', $queryBuilder->createNamedParameter('peter'))


It is crucially important to quote values correctly to not introduce SQL injection attack vectors to your application. See the section of the QueryBuilder for details.

  • ->andX() conjunction
  • ->orX() disjunction

Combine multiple single expressions with AND or OR. Nesting is possible, both methods are variadic and take any number of argument which are all combined. It usually doesn't make much sense to hand over zero or only one argument, though.

A core example to find a sys_domain record:

//     (`sys_domain`.`pid` = `pages`.`uid`)
//     AND (
//        (`sys_domain`.`domainName` = 'example.com')
//        OR
//        (`sys_domain`.`domainName` = 'example.com/')
//     )
   $queryBuilder->expr()->eq('sys_domain.pid', $queryBuilder->createNamedParameter('pages.uid', \PDO::PARAM_INT)),
      $queryBuilder->expr()->eq('sys_domain.domainName', $queryBuilder->createNamedParameter($domain)),
      $queryBuilder->expr()->eq('sys_domain.domainName', $queryBuilder->createNamedParameter($domain . '/'))

A set of methods to create various comparison expressions or SQL functions:

  • ->eq($fieldName, $value) "equal" comparison =
  • ->neq($fieldName, $value) "not equal" comparison !=
  • ->lt($fieldName, $value) "less than" comparison <
  • ->lte($fieldName, $value) "less than or equal" comparison <=
  • ->gt($fieldName, $value) "greater than" comparison >
  • ->gte($fieldName, $value) "greater than or equal" comparison >=
  • ->isNull($fieldName) "IS NULL" comparison
  • ->isNotNull($fieldName) "IS NOT NULL" comparison
  • ->like($fieldName) "LIKE" comparison
  • ->notLike($fieldName) "NOT LIKE" comparison
  • ->in($fieldName, $valueArray) "IN ()" comparison
  • ->notIn($fieldName, $valueArray) "NOT IN ()" comparison
  • ->inSet($fieldName, $value) "FIND_IN_SET('42', aField)" Find a value in a comma separated list of values
  • ->bitAnd($fieldName, $value) A bitwise AND operation &

Remarks and warnings:


// `bodytext` = 'foo' - string comparison
->eq('bodytext', $queryBuilder->createNamedParameter('foo'))

// `tt_content`.`bodytext` = 'foo'
->eq('tt_content.bodytext', $queryBuilder->createNamedParameter('foo'))

// `aTableAlias`.`bodytext` = 'foo'
->eq('aTableAlias.bodytext', $queryBuilder->createNamedParameter('foo'))

// `uid` = 42 - integer comparison
->eq('uid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT))

// `uid` >= 42
->gte('uid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT))

// `bodytext` LIKE 'klaus'

// `bodytext` LIKE '%klaus%'
   $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards('klaus') . '%')

// `uid` IN (42, 0, 44) - properly sanitized, mind the intExplode and PARAM_INT_ARRAY
      GeneralUtility::intExplode(',', '42, karl, 44', true),

// `CType` IN ('media', 'multimedia') - properly sanitized, mind the PARAM_STR_ARRAY
      ['media', 'multimedia'],
Aggregate functions

Aggregate functions used in SELECT parts, often combined with GROUP BY. First argument is the field name (or table name / alias with field name), second argument an optional alias.

  • ->min($fieldName, $alias = NULL) "MIN()" calculation
  • ->max($fieldName, $alias = NULL) "MAX()" calculation
  • ->avg($fieldName, $alias = NULL) "AVG()" calculation
  • ->sum($fieldName, $alias = NULL) "SUM()" calculation
  • ->count($fieldName, $alias = NULL) "COUNT()" calculation


// Calculate the average creation timestamp of all rows from tt_content
// SELECT AVG(`crdate`) AS `averagecreation` FROM `tt_content`
$result = $queryBuilder
      $queryBuilder->expr()->avg('crdate', 'averagecreation')

// Distinct list of all existing endtime values from tt_content
// SELECT `uid`, MAX(`endtime`) AS `maxendtime` FROM `tt_content` GROUP BY `endtime`
$statement = $queryBuilder
      $queryBuilder->expr()->max('endtime', 'maxendtime')


Database tables in TYPO3 CMS that can be administrated in the backend come with TCA definitions that specify how single fields and rows of the table should be handled and displayed by the framework.

The ctrl section of a tables TCA array specifies optional framework internal handling of soft deletes and language overlays: For instance, if a row in the backend is deleted using the page or list module, many tables are configured to not entirely drop that row from the table, instead a field (often deleted) is set from zero to one for that row. Similar mechanics kick in for start- and endtime as well as language and workspace overlays. See the ['ctrl'] section chapter in the TCA reference for details on this topic.

These mechanics however come with a price tag attached to it: Extension developers dealing with low-level query stuff must take care overlayed or deleted rows are not in the result set of a casual query.

This is where this "automatic restriction" stuff kicks in: The construct is created on top of native doctrine-dbal as TYPO3 CMS specific extension. It automatically adds WHERE expressions that suppress rows which are marked as deleted or exceeded their "active" life cycle. All that is based on the TCA configuration of the affected table.


A developer may ask why she has to go through all this and why this additional stuff is added on a low-level query layer, when "just a simple query" should be fired. The construct implements some important design goals:

  • Simple: Query creation should be easy to use without forcing a developer thinking too much about those nasty TCA details.
  • Cope with developer laziness: If the framework would force a developer to always add casual restrictions for each and every query, this is easy to forget. We're all lazy, are we?
  • Security: If in doubt, it is better to show a little too less than too much. It is much better to deal with a customer who complains some records are not shown than to show too many records. The former is "just a bug" while the latter can easily escalate to a serious privilege escalation security issue.
  • Automatic query upgrades: If a table was designed without soft-delete in the first place and later a deleted flag is added and registered in TCA, queries executed on that table will automatically upgrade and add the according deleted = 0 restriction.
  • Handing over restriction details to the framework: Having the restriction expressions done by the framework gives it the opportunity to change details without breaking extension code. This may very well happen in the future and having a happy little upgrade path for such cases in place may become very handy later.
  • Flexibility: The class construct is created in a way that allows developers to extend or substitute it with own restrictions if that is useful to model the domain in question.
Main construct

The restriction builder is called whenever a SELECT or COUNT query is executed through either the QueryBuilder or Connection. The QueryBuilder allows manipulation of those restrictions while the simplified Connection class does not. If a query deals with multiple tables in a join, restrictions for all affected tables are added.

Each single restriction like a DeletedRestriction or a StartTimeRestriction is modeled as a single class implementing the QueryRestrictionInterface. Each restriction looks up in TCA if it should kick in. If so, it adds according expressions to the WHERE clause when the final statement is compiled.

Multiple restrictions can be grouped in containers which implement the QueryRestrictionContainerInterface.

The DefaultRestrictionContainer is always added by ... uuhm ... default: It adds the DeletedRestriction, the HiddenRestriction, the StartTimeRestriction and the EndTimeRestriction. Note this is true for all contexts a query is executed in: It does not matter whether a query is created from within a frontend, a backend or a cli call, they all add the DefaultRestrictionContainer if not explicitly told otherwise by an extension developer.


Having this DefaultRestrictionContainer used everywhere is the second iteration of that code construct:

The first variant automatically added restrictions based on context. For instance, a query fired by a call that is executed in the backend did not add the hidden flag, while a query fired from within a frontend call did so. We quickly figured this ends up in a huge mess: The distinction between frontend, backend and cli is not that sharp in TYPO3, as example the frontend behaves much more like a backend call if the admin panel is used.

The currently active variant is much easier: It always adds sane defaults everywhere, a developer only has to deal with details if they don't fit. The core team hopes this approach is a good balance between hidden magic, security, transparency and convenience.

  • DeletedRestriction: (default) Evaluates ['ctrl']['delete'], adds for instance AND deleted = 0 if TCA['aTable']['ctrl']['delete'] = 'deleted' is specified.
  • HiddenRestriction: (default) Evaluates ['ctrl']['enablecolumns']['disabled'], adds AND hidden = 0 if hidden is specified as field name.
  • StarTimeRestriction: (default) Evaluates ['ctrl']['enablecolumns']['starttime'], typically adds something like AND (`tt_content`.`starttime` <= 1475580240).
  • EndTimeRestriction: (default) Evaluates ['ctrl']['enablecolumns']['endtime'].
  • FrontendGroupRestriction: Evaluates ['enablecolumns']['fe_group'].
  • RootlevelRestriction: Match records on root level, adds AND (`pid` = 0)
  • BackendWorkspaceRestriction: Determines the current workspace a backend user is working in and adds a couple of restrictions to select only records of that workspace if the table supports workspaced records.
  • FrontendWorkspaceRestriction: Restriction to filter records for fronted workspaces preview.
  • DefaultRestrictionContainer: Add DeletedRestriction, HiddenRestriction, StartTimeRestriction and EndTimeRestriction. This container is always added if not told otherwise.
  • FrontendRestrictionContainer: Adds DeletedRestriction, HiddenRestriction, StartTimeRestriction, EndTimeRestriction, FrontendWorkspaceRestriction and FrontendGroupRestriction. This container should be be added by a developer to a query if creating query statements in frontend context or if handling frontend stuff from within cli calls.

Often the default restrictions are sufficient. Nothing needs to be done in those cases.

However, many backend modules still want to show disabled records and remove the starttime and endtime restrictions to allow administration of those records for an editor. A typical setup from within a backend module:

// SELECT `uid`, `bodytext` FROM `tt_content` WHERE (`pid` = 42) AND (`tt_content`.`deleted` = 0)
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
// Remove all restrictions but add DeletedRestriction again
$result = $queryBuilder
   ->select('uid', 'bodytext')
   ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))

The DeletedRestriction should be kept in almost all cases. Usually, the only extension that dismiss that flag is the recycler module to list and resurrect deleted records. Any object implementing the QueryRestrictionInterface can be given to ->add(). This allows extensions to deliver own restrictions.

An alternative to the recommended way of first removing all restrictions and then adding needed ones again (using ->removeAll(), then ->add()) is to kick specific restrictions with a call to ->removeByType():

// Remove starttime and endtime, but keep hidden and deleted

In the frontend it is often needed to swap the DefaultRestrictionContainer with the FrontendRestrictionContainer:

// Kick default restrictions and add list of default frontend restrictions

Note that ->setRestrictions() resets any previously specified restrictions. Any class instance implementing QueryRestrictionContainerInterface can be given to ->setRestrictions(). This allows extensions to deliver and use an own set of restrictions for own query statements if needed.


It can be very helpful to debug the final statements created by the RestrictionBuilder using debug($queryBuilder->getSQL()) right before the final call to $queryBuilder->execute(). Just take care these calls do not end up in production :ref:` code.


A Statement object is returned by QueryBuilder->execute() for ->select() and ->count() query types and by Connection->select() and Connection->count() calls.

The object represents a query result set and comes with methods to ->fetch() single rows or to ->fetchAll() of them. Additionally, it can also be used to execute a single prepared statement with different values multiple times. This part is however not widely used within the TYPO3 CMS core yet, and thus not fully documented here.


The name "Statement" instead of "Result" can be puzzling at first glance: The class represents a prepared statement that can be executed multiple times with different values and then returns multiple different result sets. From this point of view "Statement" fits much better than "Result".


Fetch next row from a result statement. Usually used in while() loops. Typical example:

// Fetch all records from tt_content on page 42
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$statement = $queryBuilder
   ->select('uid', 'bodytext')
   ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT)))
while ($row = $statement->fetch()) {
   // Do something useful with that single $row

->fetch() returns arrays with single field / values pairs until the end of the result set is reached which then returns false and thus breaks the while loop.


Returns an array containing all of the result set rows by implementing the same while loop as above internally. Using that method saves some precious code characters but is more memory intensive if the result set is large with lots of rows and lot of data since big arrays are carried around in PHP:

// Fetch all records from tt_content on page 42
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$rows = $queryBuilder
   ->select('uid', 'bodytext')
   ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT)))

Returns a single column from the next row of a result set, other columns from that result row are discarded. This method is especially handy for QueryBuilder->count() queries. The Connection->count() implementation does exactly that to return the number of rows directly:

// Get the number of tt_content records on pid 42 into variable $numberOfRecords
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$numberOfRecords = $queryBuilder
   ->where($queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter(42, \PDO::PARAM_INT)))

Returns the number of rows affected by the last execution of this statement. Use that method instead of counting the number of records in a ->fetch() loop manually.


The class contains miscellaneous helper methods to build syntactically valid SQL queries.

Most helper methods are required to deal with legacy data where the format of the input is not strict enough to reliably use the SQL parts in queries directly.

The whole class is marked as @internal, should not be used by extension authors and may - if things go wrong - change at will. The class will hopefully vanish mid-term. However, there may be situations when the class methods can become handy if extension authors migrate their own extensions away from TYPO3_DB to doctrine-dbal. In practice, the core will most likely add proper deprecations to single methods if they are target of removal later.

Extension developers may keep this class in mind for migration, but must not use methods for new code created from scratch. Apart from that, as can be seen below, using those methods often ends up in rather ugly code.

The migration benefits are the only reason the methods are documented here.


Using those methods raise the risk of SQL injections, especially for methods like ->stripLogicalOperatorPrefix() since its input string tends to come from user supplied input and is sometimes added as WHERE expression without further quoting. Keep a special eye on those scenarios!


Some parts of the core framework allow string definitions like ORDER BY sorting for instance in TCA and TypoScript. The method rips those strings apart and prepares them to be fed to QueryBuilder->orderBy():

// 'ORDER BY aField ASC,anotherField, aThirdField DESC'
// ->
// [ ['aField', 'ASC'], ['anotherField', null], ['aThirdField', 'DESC'] ]
$uglyOrderBy = 'ORDER BY aField ASC,anotherField, aThirdField DESC'
foreach (QueryHelper::parseOrderBy((string)$uglyOrderBy) as $orderPair) {
   list($fieldName, $order) = $orderPair;
   $queryBuilder->addOrderBy($fieldName, $order);

Parses GROUP BY strings ready to be added via QueryBuilder->groupBy(), similar to ->parseOrderBy():

// 'GROUP BY be_groups.title, anotherField'
// ->
// ['be_groups.title', 'anotherField']
$uglyGroupBy = 'GROUP BY be_groups.title, anotherField';

Parse a table list, possibly prefixed with FROM, and explode it into and array of arrays where each item consists of a tableName and an optional alias name, ready to be put into QueryBuilder->from():

// 'FROM aTable a,anotherTable, aThirdTable AS c',
// ->
// [ ['aTable', 'a'], ['anotherTable', null], ['aThirdTable', 'c'] ]
$uglyTableString = 'FROM aTable a,anotherTable, aThirdTable AS c;
foreach (QueryHelper::parseTableList($uglyTableString) as $tableNameAndAlias) {
   list($tableName, $tableAlias) = $tableNameAndAlias;
   $queryBuilder->from($tableName, $tableAlias);

Split a JOIN SQL fragment into table name, alias and join conditions:

// 'aTable AS `anAlias` ON anAlias.uid = anotherTable.uid_foreign'
// ->
// [
//     'tableName' => 'aTable',
//     'tableAlias' => 'anAlias',
//     'joinCondition' => 'anAlias.uid = anotherTable.uid_foreign'
// ],
$uglyJoinString = 'aTable AS `anAlias` ON anAlias.uid = anotherTable.uid_foreign';
$joinParts = QueryHelper::parseJoin($uglyJoinString);

Removes the prefixes AND / OR from an input string.

Those prefixes are added in doctrine-dbal via QueryBuilder->where(), QueryBuilder->orWhere(), ExpressionBuilder->andX() and friends. Some parts of the TYPO3 framework however carry SQL fragments prefixed with AND or OR around and it's not always possible to easily get rid of those. The method helps by killing those prefixes before they are handed over to the doctrine API:

// 'AND 1=1'
// ->
// '1=1'
$uglyWherePart = 'AND 1=1'
   // WARNING: High risk of possible SQL injection here, take additional actions!

Just a left over method from the old TYPO3_DB DatabaseConnection class. Of little to no use for extension authors. This one is hopefully one of the first methods to vanish from the class.

Migrating from TYPO3_DB

This chapter is for those poor souls who want to migrate old and busted $GLOBALS['TYPO3_DB'] calls to new hotness doctrine-dbal based API.

It tries to give some hints on typical pitfalls and areas a special eye should be kept on.

Migration of a single extension is finished if a search for $GLOBALS['TYPO3_DB'] does not return hits anymore. This search is the most simple entry point to see which areas need work.

Compare raw queries

The main goal during migration is usually to fire a logically identical query. One recommended and simple approach to verify this is to note down and compare the queries at the lowest possible layer. In $GLOBALS['TYPO3_DB'], the final query statement is usually retrieved by removing the exec_ part from the method name, in doctrine method QueryBuilder->getSQL() can be used:

// Inital code:
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'index_fulltext', 'phash=' . (int)$phash);

// Remove 'exec_' and debug SQL:
debug($GLOBALS['TYPO3_DB']->SELECTquery('*', 'index_fulltext', 'phash=' . (int)$phash));
// Returns:
'SELECT * FROM index_fulltext WHERE phash=42'

// Migrate to doctrine and debug SQL:
// 'SELECT * FROM index_fulltext WHERE phash=42'
   $queryBuilder->expr()->eq('phash', $queryBuilder->createNamedParameter($pash, \PDO::PARAM_INT))

The above example returns the exact same query as before. This is not always as trivial to see since WHERE clauses are often in a different order. This especially happens if the RestrictionBuilder is involved. Since the restrictions are crucial and can easily go wrong it is advised to keep an eye on those where parts during transition.

enableFields() and deleteClause()

BackendUtility::deleteClause() adds deleted=0 if ['ctrl']['deleted'] is specified in the table's TCA. The method call should be removed during migration. If there is no other restriction method involved in the old call like enableFields(), the migrated code typically removes all doctrine default restrictions and just adds the DeletedRestriction again:

// Before:
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
   'uid, TSconfig',
   'TSconfig != \'\''
      . BackendUtility::deleteClause('pages'),

// After:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$res = $queryBuilder->select('uid', 'TSconfig')
   ->where($queryBuilder->expr()->neq('TSconfig', $queryBuilder->createNamedParameter('')))

BackendUtility::versioningPlaceholderClause('pages') is typically substituted with the BackendWorkspaceRestriction. Example very similar to the above one:

// Before:
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
   'uid, TSconfig',
   'TSconfig != \'\''
      . BackendUtility::deleteClause('pages')
      . BackendUtility::versioningPlaceholderClause('pages'),

// After:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$res = $queryBuilder->select('uid', 'TSconfig')
   ->where($queryBuilder->expr()->neq('TSconfig', $queryBuilder->createNamedParameter('')))

BackendUtility::BEenableFields() in combination with BackendUtility::deleteClause() adds the same calls as the DefaultRestrictionContainer. No further configuration needed:

// Before:
   'title, content, crdate',
      . BackendUtility::BEenableFields($systemNewsTable)
      . BackendUtility::deleteClause($systemNewsTable)

// After:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
   ->select('title', 'content', 'crdate')

cObj->enableFields() in frontend context is typically directly substituted with FrontendRestrictionContainer:

// Before:
   '*', $table,
   'pid=' . (int)$pid
      . $this->cObj->enableFields($table)

// After:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
      $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT))
Result set iteration

The exec_* calls return a resource object that is typically iterated over using sql_fetch_assoc(). This is typically changed to ->fetch() on the Statement object:

// Before:
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(...);
while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
   // Do something

// After:
$statement = $queryBuilder->execute();
while ($row = $statement->fetch()) {
   // Do something

It is sometimes needed to fetch the new uid of a just added record to further work with that row. In TYPO3_DB this was done with a call to ->sql_insert_id() after a ->exec_INSERTquery() call on the same resource. ->lastInsertId() can be used instead:

// Before:
      'pid' => 0,
      'title' => 'Home',
$pageUid = $GLOBALS['TYPO3_DB']->sql_insert_id();

// After:
$databaseConnectionForPages = $connectionPool->getConnectionForTable('pages');
      'pid' => 0,
      'title' => 'Home',
$pageUid = $databaseConnectionForPages->lastInsertId('pages');

->fullQuoteStr() is rather straight changed to a ->createNamedParameter(), typical case:

// Before:
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
   'uid, title',
   'bodytext = ' . $GLOBALS['TYPO3_DB']->fullQuoteStr('horst')

// After:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
$statement = $queryBuilder
   ->select('uid', 'title')
      $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('horst'))

The schema migrator that compiles ext_tables.sql files from all loaded extensions and compares them with current schema definitions in the database has been fully rewritten. It mostly should work as before, some specific fields however tend to grow a little larger on mysql platforms than before. This usually shouldn't have negative side effects, typically no ext_tables.sql changes are needed when migrating an extension to the new query API.

extbase QueryBuilder

The extbase internal QueryBuilder used in Repositories still exists and works a before. There is usually no manual migration needed. It is theoretically possible to use the doctrine based query builder object in extbase which can become handy since the new one is much more feature rich, but that topic didn't yet fully settle in the core and no general recommendation can be given yet.

Various tips and tricks

  • Use Find usages of PhpStorm for examples! The source code of the core is a great way to learn how specific methods of the API are used. In PhpStorm it is extremely helpful to right click on a single method and list all method usages with Find usages. This is especially handy to quickly see usage examples of complex methods like join() from the QueryBuilder.

  • INSERT, UPDATE and DELETE statements are often easier to read and write using the Connection object instead of the QueryBuilder.

  • SELECT DISTINCT aField is not supported but can be substituted with a ->groupBy('aField').

  • getSQL() and execute() can be used after each other during development to simplify debugging:

          $queryBuilder->expr()->eq('bodytext', $queryBuilder->createNamedParameter('klaus'))
    $statement = $queryBuilder->execute();
  • In contrast to the old API based on $GLOBALS['TYPO3_DB'], doctrine-dbal will throw exceptions if something goes wrong when calling execute(). The exception type is a \Doctrine\DBAL\DBALException which can be caught and transferred to a better error message if the application has to expect query errors. Note this is not good habit and often indicates an architectural flaw of the application at a different layer.

  • count() query types using the QueryBuilder typically call ->fetchColumn(0) to receive the count value. The count() method of Connection object does that automatically and returns the count value result directly.

JavaScript in TYPO3

Some thid-party JavaScript libraries are packaged with the TYPO3 source code. The TYPO3 backend itself relies on quite a lot of JavaScript to do its job. The topic of this chapter is to present how to use JavaScript properly with TYPO3, in particular in the backend. It presents the most important APIs in that regard.


Since TYPO3 4.4, the TYPO3 backend relies primarily on ExtJS. As of TYPO3 6.0, jQuery was introduced in the new Extension Manager and is the library of choice for the future. Since TYPO3 7.4, Prototype and Scriptaculous are not packed with the Core anymore. If you need them for your projects, you need to take care of shipping them yourself, preferable by usage of requireJs.

AJAX in the TYPO3 Backend

In TYPO3 4.2 a new model for writing AJAX code in the TYPO3 Backend was introduced. Although there were some parts in the TYPO3 Backend that used AJAX already, they are now unified into a single interface that handles errors and dispatches the different calls to their final locations. This way it is ensured that e.g. a BE user is logged in and all TYPO3 variables are loaded.

The whole architecture builds on top of successful techniques developers already know. It's a mixture between the eID concept from the TYPO3 Frontend, the hooking idea we know from other places in TYPO3, piped through the single entrypoint file index.php?ajaxID=foobar that creates a PHP AJAX object to see if an error occurred or not. If something went wrong, the X-JSON header is set to false and the client-side AjaxRequestHandler will know that there is an error.

In-depth presentation

Client-Side programming


This paragraph describes obsolete processes using Prototype and Scriptaculous

On the client-side we are using the Prototype JS library (located in typo3/contrib/prototype/prototype.js). If you have used it already, you know that you can make AJAX calls with AJAX.Request, AJAX.Updater and AJAX.PeriodicalUpdater. We extended the library and hooked in these objects, or better: in the callbacks users can define. If an AJAX request is made to our server-side component (typo3/ajax.php), everything developers need to do is to call this URL and add a unique, already registered parameter for their ajaxID. Their defined "onComplete" and "onSuccess" are only rendered if the X-JSON header is set to true by the server-side script. If the X-JSON header is set to false, the Responder checks if there is a callback function named "onT3Error" and executes it instead of the "onComplete" method. If the "onT3Error" method is not defined, the default TYPO3 error handler will be displaying the error in the TYPO3 backend. If the X-JSON header is set to false, the "onSuccess" callback will not be executed as well as but an error message will be shown in the notification area. This behaviour is done automatically with every AJAX call to "ajax.php" made through Prototype's AJAX classes. This responder is also only active if "typo3/js/common.js" is added to the base script.

Since TYPO3 4.4, ExtJS is used instead for AJAX calls. TYPO3 even supports usage of Ext.Direct.

Server-side programming

If you look into "typo3/ajax.php", it is only a small dispatcher script. It checks for an ajaxID in the $TYPO3_CONF_VARS['BE']['AJAX'] array and tries to execute the function pointer. The function has two parameters, where the first (an array) is not used yet. The second parameter is the TYPO3 AJAX Object (located in typo3/classes/typo3ajax.php) that is used to add the content that should be returned as the server-response to the Javascript part, or the error message that should be displayed. The X-JSON header will be set depending on whether setError() was called on this AJAX object. You can also specify if the object should return the result in a valid XML object tree, as text/html (default) or as a JSON object, see below.

The "ajaxID" is a unique identifier and can be used to override the existing AJAX calls. Therefore you can extend existing AJAX calls that already exist in the backend by redirecting it to your function. But be aware of the side-effects of this feature: Other extensions could overwrite this function as well (similar problem as with XCLASSing or single inheritance in OOP).

Also, for every TYPO3 request, you will now have a TYPO3_REQUESTTYPE variable that can be used for bitwise comparison. You can now check if you're in Backend or Frontend or in an valid AJAX request with


to see if you're calling through the new AJAX interface.

Different Content Formats

As with every AJAX response you can send it in different response formats.

  • text/html - plain text
  • text/xml - strict XML formatting
  • application/json - JSON notation

You can also specify the contentFormat in the AJAX object like this:


For the keyword you can choose between "plain" (default), "xml" and "json", "jsonbody" and "jsonhead".

Here are the specifics for each format.

Plain Text

The content array in the backend will be concatenated and returned uninterpreted.

The result will be available in the transport object as a string through "xhr.responseText".


The content needs to be valid XML and will be available in javascript as "xhr.responseXML".


The content is transformed to JSON using PHP's built-in functions and is then available in JSON notation through the second parameter in the onComplete / onSuccess methods, and additionally in the "responseText" part of the transport object ("xhr.responseText"). If it is set to "jsonbody", only the latter variable is filled, if "jsonhead" is set, it is only in the second parameter. This is useful to save traffic and you can use it with whatever format you like.

Developing with AJAX in the TYPO3 Backend

This section describes how to correctly make AJAX calls in the TYPO3 CMS BE.


This section was fully updated for TYPO3 CMS 6.2. For older versions, please refer to the related version of this manual.

How to choose the right ajaxID

An AJAX call is represented by a system-wide identifier which is used to register the handler that will receive the call. The ajaxID consists of two parts, the class name and the action name, delimited by "::" (<class>::<action>).

Although it looks like a static function call, it is really just a key. Developers must stick to a common naming scheme as described above, to avoid using identical names in different extensions.

Some good examples for an ajaxID:

  • SC_alt_db_navframe::expandCollapse
  • BackendLogin::refreshLogin
  • tx_myext_module1::executeSomething

Some bad examples for an ajaxID (the first part is too generic or the identifier contains a single part):

  • search::findRecordByTitle
  • core::reloadReferences
  • inline::processAjaxRequest
  • updateRecordList

Since TYPO3 CMS 6.2, the registration is done via an API, which provides CSRF protection on the AJAX call and an automatic registration of the AJAX call URL (in typo3conf/opendocs/ext_tables.php):

\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::registerAjaxHandler (

This is how the "opendocs" system extension registers the AJAX call to render the open documents menu in the top toolbar. The first argument is the ajaxID (as described above) and the second argument is a pointer to a class and method. This code must be located in an extension's ext_tables.php file.

The target method receives an array of parameters (depending on the call context) and a backreference to the general AJAX handler (\TYPO3\CMS\Core\Http\AjaxRequestHandler). The API of this object is used to set the content to output or to write an error message if something went wrong.

In the above example, here's how the handling method looks like (in typo3conf/opendocs/Classes/Controller/OpendocsController.php):

 * Renders the menu so that it can be returned as response to an AJAX call
 * @param array $params Array of parameters from the AJAX interface, currently unused
 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxObj Object of type AjaxRequestHandler
 * @return void
public function renderAjax($params = array(), \TYPO3\CMS\Core\Http\AjaxRequestHandler &$ajaxObj = NULL) {
   $menuContent = $this->renderMenu();

   // addContent('key', 'content to add')
   // 'key' = the new content key where the content should be added in the content array
   $ajaxObj->addContent('opendocsMenu', $menuContent);

   // the new content, "$menuContent" can now be referenced like this:
   // $ajaxObj->getContent('opendocsMenu');

The API mentioned above registers a corresponding AJAX URL in the global TYPO3.settings.ajaxUrls JavaScript array.

Whatever library you use, this URL can easily be accessed by using the registration key.

var ajaxUrl = TYPO3.settings.ajaxUrls['<registration key>'];

Here is the client-side part corresponding to the above example (an extract of typo3/sysext/backend/Resources/Public/JavaScript/shortcutmenu.js):

var del = new Ajax.Request(TYPO3.settings.ajaxUrls['ShortcutMenu::delete'], {
   parameters : '&shortcutId=' + shortcutId,
   onComplete : this.reRenderMenu.bind(this)

Using ExtJS

Loading ExtJS

To load the ExtJS library use this syntax:


There are 2 optional parameters in this call:

$this->doc->loadExtJS($css = TRUE, $theme = TRUE);
  • The first parameter is a boolean. If set to true, ext-all.css is added automatically.
  • The second parameter is also a boolean. If set to true, theme-grey is added automatically.

Additionally the function takes care of:

  • adding the correct adapter
  • adding the localization file of BE-User language
  • adding Ext.BLANK_IMAGE_URL

To do debugging in the ExtJS library, use the following call to force the debug variant to be loaded:



What is Ext.Direct?
"Ext Direct is a platform and language agnostic technology to remote server-side methods to the client-side. Ext Direct allows for seamless communication between the client-side of an Ext JS application and all popular server platforms."

Source: http://www.sencha.com/products/extjs/extdirect

In effect it means that JavaScript functions can be mapped - in the case of TYPO3 - to PHP methods on the server. AJAX calls are made transparently and TYPO3 dispatches the request and sends back the response. It's like calling the PHP function in JavaScript.

Let's look the old fashioned way of creating a server-side request:

new Ajax.Request('ajax.php', {
    method: 'get',
    parameters: 'ajaxID=tx_myext_module1::executeSomething',
    onComplete: function(xhr, json) {
        // Do something with the response

and the same with Ext.Direct:

TYPO3.Backend.MyModule.doSomething('someValue', function(response, options) {
   // Do something with the response

This features exists since TYPO3 4.4.

How to use Ext.Direct?

First of all, the PHP method and the JavaScript function must be declared in order to paired together (code taken from the "examples" extension):

   \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY, 'Classes/ExtDirect/Server.php:Tx_Examples_ExtDirect_Server'),

The last two parameters are used to define the access level of your Ext.Direct code for the BE! The third one can limit access to special modules and the fourth can be used to define authorized access only.

The next step is to make sure that the Ext.Direct code is loaded and registered with the proper namespace. This is achieved by calling up the page renderer from the BE module or from a specially designed Fluid View Helper, when making Extbase-based modules. Example taken from file EXT:examples/Classes/ViewHelpers/Be/HeaderViewHelper.php:

   /** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
$pageRenderer = $this->getDocInstance()->getPageRenderer();
   // Add base Ext.Direct code
   // Make localized labels available in JavaScript context

On the server-side, the method is implement as any other PHP method, receiving the same arguments as the JavaScript function and returning whatever data it is expected to produce:

public function countRecords($table) {
      // Return the count of all non-deleted records for the given table
   return array(
      'data' => $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', $table, '1 = 1' . \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($table))

Here the method receives a table name and sends back the count of undeleted records. The corresponding JavaScript looks like:

TYPO3.Examples.ExtDirect.countRecords(table, function(response) {
      // If the response contains data, display it in a JavaScript flash message
   if (response.data) {
      var message = String.format(TYPO3.lang['record_count_message'], response.data, table);
      TYPO3.Flashmessage.display(TYPO3.Severity.ok, TYPO3.lang['record_count_title'], message, 5);

The data is handled inside a callback function as is usual with asynchronous calls. In this case we simple display a popup flash message.


This chapter should include an example about using Ext.Direct in the FE too. The last time I tried to do this it didn't work. I don't have time to dig into that now (François - 10.11.2012)

Debugging and exception handling

The Ext.Direct implementation in the TYPO3 BE makes it possible both to catch exceptions and perform some debugging output. Exceptions are simply caught and displayed as error-level flash messages. Debugging output is redirected to the debug console. Just call the debug() function.

The API Generator

Looking at what happens under the hood, the following call:


not only adds all the base JavaScript code related to Ext.Direct, but also uses reflection to analyze the declared PHP class and build an API out of it.

In particular if your server-side method is expected to handle a form submission, it must be declared with the @formHandler annotation. Example taken from the Extension Manager's code:

 * Save extension configuration
 * @formHandler
 * @param array $parameter
 * @return array
public function saveExtensionConfiguration($parameter) {
 // ...

Backend Viewport

The TYPO3 backend is structured with an ExtJS viewport. This makes it easy to display various panels in different parts of the user interface and to resize those parts.

The viewport is defined in the files found in typo3/js/extjs/viewport*. It consists of a configuration file and the viewport component code itself. The viewport component is an extension of the Ext.Viewport component, meaning you can use all methods and functionalities from that component.

Viewport Structure

The viewport is structured in the following way:

| Top Menu                                               |
| +----------------------------------------------------+ |
| | +-------------+-------------------+--------------+ | |
| | | Module Menu | Navigation Widget | Content Area | | |
| | +-------------+-------------------+--------------+ | |
| +----------------------------------------------------+ |
| | Debug Panel                                        | |
| +----------------------------------------------------+ |
Extending the Viewport

You can extend the TYPO3 viewport yourself if you need some special configuration options. The next example demonstrates this by adding a collapse/expand functionality to the module menu.


The example below works in that it achieves its aim, but breaks the rest of the TYPO3 backend. If someone knows how to make it work properly, your help is very welcome.

First a class must be declared to use the "render-preProcess" hook of the \TYPO3\CMS\Core\Page\PageRenderer class (in the ext_localconf.php file):

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_pagerenderer.php']['render-preProcess'][] =
  'EXT:' . $_EXTKEY . '/Classes/Utilities/Viewport.php:Tx_Examples_Utilities_Viewport->renderPreProcess';

Then here is the class itself (as usual taken from the "examples" extension):

public function renderPreProcess($parameters, $pageRenderer) {
      Ext.apply(TYPO3.Viewport.configuration.items[1], {
         split: true,
         collapsible: true,
         collapseMode: "mini",
         hideCollapseTool: true,
         animCollapse: false
Debug Console

The debug console is located inside the debug panel position at the south of the viewport. It's based upon an extended ExtJS tabPanel component. A new tab can be added to the debug console by calling \TYPO3\CMS\Core\Utility\DebugUtility::debug():

\TYPO3\CMS\Core\Utility\DebugUtility::debug('New debug console message', 'Title', 'My new tab');

It seems possible to also manipulate the debug console with JavaScript, but working examples are missing for now (examples from the TYPO3 wiki don't work (anymore?)).

Page tree

Since TYPO3 4.5, the page tree is powered by ExtJS. It is implemented in the global viewport as a navigation component.

UML class diagram for the TYPO3 Page Tree

A UML diagram of the related TYPO3 classes


Unfortunately the components were never cleaned up nor generalized, so it's not possible to reuse them in different contexts. However it does provide a clean API for context-sensitive menu items.

The options available to configure the page tree are described in the TSconfig reference.

Additionally a limit can be set to the number of pages preloaded by the tree. Loading more pages will make it more responsive, but also increases the number of queries executed. The setting is:


Context-sensitive menus

With the new page tree the implementation of the context-sensitive menu (CSM) items was made more flexible and easier to configure.


The section below applies only to the CSM for the page tree. Other CSM still use the old way of doing things.


The CSM is entirely described in User TSconfig. The configuration is found in typo3/sysext/core/Configuration/DefaultConfiguration.php. Here is a sample:

options.contextMenu {
   table {
      pages_root {
         disableItems =

         items {
            100 = ITEM
            100 {
               name = view
               label = LLL:EXT:lang/locallang_core.xml:cm.view
               spriteIcon = actions-document-view
               displayCondition = canBeViewed != 0
               callbackAction = viewPage
            // ...

      pages {
         disableItems =

         items {
            // ...
            700 = ITEM
            700 {
               name = history
               label = LLL:EXT:lang/locallang_misc.xml:CM_history
               spriteIcon = actions-document-history-open
               displayCondition = canShowHistory != 0
               callbackAction = openHistoryPopUp

            800 = DIVIDER

            900 = SUBMENU
            900 {
               label = LLL:EXT:lang/locallang_core.xml:cm.copyPasteActions

               100 = ITEM
               100 {
                  name = new
                  label = LLL:EXT:lang/locallang_core.xml:cm.new
                  spriteIcon = actions-page-new
                  displayCondition = canCreateNewPages != 0
                  callbackAction = newPageWizard
               // ...
            // ...

As one can see the page tree is organized in tables (that's really just "pages" for now), with a special case for the tree root. Then comes a list of items, which are either ITEM or SUBMENU components. The latter are comprised of more ITEM components. A DIVIDER component can also be used to introduce a visual separator.

The numbered keys determine the order the items appear in. The default configuration uses multiples of hundred, so that there is plenty of room to introduce custom items in between the default ones (e.g. item 751 will appear between items 700 and 800).

Adding custom actions

Delivering custom menu items via an extension is a several step process. The code samples presented below come from extension sm_clearcachecm by Steffen Müller. Thanks to him for allowing these samples to be shared. The extension adds an item in the "Page actions" and the "Branc actions" submenus to clear the cache of the page, respectively the branch.

The contextual menu with the custom item

The contextual menu with the "Clear page cache" item added to it.

Extending the configuration

The first step is to add the necessary User TSconfig in the extension's ext_tables.php file, to declare and position the custom item in the existing menu:

$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= '
   options.contextMenu.table.pages.items {
      900 {
         1010 = DIVIDER
            1020 = ITEM
         1020 {
            name = clearPageCache
            label = LLL:EXT:sm_clearcachecm/Ressources/Private/Language/locallang_cm:clearPageCache
            spriteIcon = actions-system-cache-clear
            callbackAction = clearPageCache
      1000 {
         410 = DIVIDER
            420 = ITEM
         420 {
            name = clearBranchCache
            label = LLL:EXT:sm_clearcachecm/Ressources/Private/Language/locallang_cm:clearBranchCache
            spriteIcon = actions-system-cache-clear-impact-medium
            callbackAction = clearBranchCache

Here are the various parameters that must or should be used when declaring menu items:

Name Description
name Name of the action (can be used to disable it).
label Language label of the action.
icon Image that should be shown to the left of the action item.
spriteIcon Key of the sprite icon to use instead of some arbitrary image (see "icon" above).

Condition that is parsed and interpreted to decide whether the action item should be shown or not for a given tree node.

The syntax is similar to PHP conditions, with the usual comparison operators and the logical operators && and || to chain conditions. The values that can be used for comparison are the return values provided by the methods in the API of class \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode. If that return value is an array, any index can be accessed using the | operator:

displayCondition = getRecord|hidden = 1 && canBeDisabledAndEnabled != 0

In the above example the condition calls the getRecord() method which returns the complete record array of the page the node corresponds to. The index "hidden" of this array is checked to see if it's true. Afterwards it is chained with an && ("AND") operator to the next condition, which checks whether a node can be disabled and enabled, returning boolean value. If both conditions evaluate to true, the context action is displayed.

For more examples look up the default configuration found in t3lib/stddb/DefaultConfiguration.php.

callbackAction Javascript callback action that should be triggered after the action item is clicked.
customAttributes TypoScript array of custom attributes that can be used for your actions.
JavaScript action

The next step is to add the JavaScript actions triggered by a click to the actions of the page tree component. This script must be loaded as an "additional backend item", using the dedicated API (in file ext_tables.php:

$GLOBALS['TYPO3_CONF_VARS']['typo3/backend.php']['additionalBackendItems'][] = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY, 'Ressources/Private/Php/RegisterJavaScriptForPagetreeAction.php');

This file contains the following code:

Ext.onReady(function() {
   Ext.apply(TYPO3.Components.PageTree.Actions, {
      clearPageCache: function(node, tree) {
            function(response) {
               if (response) {
                  TYPO3.Flashmessage.display(TYPO3.Severity.error, '', response);
               } else {
                  TYPO3.Flashmessage.display(TYPO3.Severity.ok, '', TYPO3.lang.sm_clearcachecm_clearPageCacheSuccess);
   Ext.apply(TYPO3.Components.PageTree.Actions, {
      clearBranchCache: function(node, tree) {
            function(response) {
               if (response) {
                  TYPO3.Flashmessage.display(TYPO3.Severity.error, '', response);
               } else {
                  TYPO3.Flashmessage.display(TYPO3.Severity.ok, '', TYPO3.lang.sm_clearcachecm_clearBranchCacheSuccess);

The above code adds Ext.Direct callbacks to the new menu items.

Ext.Direct stuff

The last step is to write the PHP code that will answer to the Ext.Direct calls. First the PHP class must be registered as usual:

   \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath($_EXTKEY) . 'Classes/Hooks/ClickmenuAction.php:Tx_SmClearcachecm_Hooks_ClickmenuAction'

The PHP class itself contains the methods corresponding to the Ext.Direct JavaScript methods, in this case clearPageCache and clearBranchCache. They receive information about which node was clicked. This information can be made into a node object, as demonstrated below:

public function clearPageCache($nodeData) {
      $nodeUids = array();
         /* @var $node \TYPO3\CMS\Backend\Tree\Pagetree\PagetreeNode */
   $node = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\Tree\\Pagetree\\PagetreeNode', (array) $nodeData);
         // Get uid of page
   $nodeUids[] = $node->getId();
         // Clear the page cache of the page
   $success = $this->performClearCache($nodeUids);
      if (!$success) {
      return $GLOBALS['LANG']->sL('LLL:EXT:sm_clearcachecm/Ressources/Private/Language/locallang_cm.xml:clearPageCacheError', TRUE);
Predefined callbacks

The TYPO3 Core provides one predefined callback which can be used to open a defined URL inside the content frame. It's called openCustomUrlInContentFrame and needs a defined custom attribute named "contentUrl". The keyword ###ID### is automatically replaced with the selected page id. Example:

720 = ITEM
720 {
   name = someCustomeAction
   label = LLL:EXT:extension/locallang.xml:someCustomeAction
   icon = ' . \TYPO3\CMS\Core\Utility\GeneralUtility::locationHeaderUrl(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath($_EXTKEY) . 'ext_icon.gif') . '
   spriteIcon =
   displayCondition =
   callbackAction = openCustomUrlInContentFrame
   customAttributes.contentUrl = mod.php?M=web_WorkspacesWorkspaces&id=###ID###
Class diagram

The class diagram may help understanding the code structure.

UML class diagram for the contextual menus

A UML diagram of the related TYPO3 classes

Caching framework

Since TYPO3 CMS 4.3, the core contains a data caching framework which supports a wide variety of storage solutions and options for different caching needs. Each cache can be configured individually and can implement its own specific storage strategy. Major parts of the system are backported from TYPO3 Flow and are kept in sync between the two systems.

The caching framework exists to help speeding up TYPO3 sites, especially heavily loaded ones. It is possible to move all caches to a dedicated cache server with specialized cache systems like the Redis key-value store (a so called NoSQL database).

Since TYPO3 CMS 4.6, the caching framework is always enabled, the old and unflexible approach to cache content is gone. This document covers settings for TYPO3 CMS 6.0 and beyond.

Quick start for Integrators

This section gives come simple instructions for getting started with using the caching framework without giving the whole details under the hood.

Change specific cache options

By default, most core caches use the database backend. Default cache configuration is defined in typo3/sysext/core/Configuration/DefaultConfiguration.php and can be overridden in LocalConfiguration.php.

If specific settings should be applied to the configuration, they should be added to LocalConfiguration.php. All settings in LocalConfiguration.php will be merged with DefaultConfiguration.php. The easiest way to see the final cache configuration is to use the TYPO3 Backend module Admin Tools > Configuration > $TYPO3_CONF_VARS.

Example for a configuration of redis cache backend on redis database number 42 instead of the default database backend with compression for the pages cache:

return array(
// ...
   'SYS' => array(
   // ...
      'caching' => array(
         // ...
         'cache_pages' => array(
            'backend' => 'TYPO3\CMS\Core\Cache\Backend\RedisBackend',
            'options' => array(
               'database' => 42,

Garbage collection task

Most cache backends do not have an internal system to remove old cache entries that exceeded their lifetime. A cleanup must be triggered externally to find and remove those entries, otherwise caches could grow to arbitrary size. This could lead to a slow website performance, might sum up to significant hard disk or memory usage and could render the server system unusable.

It is advised to always enable the scheduler and run the "Caching framework garbage collection" task to retain clean and small caches. This housekeeping could be done once a day when the system is otherwise mostly idle.


Caches are configured in the array $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']. The basic structure is predefined in typo3/sysext/core/Configuration/DefaultConfiguration.php, and consists of the single section:

  • cacheConfigurations: Registry of all configured caches. Each cache is identified by its array key. Each cache can have the sub keys frontend, backend and options to configure the used frontend, backend and possible backend options.

Cache configurations

Unfortunately in TYPO3 CMS, all ext_localconf.php files are loaded after the instance specific configuration from LocalConfiguration.php and AdditionalConfiguration.php. This enables extensions to overwrite cache configuration already done for the instance. Any extension should avoid this situation and should just define the very required minimum of cache configuration. This boils down to define just the array key to populate a new cache to the system. Without further configuration, the cache system fall back to default backend and default frontend settings:

if (!is_array($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['myext_mycache'])) {