typo3-datahandler
dirnbauer/typo3-datahandler-skillAgent Skill for TYPO3 DataHandler patterns. Use when working with TYPO3 database operations, record manipulation (create, update, copy, move, delete, localize), workspaces, versioning, staging, publishing, TCEmain hooks, cache clearing, or backend record processing. Essential for TYPO3 extension development.
SKILL.md
name: typo3-datahandler description: Agent Skill for TYPO3 DataHandler patterns. Use when working with TYPO3 database operations, record manipulation (create, update, copy, move, delete, localize), workspaces, versioning, staging, publishing, TCEmain hooks, cache clearing, or backend record processing. Essential for TYPO3 extension development.
TYPO3 DataHandler Skill
Expert patterns for working with TYPO3's DataHandler (formerly TCEmain) - the central API for all database record operations in the TYPO3 backend.
What is DataHandler?
The DataHandler (\TYPO3\CMS\Core\DataHandling\DataHandler) is TYPO3's core class for:
- Creating new records in any TCA-configured table
- Updating existing records
- Copying records (with optional child elements)
- Moving records between pages
- Deleting/Undeleting records (soft delete)
- Localizing records for multilingual sites
- Versioning and Workspaces (staging, publishing, approval workflows)
- Cache clearing operations
All operations respect TCA configuration, user permissions, and workspace states.
Workspaces Overview
Workspaces allow staging changes before publishing:
- Live workspace (
id = 0): Published content visible to visitors - Custom workspaces (
id > 0): Staging areas for draft changes - DataHandler automatically creates versions when editing in a workspace
- Use
versioncommand for publish/swap/discard operations
Quick Reference
Basic Usage Pattern
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
// NEVER inject DataHandler - always create new instance
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start($data, $cmd);
$dataHandler->process_datamap(); // For data operations
$dataHandler->process_cmdmap(); // For command operations
// Check for errors
if ($dataHandler->errorLog !== []) {
foreach ($dataHandler->errorLog as $error) {
// Handle error
}
}
Data Array Syntax (Create/Update)
// Create new record (use NEW + unique ID)
$data['pages']['NEW123'] = [
'pid' => 1, // Parent page ID
'title' => 'My Page',
'hidden' => 0,
];
// Update existing record
$data['tt_content'][42] = [
'header' => 'Updated Header',
'bodytext' => 'New content',
];
// Reference NEW record in another NEW record
$data['sys_category']['NEW_cat'] = ['title' => 'New Category', 'pid' => 1];
$data['tt_content']['NEW_content'] = [
'pid' => 1,
'categories' => 'NEW_cat', // References the category above
];
Command Array Syntax (Copy/Move/Delete)
// Delete record
$cmd['tt_content'][42]['delete'] = 1;
// Undelete record
$cmd['tt_content'][42]['undelete'] = 1;
// Copy to page (positive = first in page)
$cmd['tt_content'][42]['copy'] = 10;
// Copy after record (negative = after that UID)
$cmd['tt_content'][42]['copy'] = -55;
// Move to page
$cmd['tt_content'][42]['move'] = 10;
// Localize to language
$cmd['tt_content'][42]['localize'] = 2; // Language UID
// Copy to language (free mode, no parent reference)
$cmd['tt_content'][42]['copyToLanguage'] = 2;
Expertise Areas
Data Operations (process_datamap())
- Record creation with NEW placeholders
- Record updates with field values
- Inline (IRRE) record handling
- FlexForm data submission
- File references (FAL)
- MM relation handling
Command Operations (process_cmdmap())
copy- Duplicate records with childrenmove- Relocate recordsdelete- Soft delete (respects TCA delete field)undelete- Restore deleted recordslocalize- Create language overlays (connected mode)copyToLanguage- Copy to language (free mode)version- Workspace operations
Getting Created Record UIDs
// After process_datamap(), get the real UID
$realUid = $dataHandler->substNEWwithIDs['NEW123'];
// For copied records
$dataHandler->copyMappingArray_merged['tt_content'][42]; // Returns new UID
Cache Operations
$dataHandler->start([], []);
$dataHandler->clear_cacheCmd('pages'); // Clear all page cache
$dataHandler->clear_cacheCmd('all'); // Clear all caches (admin only)
$dataHandler->clear_cacheCmd(123); // Clear specific page cache
Reference Files
references/data-array.md- Data array patterns and examplesreferences/command-array.md- Command operations referencereferences/workspaces.md- Workspaces, versioning, staging, publishingreferences/hooks-events.md- Hook into DataHandler operationsreferences/common-patterns.md- Typical use cases and solutions
Critical Rules
⚠️ NEVER Inject DataHandler
// ❌ WRONG - DataHandler is stateful
public function __construct(private DataHandler $dataHandler) {}
// ✅ CORRECT - Always create new instance
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
⚠️ Backend Context Required
DataHandler requires $GLOBALS['BE_USER']. In CLI commands:
\TYPO3\CMS\Core\Core\Bootstrap::initializeBackendAuthentication();
⚠️ TCA Configuration Required
Fields must be configured in TCA to be processed. If type is none or invalid, DataHandler ignores the field.
⚠️ sys_file Table Blocked
Direct modifications to sys_file are blocked since TYPO3 11.5.35/12.4.11/13.0.1 for security.
Common Patterns
Create Page with Content
$data = [
'pages' => [
'NEW_page' => [
'pid' => 1,
'title' => 'New Page',
'doktype' => 1,
],
],
'tt_content' => [
'NEW_content' => [
'pid' => 'NEW_page', // Reference to new page
'CType' => 'text',
'header' => 'Welcome',
'bodytext' => 'Content here',
],
],
];
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start($data, []);
$dataHandler->process_datamap();
$pageUid = $dataHandler->substNEWwithIDs['NEW_page'];
$contentUid = $dataHandler->substNEWwithIDs['NEW_content'];
Bulk Operations with Ordering
// Create multiple records in correct order
$data = [
'pages' => [
'NEW_1' => ['pid' => 1, 'title' => 'Page 1'],
'NEW_2' => ['pid' => 1, 'title' => 'Page 2'],
],
];
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->reverseOrder = true; // Preserve array order
$dataHandler->start($data, []);
$dataHandler->process_datamap();
Copy Record with Modifications
$cmd = [
'tt_content' => [
42 => [
'copy' => [
'action' => 'paste',
'target' => 10, // Page UID
'update' => [
'header' => 'Copied: ' . $originalHeader,
],
],
],
],
];
Workspace Operations
// Publish (swap) a workspace version
$cmd['pages'][42]['version'] = [
'action' => 'swap',
'swapWith' => 123, // Workspace version UID
];
// Discard workspace changes
$cmd['pages'][42]['version'] = [
'action' => 'clearWSID',
];
// Set approval stage
$cmd['pages'][42]['version'] = [
'action' => 'setStage',
'stageId' => 10, // -1=rejected, 0=editing, 1=review, 10=publish
'comment' => 'Ready for review',
];
// Bypass workspace restrictions (use carefully!)
$dataHandler->bypassWorkspaceRestrictions = true;
See references/workspaces.md for complete workspace patterns.
Hooking Into DataHandler
Legacy Hooks (still supported)
// ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]
= \MyVendor\MyExt\Hook\DataHandlerHook::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][]
= \MyVendor\MyExt\Hook\DataHandlerHook::class;
Hook Methods
class DataHandlerHook
{
// Before record is saved
public function processDatamap_preProcessFieldArray(
array &$fieldArray,
string $table,
string|int $id,
DataHandler $dataHandler
): void {}
// After record is saved
public function processDatamap_postProcessFieldArray(
string $status, // 'new' or 'update'
string $table,
string|int $id,
array $fieldArray,
DataHandler $dataHandler
): void {}
// After all records processed
public function processDatamap_afterAllOperations(DataHandler $dataHandler): void {}
// Before command executed
public function processCmdmap_preProcess(
string $command,
string $table,
int $id,
mixed $value,
DataHandler $dataHandler,
bool|string $pasteUpdate
): void {}
// After command executed
public function processCmdmap_postProcess(
string $command,
string $table,
int $id,
mixed $value,
DataHandler $dataHandler,
bool|string $pasteUpdate,
bool|string $pasteDatamap
): void {}
}
Error Handling
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start($data, $cmd);
$dataHandler->process_datamap();
$dataHandler->process_cmdmap();
if ($dataHandler->errorLog !== []) {
foreach ($dataHandler->errorLog as $error) {
$this->logger->error('DataHandler error', ['message' => $error]);
}
}
Verification Checklist
Before using DataHandler:
- TCA is properly configured for all tables/fields
- Backend user context exists (
$GLOBALS['BE_USER']) - User has permissions for the operation
- Creating new instance (not injecting)
- Checking
errorLogafter operations - Using
substNEWwithIDsto get created record UIDs
Documentation: TYPO3 DataHandler Reference
README
TYPO3 DataHandler Skill
Note: We're testing AI agent skills here. Maybe useful, maybe not. :)
Agent Skill for TYPO3 DataHandler (formerly TCEmain) patterns - the central API for all database record operations in TYPO3.
🔌 Compatibility
This is an Agent Skill following the Agent Skills specification developed by Anthropic.
Supported Platforms:
- ✅ Cursor (via
.cursor/skills/or.cursor/rules/) - ✅ Claude Code (via
~/.claude/skills/or project.claude/skills/) - ✅ GitHub Copilot (via skills-compatible configuration)
- ✅ Other skills-compatible AI agents
Features
- Data Operations: Record creation, updates, inline (IRRE) handling, FlexForm data
- Command Operations: Copy, move, delete, undelete, localize, version
- Workspaces & Versioning: Staging, publishing, approval workflows, workspace restrictions
- Hooks & Events: Extension points for intercepting operations
- Cache Management: Programmatic cache clearing
- Error Handling: Proper error checking patterns
- CLI Support: Using DataHandler in Symfony commands
Installation
Cursor (This Project)
The skill is already installed at .cursor/skills/typo3-datahandler-skill/.
A Cursor rule file is also available at .cursor/rules/typo3-datahandler.mdc for automatic activation when working with PHP files.
Claude Code
Option 1: Project-specific (Recommended)
Copy the skill folder to your project:
mkdir -p .claude/skills
cp -r .cursor/skills/typo3-datahandler-skill .claude/skills/
Option 2: Global Installation
Install globally for all projects:
mkdir -p ~/.claude/skills
cp -r .cursor/skills/typo3-datahandler-skill ~/.claude/skills/
Option 3: From Repository
# Clone and install
git clone https://github.com/dirnbauer/typo3-datahandler-skill.git ~/.claude/skills/typo3-datahandler-skill
Usage
This skill is automatically triggered when:
- Working with TYPO3 record operations (create, update, delete)
- Implementing DataHandler hooks or events
- Writing CLI commands that modify database records
- Copying/moving pages or content
- Implementing localization features
- Managing cache programmatically
Example Queries
- "Create a new page with DataHandler"
- "How do I hook into record save operations?"
- "Copy a content element to another page"
- "Delete records programmatically in TYPO3"
- "Use DataHandler in a Symfony command"
- "Publish workspace changes programmatically"
- "How do workspaces affect DataHandler operations?"
- "Set approval stage for workspace records"
Structure
typo3-datahandler-skill/
├── SKILL.md # Main skill file with core patterns
├── README.md # This file
└── references/
├── data-array.md # Data array syntax and examples
├── command-array.md # Command operations reference
├── workspaces.md # Workspaces, versioning, staging, publishing
├── hooks-events.md # DataHandler extension points
└── common-patterns.md # Real-world usage patterns
Quick Reference
Basic Usage
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;
// Always create new instance
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start($data, $cmd);
$dataHandler->process_datamap();
$dataHandler->process_cmdmap();
// Check errors
if ($dataHandler->errorLog !== []) {
// Handle errors
}
Create Record
$data['pages']['NEW_page'] = [
'pid' => 1,
'title' => 'New Page',
];
Update Record
$data['pages'][42] = [
'title' => 'Updated Title',
];
Delete Record
$cmd['pages'][42]['delete'] = 1;
Copy Record
$cmd['pages'][42]['copy'] = 10; // To page 10
// After process_cmdmap():
$newUid = $dataHandler->copyMappingArray_merged['pages'][42];
Documentation
License
MIT License - See LICENSE for details.
Credits
Inspired by Netresearch's PHP Modernization Skill and the Anthropic Agent Skills specification.