---
title: "DataHandler basics"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:datahandler-basics@main"
source: "ApiOverview/DataHandler/Database/Index.rst"
rendered: "2026-09-20T15:52:37+00:00"
---

# DataHandler basics {#datahandler-basics}

-   [Introduction](https://docs.typo3.org/permalink/t3coreapi:introduction@main)
-   [Basic usage](https://docs.typo3.org/permalink/t3coreapi:basic-usage@main)
-   [Commands array](https://docs.typo3.org/permalink/t3coreapi:commands-array@main)
-   [Data array](https://docs.typo3.org/permalink/t3coreapi:data-array@main)
-   [Clear cache](https://docs.typo3.org/permalink/t3coreapi:clear-cache@main)
-   [Flags in the DataHandler](https://docs.typo3.org/permalink/t3coreapi:flags-in-the-datahandler@main)

## Introduction {#tce-database-basics-introduction}

When you are using DataHandler 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](https://docs.typo3.org/permalink/t3coreapi:datahandler-data@main) and [commands](https://docs.typo3.org/permalink/t3coreapi:datahandler-commands@main).

"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 need to understand the
hierarchy of these two arrays.

> [!WARNING]
> **Caution**
>
> The DataHandler needs a properly configured [TCA](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Index.html#start). If
> your field is not configured in the TCA the DataHandler will not be able to
> interact with it. This also is the case if you configured
> `"type"="none"` (which is in fact a valid type) or if an invalid
> type is specified. In that case, the DataHandler is not
> able to determine the correct value of the field.

## Basic usage {#datahandler-commands}

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\DataHandling;

use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;

final class MyClass
{
  public function basicUsage(): void
  {
    /** @var DataHandler $dataHandler */
    // Do not inject or reuse the DataHander as it holds state!
    // Do not use `new` as GeneralUtility::makeInstance handles dependencies
    $dataHandler = GeneralUtility::makeInstance(DataHandler::class);

    $cmd = [];
    $data = [];
    $dataHandler->start($data, $cmd);

    // ... do something more ...
  }
}

```

After this initialization you usually want to perform the actual operations by
calling one (or both) of these two methods:

```php
$this->dataHandler->process_datamap();
$this->dataHandler->process_cmdmap();
```

> [!NOTE]
> Any error that might have occurred during your DataHandler operations can be
> accessed via its public property `$this->dataHandler->errorLog`.
> See [Error handling](https://docs.typo3.org/permalink/t3coreapi:tcemain-error-handling@main).

## Commands array {#tce-database-basics-commands-array}

Syntax:

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

Description of keywords in syntax:

-   **tablename**

    -   *Data type:* string

    Name of the database table. It must be configured in the
    `$GLOBALS['TCA']` array, otherwise it cannot be processed.

### uid {#datahandler-cmd-uid}

-   **uid**

    -   *Data type:* integer

    The UID of the record that is manipulated. This is always an integer.

### command {#datahandler-cmd-command}

-   **command**

    -   *Data type:* string (command keyword)

    The command type you want to execute.

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

    See [command keywords and values](https://docs.typo3.org/permalink/t3coreapi:datahandler-command-keywords@main)

### value {#datahandler-cmd-value}

-   **value**

    -   *Data type:* mixed

    The value for the command.

    See [command keywords and values](https://docs.typo3.org/permalink/t3coreapi:datahandler-command-keywords@main)

### Command keywords and values {#datahandler-command-keywords}

-   **copy**

    -   *Data type:* integer or array

    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
        [$GLOBALS\['TCA'\]\[$table\]\['ctrl'\]\['sortby'\]](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Ctrl/Index.html#ctrl-reference-sortby)
        is set, then it will be positioned *after*.

    -   **Zero value**

        Record is inserted on tree root level.

    -   **array**

        The array has to contain the integer value as in examples above and
        may contain field => value pairs for updates. The array is structured
        like:

        **Structure of the DataHandler command array**

        ```php
        [
            'action' => 'paste', // 'paste' is used for both move and copy commands
            'target' => $pUid,   // Defines the page to insert the record, or record uid to copy after
            'update' => $update, // Array with field => value to be updated.
        ]
        ```

#### move {#datahandler-cmd-move}

-   **move**

    -   *DataType:* integer

    Works like [copy](https://docs.typo3.org/permalink/t3coreapi:confval-datahandler-cmd-copy@main) but moves the record instead of
    making a copy.

#### delete {#datahandler-cmd-delete}

-   **delete**

    -   *Data Type:* integer (1)

    Value should always be "1".

    This action will delete the record (or mark the record "deleted", if
    configured in
    [$GLOBALS\['TCA'\]\[$table\]\['ctrl'\]\['delete'\]](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Ctrl/Index.html#ctrl-reference-delete)).

#### undelete {#datahandler-cmd-undelete}

-   **undelete**

    -   *Data Type:* integer (1)

    Value should always be "1".

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

#### localize {#datahandler-cmd-localize}

-   **localize**

    -   *Data type:* integer

    The value is the `languageId` (defined in the
    [site configuration](https://docs.typo3.org/permalink/t3coreapi:sitehandling-addinglanguages@main)) 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](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Columns/Index.html#columns-properties-l10n-mode)) but
    changing relevant fields to point to the right language ID.

    Requirements for a successful localization is this:

    -   `[ctrl]` options
        [languageField](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Ctrl/Index.html#ctrl-reference-languagefield) and
        [transOrigPointerField](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Ctrl/Index.html#ctrl-reference-transorigpointerfield)
        must be defined for the table
    -   A `languageId` must be configured in the site configuration.
    -   The record to be localized by currently be set to default language
        and not have any value set for the TCA `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.

    The `localize` DataHandler command should be used when translating
    records in "[connected mode](https://docs.typo3.org/m/typo3/guide-frontendlocalization/main/en-us/LocalizedContent/Index.html#localized-connected-content)"
    (strict translation of records from the default language). This command is
    used when selecting the "Translate" strategy in the content elements
    translation wizard.

#### copyToLanguage {#datahandler-cmd-copytolanguage}

-   **copyToLanguage**

    -   *Data type:* integer

    It behaves like [localize](https://docs.typo3.org/permalink/t3coreapi:confval-datahandler-cmd-localize@main) command (both record and
    child records are copied to given language), but does not set
    [transOrigPointerField](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Ctrl/Index.html#ctrl-reference-transorigpointerfield)
    fields (for example, `l10n_parent`).

    The `copyToLanguage` command should be used when localizing records in
    the "[free mode](https://docs.typo3.org/m/typo3/guide-frontendlocalization/main/en-us/LocalizedContent/Index.html#localized-content-free-content)". This
    command is used when localizing content elements using translation wizard's
    "Copy" strategy.

#### inlineLocalizeSynchronize {#datahandler-cmd-inlinelocalizesynchronize}

-   **inlineLocalizeSynchronize**

    -   *Data type:* array

    Performs localization or synchronization of child records.
    The command structure is like:

    **EXT:my_extension/Classes/DataHandling/MyClass.php (excerpt)**

    ```php
    $cmd['tt_content'][13]['inlineLocalizeSynchronize'] = [ // 13 is a parent record uid
        'field' => 'tx_myfieldname', // field we want to synchronize
        'language' => 2,             // uid of the target language
        // either the key 'action' or 'ids' must be set
        'action' => 'localize',      // or 'synchronize'
        'ids' =>  [1, 2, 3],         // array of child IDs to be localized
    ];
    ```

#### version {#datahandler-cmd-version}

-   **version**

    -   *Data type:* array

    Versioning action.

    > [!NOTE]
    > This section is currently outdated.

    **Keys:**

    -   **\[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-separated list of ids in order to perform the
            stageChange over a number of records. Also, the internal variable
            ->generalComment (also available through \`/record/commit\` route as
            \`&generalComment\`) can be used to set a default comment for all
            stage changes of an instance of the data handler.* Additional keys
            for this action are:

            -   **\[stageId\]**

                Values are:

                -   -1 (rejected)
                -   0 (editing, default)
                -   1 (review),
                -   10 (publish)

            -   **\[comment\]**

                Comment string that goes into the log.

### Examples of commands {#tce-command-examples}

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$cmd['tt_content'][54]['delete'] = 1;    // Deletes tt_content record with uid=54
$cmd['tt_content'][1203]['copy'] = -303; // Copies tt_content uid=1203 to the position after tt_content uid=303 (new record will have the same pid as tt_content uid=1203)
$cmd['tt_content'][1203]['copy'] = 400;  // Copies tt_content uid=1203 to first position in page uid=400
$cmd['tt_content'][1203]['move'] = 400;  // Moves tt_content uid=1203 to the first position in page uid=400
```

### Accessing the uid of copied records {#tce-database-basics-commands-array-accessing-uid-copied}

The `DataHandler` keeps track of records created by `copy`
operations in its `$copyMappingArray_merged` property. This
property is public but marked as `@internal`. So it is subject to change
in future TYPO3 versions without notice.

The `$copyMappingArray_merged` property can be used to determine the UID
of a record copy based on the UID of the copied record.

> [!WARNING]
> **Caution**
>
> The `$copyMappingArray_merged` property should not be mixed up with
> the `$copyMappingArray` property which contains only information
> about the last copy operation and is cleared between each operation.

The structure of the `$copyMappingArray_merged` property looks like this:

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$copyMappingArray_merged = [
   <table> => [
      <original-record-uid> => <record-copy-uid>,
   ],
];
```

The property contains the names of the manipulated tables as keys and a map
of original record UIDs and UIDs of record copies as values.

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$cmd['tt_content'][1203]['copy'] = 400;  // Copies tt_content uid=1203 to first position in page uid=400
$this->dataHandler->start([], $cmd);
$this->dataHandler->process_cmdmap();

$uid = $this->dataHandler->copyMappingArray_merged['tt_content'][1203];
```

## Data array {#datahandler-data}

Syntax: `$data['<tablename>'][<uid>]['<fieldname>'] = 'value'`

Description of keywords in syntax:

-   **tablename**

    -   *Data type:* string

    Name of the database table. There must be a configuration for the table in
    `$GLOBALS['TCA']` array, otherwise it cannot be processed.

### uid {#datahandler-data-uid}

-   **uid**

    -   *Data type:* string|int

    The UID of the record that is modified. If the record already exists,
    this is an integer.

    If you are creating new records, use a random string prefixed with `NEW`,
    for example, `NEW7342abc5e6d`. You can use static strings (`NEW1`, `NEW2`,
    ...) or generate them using
    `\TYPO3\CMS\Core\Utility\StringUtility::getUniqueId('NEW')`.

> [!WARNING]
> **Caution**
>
> If you supply your own string `NEW` must not be followed by an underscore.
> The occurance of an underscore implies a reference to a record in a table.

### fieldname {#datahandler-data-fieldname}

-   **fieldname**

    -   *Data type:* string

    Name of the database field you want to set a value for. The columns of the
    table must be configured in
    [$GLOBALS\['TCA'\]\[$table\]\['columns'\]](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/Columns/Index.html#columns).

### value {#datahandler-data-value}

-   **value**

    -   *Data type:* string

    Value for "fieldname".

    For fields of type [inline](https://docs.typo3.org/m/typo3/reference-tca/main/en-us/ColumnsConfig/Type/Inline/Index.html#columns-inline) this is a
    comma-separated list of UIDs of referenced records.

> [!NOTE]
> For [FlexForms](https://docs.typo3.org/permalink/t3coreapi:flexforms@main) 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.

> [!WARNING]
> **Caution**
>
> Modifying the `sys_file` table using DataHandler is blocked. The table
> **must** not be extended and additional fields should be added to
> `sys_file_metadata`. See [security advisory TYPO3-CORE-SA-2024-006](https://typo3.org/security/advisory/typo3-core-sa-2024-006)
> for more information.

### Examples of data submission {#tce-data-examples}

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

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$data['pages']['NEW9823be87'] = [
    '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:

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$data['pages']['NEW9823be87'] = [
    '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:

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$data['pages']['NEW9823be87'] = [
    'title' => 'Page 1',
    'pid' => '-45'
];
$data['pages']['NEWbe68s587'] = [
    '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 creates a new content record with references to existing and
one new system category:

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$data['sys_category']['NEW9823be87'] = [
    'title' => 'New category',
    'pid' => 1,
];
$data['tt_content']['NEWbe68s587'] = [
    'header' => 'Look ma, categories!',
    'pid' => 45,
    'categories' => '1,2,NEW9823be87', // category uids are listed in a comma-separated string. You can also use placeholders here.
];
```

> [!NOTE]
> To get real uid of the record you have just created use DataHandler's
> `substNEWwithIDs` property like:
> `$uid = $this->dataHandler->substNEWwithIDs['NEW9823be87'];`

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

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$data['pages'][9834] = [
    'title' => 'New title for this page',
    'no_cache' => '1'
];
```

## Clear cache {#tce-clear-cache}

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

**EXT:my_extension/Classes/DataHandling/MyClass.php**

```php
$this->dataHandler->clear_cacheCmd($cacheCmd);
```

Values for the `$cacheCmd` argument:

-   **\[integer\]**

    Clear the cache for the page ID given.

### "all" {#datahandler-clear-cachecmd-all}

-   **"all"**

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

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

### "pages" {#datahandler-clear-cachecmd-pages}

-   **"pages"**

    Clears all pages from `cache_pages`.

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

### Clear cache using cache tags {#tce-cache-hook}

Every processing of data or commands is finalized with flushing a few caches in
the `pages` group. Cache tags are used to specifically flush the
relevant cache entries instead of the cache as whole.

By default the following cache tags are flushed:

-   The table name of the updated record, for example, `pages` when
    updating a page or `tx_myextension_mytable` when updating a record of
    this table.
-   A combination of table name and record UID, for example, `pages_10`
    when updating the page with UID 10 or `tx_myextension_mytable_20` when
    updating the record with UID 20 of this table.
-   A page UID prefixed with `pageID_` (`pageId_<page-uid>`), for
    example, `pageId_10` when updating a page with UID 10 (additionally all
    related pages, see
    [clearcache-pagegrandparent](https://docs.typo3.org/m/typo3/reference-typoscript/main/en-us/PageTsconfig/TceMain.html#pagetcemain-clearcache-pagegrandparent)
    and
    [clearcache-pagesiblingchildren](https://docs.typo3.org/m/typo3/reference-typoscript/main/en-us/PageTsconfig/TceMain.html#pagetcemain-clearcache-pagesiblingchildren))
    and `pageId_10` when updating a record if a record of any table placed
    on the page with UID 10 (`<table>.pid = 10`) is updated.

Notice that you can also use the [\\TYPO3\\CMS\\Core\\Cache\\CacheDataCollector::addCacheTags](https://docs.typo3.org/permalink/t3coreapi:typo3-cms-core-cache-cachedatacollector-addcachetags@main)
method to register additional tags for the cache entry of the current page while
it is rendered. This way you can implement an elaborate caching behavior which
ensures that every record update in the TYPO3 backend (which is processed by the
`DataHandler`) automatically flushes the cache of all pages where that
record is displayed.

Following the rules mentioned above you could register [cache tags](https://docs.typo3.org/permalink/t3coreapi:caching@main)
from within your [Extbase](https://docs.typo3.org/permalink/t3coreapi:extbase-extension-framework@main) plugin (for example, controller or a
custom ViewHelper):

**EXT:my_extension/Classes/Controller/SomeController.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Model\ExampleModel;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Cache\CacheDataCollector;
use TYPO3\CMS\Core\Cache\CacheTag;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

final class SomeController extends ActionController
{
  public function showAction(ExampleModel $example): ResponseInterface
  {
    // ...

    /** @var CacheDataCollector $cacheDataCollector */
    $cacheDataCollector = $this->request->getAttribute('frontend.cache.collector');
    $cacheDataCollector->addCacheTags(
      new CacheTag(sprintf('tx_myextension_example_%d', $example->getUid())),
    );

    // ...

    return $this->htmlResponse();
  }
}

```

### Hook for cache post-processing {#tce-clear-cache-hook-cache-post}

You can configure cache post-processing with a user defined PHP
function. Configuration of the hook can be done from
[`ext_localconf.php`](../../../ExtensionArchitecture/FileStructure/ExtLocalconf.md#file-extension-ext-localconf-php). An example might look like:

**EXT:my_extension/ext_localconf.php**

```php
<?php

declare(strict_types=1);

use Vendor\SomeExtension\Hook\DataHandlerHook;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['clearCachePostProc'][] =
    DataHandlerHook::class . '->postProcessClearCache';

```

## Flags in the DataHandler {#tce-flags}

<!-- TODO: no Markdown rendering for "versionchanged" -->

The following public properties of the PHP class TYPO3CMSCoreDataHandlingDataHandler have been removed:copyWhichTablesneverHideAtCopycopyTreeSee Breaking: #107856 - DataHandler: Remove internal property copyWhichTables and properties neverHideAtCopy and copyTree

There are a few internal variables you can set prior to executing
commands or data submission.

### ->reverseOrder {#datahandler-flags-reverseorder}

-   **->reverseOrder**

    -   *Data type:* boolean
    -   *Default:* false

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