Content Blocks Migration Guide
Compatibility: TYPO3 v13.x and v14.x
Related Skill: typo3-content-blocks - Main Content Blocks development guide
This skill covers bidirectional migration between classic TYPO3 extensions (TCA/SQL/TypoScript) and the modern Content Blocks approach.
1. Migrating Classic Extensions to Content Blocks
This section guides you through converting traditional TYPO3 extensions (with separate TCA, SQL, TypoScript) to the modern Content Blocks approach.
When to Migrate
| Scenario | Recommendation |
|---|---|
| New content elements | ✅ Use Content Blocks from the start |
| Simple records (products, team, events) | ✅ Migrate to Content Blocks |
| Complex Extbase extensions with controllers | ⚠️ Keep Extbase, optionally use Content Blocks for TCA |
| Heavy business logic in domain models | ⚠️ Keep Extbase models, consider Content Blocks for forms only |
| Extensions with many plugins | ❌ Keep traditional approach |
Migration Strategy
┌─────────────────────────────────────────────────────────────────┐
│ 1. ANALYZE │
│ └─ Identify TCA, SQL, TypoScript, Templates │
├─────────────────────────────────────────────────────────────────┤
│ 2. MAP │
│ └─ Create field mapping from TCA columns to Content Blocks │
├─────────────────────────────────────────────────────────────────┤
│ 3. CREATE │
│ └─ Build config.yaml with mapped fields │
├─────────────────────────────────────────────────────────────────┤
│ 4. MIGRATE DATA │
│ └─ Rename columns if needed, update CTypes │
├─────────────────────────────────────────────────────────────────┤
│ 5. CLEANUP │
│ └─ Remove old TCA, SQL, TypoScript files │
└─────────────────────────────────────────────────────────────────┘
TCA to Content Blocks Field Mapping
| TCA Type | TCA renderType | Content Blocks Type | Notes |
|---|---|---|---|
input |
- | Text |
Basic text input |
input |
inputDateTime |
DateTime |
Date/time picker |
input |
inputLink |
Link |
Link browser |
input |
colorPicker |
Color |
Color picker |
input |
slug |
Slug |
URL slug |
text |
- | Textarea |
Multi-line text |
text |
(richtext) | Textarea + enableRichtext: true |
RTE |
check |
- | Checkbox |
Boolean checkbox |
radio |
- | Radio |
Radio buttons |
select |
selectSingle |
Select |
Single selection |
select |
selectMultipleSideBySide |
Select + multiple: true |
Multiple selection |
select |
selectCheckBox |
Select + renderType: selectCheckBox |
Checkbox group |
group |
- | Relation or File |
Depends on internal_type |
file |
- | File |
FAL references |
inline |
- | Collection |
IRRE relations |
category |
- | Category |
System categories |
flex |
- | FlexForm |
FlexForm container |
json |
- | Json |
JSON data |
Migration Example 1: Content Element (tt_content)
BEFORE (Classic):
// Configuration/TCA/Overrides/tt_content.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPlugin(
['LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:ce.hero', 'myext_hero'],
'CType',
'my_ext'
);
$GLOBALS['TCA']['tt_content']['types']['myext_hero'] = [
'showitem' => '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
--palette--;;general,
header,
tx_myext_subheadline,
tx_myext_image,
tx_myext_link,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;;frames,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
--palette--;;hidden,
',
];
$tempColumns = [
'tx_myext_subheadline' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:subheadline',
'config' => [
'type' => 'input',
'size' => 50,
'max' => 255,
],
],
'tx_myext_image' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:image',
'config' => [
'type' => 'file',
'maxitems' => 1,
'allowed' => 'common-image-types',
],
],
'tx_myext_link' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:link',
'config' => [
'type' => 'link',
],
],
];
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('tt_content', $tempColumns);
-- ext_tables.sql
CREATE TABLE tt_content (
tx_myext_subheadline varchar(255) DEFAULT '' NOT NULL,
tx_myext_image int(11) DEFAULT 0 NOT NULL,
tx_myext_link varchar(1024) DEFAULT '' NOT NULL
);
# Configuration/TypoScript/setup.typoscript
tt_content.myext_hero = FLUIDTEMPLATE
tt_content.myext_hero {
templateName = Hero
templateRootPaths.10 = EXT:my_ext/Resources/Private/Templates/
}
AFTER (Content Blocks):
# ContentBlocks/ContentElements/hero/config.yaml
name: myvendor/hero
basics:
- TYPO3/Appearance
- TYPO3/Links
fields:
- identifier: header
useExistingField: true
- identifier: subheadline
type: Text
- identifier: image
type: File
maxitems: 1
allowed: common-image-types
- identifier: link
type: Link
<!-- ContentBlocks/ContentElements/hero/templates/frontend.html -->
<section class="hero">
<f:if condition="{data.image}">
<f:for each="{data.image}" as="img">
<f:image image="{img}" class="hero-bg"/>
</f:for>
</f:if>
<h1>{data.header}</h1>
<f:if condition="{data.subheadline}">
<p>{data.subheadline}</p>
</f:if>
<f:if condition="{data.link}">
<f:link.typolink parameter="{data.link}" class="btn">Learn More</f:link.typolink>
</f:if>
</section>
That's it! No TCA files, no SQL, no TypoScript for rendering.
Migration Example 2: Custom Record Table
BEFORE (Classic):
// Configuration/TCA/tx_myext_domain_model_product.php
return [
'ctrl' => [
'title' => 'Product',
'label' => 'name',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'delete' => 'deleted',
'sortby' => 'sorting',
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'iconfile' => 'EXT:my_ext/Resources/Public/Icons/product.svg',
],
'columns' => [
'hidden' => [
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.hidden',
'config' => ['type' => 'check'],
],
'name' => [
'label' => 'Name',
'config' => [
'type' => 'input',
'size' => 50,
'max' => 255,
'required' => true,
],
],
'description' => [
'label' => 'Description',
'config' => [
'type' => 'text',
'enableRichtext' => true,
],
],
'price' => [
'label' => 'Price',
'config' => [
'type' => 'number',
'format' => 'decimal',
],
],
// ... more columns
],
'types' => [
'1' => ['showitem' => 'hidden, name, price, description'],
],
];
-- ext_tables.sql
CREATE TABLE tx_myext_domain_model_product (
name varchar(255) DEFAULT '' NOT NULL,
description text,
price double(11,2) DEFAULT 0.00 NOT NULL
);
AFTER (Content Blocks):
# ContentBlocks/RecordTypes/product/config.yaml
name: myvendor/product
table: tx_myext_domain_model_product
labelField: name
languageAware: true
sortable: true
softDelete: true
trackCreationDate: true
trackUpdateDate: true
restriction:
disabled: true
startTime: true
endTime: true
security:
ignorePageTypeRestriction: true
fields:
- identifier: name
type: Text
required: true
- identifier: price
type: Number
format: decimal
- identifier: description
type: Textarea
enableRichtext: true
That's it! No TCA file, no SQL file.
Migration Example 3: IRRE Child Records
BEFORE (Classic with IRRE):
// Parent TCA with inline field
'slides' => [
'label' => 'Slides',
'config' => [
'type' => 'inline',
'foreign_table' => 'tx_myext_domain_model_slide',
'foreign_field' => 'parentid',
'foreign_table_field' => 'parenttable',
'maxitems' => 10,
'appearance' => [
'collapseAll' => true,
'levelLinksPosition' => 'both',
'useSortable' => true,
],
],
],
// Separate TCA file for tx_myext_domain_model_slide
// Separate SQL for tx_myext_domain_model_slide
AFTER (Content Blocks with inline Collection):
# ContentBlocks/ContentElements/slider/config.yaml
name: myvendor/slider
fields:
- identifier: slides
type: Collection
labelField: title
maxitems: 10
appearance:
collapseAll: true
levelLinksPosition: both
fields:
- identifier: title
type: Text
- identifier: image
type: File
maxitems: 1
allowed: common-image-types
- identifier: link
type: Link
Or with separate Record Type as child:
# ContentBlocks/RecordTypes/slide/config.yaml
name: myvendor/slide
table: tx_myext_domain_model_slide
labelField: title
fields:
- identifier: title
type: Text
- identifier: image
type: File
maxitems: 1
- identifier: link
type: Link
# ContentBlocks/ContentElements/slider/config.yaml
name: myvendor/slider
fields:
- identifier: slides
type: Collection
foreign_table: tx_myext_domain_model_slide
shareAcrossTables: true
shareAcrossFields: true
Data Migration Script
When migrating existing content, you may need to rename columns and update CType values:
<?php
// Classes/Command/MigrateToContentBlocksCommand.php
namespace MyVendor\MyExt\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class MigrateToContentBlocksCommand extends Command
{
protected function configure(): void
{
$this->setDescription('Migrate classic content elements to Content Blocks');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tt_content');
// Step 1: Update CType from old to new
$oldCType = 'myext_hero';
$newCType = 'myvendor_hero'; // Content Blocks generates: vendor_name
$updated = $connection->update(
'tt_content',
['CType' => $newCType],
['CType' => $oldCType]
);
$output->writeln("Updated $updated records from $oldCType to $newCType");
return Command::SUCCESS;
}
}
Database Column Renaming
If Content Blocks generates different column names, use the Install Tool:
- Create the Content Block (new columns will be detected)
- Go to Admin Tools → Maintenance → Analyze Database Structure
- Add new columns (Content Blocks generated)
- Run migration script to copy data:
-- Copy data from old columns to new columns
UPDATE tt_content
SET myvendor_hero_subheadline = tx_myext_subheadline
WHERE CType = 'myvendor_hero' AND tx_myext_subheadline != '';
-- After verification, drop old columns via Install Tool
Keeping Same Column Names (Recommended)
To avoid data migration, configure Content Blocks to use the same column names:
name: myvendor/hero
prefixFields: false # No automatic prefixing
fields:
- identifier: tx_myext_subheadline # Use exact old column name
type: Text
- identifier: tx_myext_image
type: File
- identifier: tx_myext_link
type: Link
Or use prefixField: false per field:
name: myvendor/hero
prefixFields: true # Enable prefixing by default
fields:
- identifier: tx_myext_subheadline
type: Text
prefixField: false # Keep original column name
Migration Checklist
## Pre-Migration
- [ ] List all content elements and record types to migrate
- [ ] Document existing TCA column names
- [ ] Backup database
- [ ] Install friendsoftypo3/content-blocks
## For Each Content Type
- [ ] Create config.yaml with field mappings
- [ ] Create frontend.html template
- [ ] Create backend-preview.html (optional)
- [ ] Create labels.xlf translations
- [ ] Run cache:flush and extension:setup
## Data Migration
- [ ] Update CType values in database
- [ ] Rename columns if needed (or use prefixField: false)
- [ ] Verify data displays correctly
## Cleanup
- [ ] Remove old TCA files
- [ ] Remove ext_tables.sql (or remove migrated columns)
- [ ] Remove old TypoScript rendering config
- [ ] Remove old Fluid templates
- [ ] Update documentation
Files to Delete After Migration
| Classic File | Why Remove |
|---|---|
Configuration/TCA/*.php |
Replaced by config.yaml |
Configuration/TCA/Overrides/tt_content.php |
Replaced by config.yaml |
ext_tables.sql |
Auto-generated by Content Blocks |
Configuration/TypoScript/setup.typoscript (rendering part) |
Auto-registered |
Resources/Private/Templates/ContentElements/*.html |
Moved to ContentBlocks folder |
2. Reverting Content Blocks to Classic Extension Format
This section guides you through converting Content Blocks back to traditional TYPO3 extension format with separate TCA, SQL, and TypoScript files.
When to Revert
| Scenario | Recommendation |
|---|---|
| Content Blocks has breaking changes | ✅ Revert to classic for stability |
| Need TCA features not supported by Content Blocks | ✅ Revert for full TCA control |
| Team prefers traditional TYPO3 structure | ✅ Revert for familiarity |
| Complex Extbase domain models needed | ✅ Revert for full Extbase integration |
| Performance-critical applications | ⚠️ Consider revert (measure first) |
| Simple content elements working fine | ❌ Keep Content Blocks |
Revert Strategy
┌─────────────────────────────────────────────────────────────────┐
│ 1. ANALYZE │
│ └─ Document all config.yaml files and field mappings │
├─────────────────────────────────────────────────────────────────┤
│ 2. GENERATE │
│ └─ Create TCA, SQL, TypoScript from config.yaml │
├─────────────────────────────────────────────────────────────────┤
│ 3. MOVE TEMPLATES │
│ └─ Move Fluid templates to traditional locations │
├─────────────────────────────────────────────────────────────────┤
│ 4. MIGRATE DATA │
│ └─ Update CTypes, rename columns if needed │
├─────────────────────────────────────────────────────────────────┤
│ 5. REMOVE DEPENDENCY │
│ └─ Uninstall Content Blocks, delete ContentBlocks folder │
└─────────────────────────────────────────────────────────────────┘
Content Blocks to TCA Field Mapping (Reverse)
| Content Blocks Type | TCA type | TCA renderType | Additional Config |
|---|---|---|---|
Text |
input |
- | max => 255 |
Textarea |
text |
- | rows => 5 |
Textarea + enableRichtext |
text |
- | enableRichtext => true |
Email |
email |
- | - |
Link |
link |
- | - |
Number |
number |
- | format => 'integer' or 'decimal' |
DateTime |
datetime |
- | format => 'date' or 'datetime' |
Color |
color |
- | - |
Checkbox |
check |
- | - |
Radio |
radio |
- | items => [...] |
Slug |
slug |
- | generatorOptions => [...] |
Password |
password |
- | - |
Select |
select |
selectSingle |
items => [...] |
Select + multiple |
select |
selectMultipleSideBySide |
- |
File |
file |
- | allowed => '...' |
Relation |
group or select |
- | foreign_table => '...' |
Category |
category |
- | - |
Collection (inline) |
inline |
- | foreign_table => '...' |
Collection (with fields) |
inline |
- | foreign_table => auto-generated |
FlexForm |
flex |
- | ds => [...] |
Json |
json |
- | - |
Tab |
- | - | --div--;Label in showitem |
Palette |
- | - | --palette--;;name in showitem |
YAML Options to TCA Config Mapping
| Content Blocks YAML | TCA Config Key | Example |
|---|---|---|
required: true |
required => true |
- |
default: "value" |
default => 'value' |
- |
placeholder: "text" |
placeholder => 'text' |
- |
minitems: 1 |
minitems => 1 |
- |
maxitems: 10 |
maxitems => 10 |
- |
allowed: common-image-types |
allowed => 'common-image-types' |
- |
displayCond: 'FIELD:x:=:1' |
displayCond => 'FIELD:x:=:1' |
- |
onChange: reload |
onChange => 'reload' |
- |
labelField: name |
ctrl['label'] => 'name' |
Record Types |
languageAware: true |
ctrl['languageField'] etc. |
Record Types |
sortable: true |
ctrl['sortby'] => 'sorting' |
Record Types |
softDelete: true |
ctrl['delete'] => 'deleted' |
Record Types |
Revert Example: Content Element
BEFORE (Content Blocks):
# ContentBlocks/ContentElements/hero/config.yaml
name: myvendor/hero
basics:
- TYPO3/Appearance
- TYPO3/Links
fields:
- identifier: header
useExistingField: true
- identifier: subheadline
type: Text
- identifier: hero_image
type: File
maxitems: 1
allowed: common-image-types
- identifier: cta_link
type: Link
- identifier: cta_text
type: Text
AFTER (Classic):
Step 1: Create TCA Override
<?php
// Configuration/TCA/Overrides/tt_content.php
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
defined('TYPO3') or die();
// Register the content element
ExtensionManagementUtility::addPlugin(
[
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:ce.hero.title',
'value' => 'myext_hero',
'icon' => 'EXT:my_ext/Resources/Public/Icons/ContentElements/hero.svg',
'group' => 'default',
],
'CType',
'my_ext'
);
// Define columns
$tempColumns = [
'tx_myext_subheadline' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:field.subheadline',
'config' => [
'type' => 'input',
'size' => 50,
'max' => 255,
],
],
'tx_myext_hero_image' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:field.hero_image',
'config' => [
'type' => 'file',
'maxitems' => 1,
'allowed' => 'common-image-types',
],
],
'tx_myext_cta_link' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:field.cta_link',
'config' => [
'type' => 'link',
],
],
'tx_myext_cta_text' => [
'label' => 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:field.cta_text',
'config' => [
'type' => 'input',
'size' => 30,
'max' => 100,
],
],
];
ExtensionManagementUtility::addTCAcolumns('tt_content', $tempColumns);
// Define showitem
$GLOBALS['TCA']['tt_content']['types']['myext_hero'] = [
'showitem' => '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
--palette--;;general,
header,
tx_myext_subheadline,
tx_myext_hero_image,
tx_myext_cta_link,
tx_myext_cta_text,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;;frames,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
--palette--;;hidden,
',
];
Step 2: Create SQL Schema
-- ext_tables.sql
CREATE TABLE tt_content (
tx_myext_subheadline varchar(255) DEFAULT '' NOT NULL,
tx_myext_hero_image int(11) unsigned DEFAULT 0 NOT NULL,
tx_myext_cta_link varchar(1024) DEFAULT '' NOT NULL,
tx_myext_cta_text varchar(100) DEFAULT '' NOT NULL
);
Step 3: Create TypoScript Rendering
# Configuration/TypoScript/setup.typoscript
tt_content.myext_hero = FLUIDTEMPLATE
tt_content.myext_hero {
templateName = Hero
templateRootPaths {
10 = EXT:my_ext/Resources/Private/Templates/ContentElements/
}
dataProcessing {
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
10 {
references.fieldName = tx_myext_hero_image
as = heroImages
}
}
}
Step 4: Update Fluid Template
<!-- Resources/Private/Templates/ContentElements/Hero.html -->
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:layout name="Default"/>
<f:section name="Main">
<section class="hero-banner">
<f:if condition="{heroImages}">
<f:for each="{heroImages}" as="image">
<f:image image="{image}" alt="{data.header}" class="hero-image"/>
</f:for>
</f:if>
<div class="hero-content">
<h1>{data.header}</h1>
<f:if condition="{data.tx_myext_subheadline}">
<p class="subheadline">{data.tx_myext_subheadline}</p>
</f:if>
<f:if condition="{data.tx_myext_cta_link}">
<f:link.typolink parameter="{data.tx_myext_cta_link}" class="btn btn-primary">
{data.tx_myext_cta_text -> f:or(default: 'Learn more')}
</f:link.typolink>
</f:if>
</div>
</section>
</f:section>
</html>
Data Migration Script (Revert Direction)
<?php
// Classes/Command/RevertFromContentBlocksCommand.php
namespace MyVendor\MyExt\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
#[AsCommand(
name: 'myext:revert-content-blocks',
description: 'Revert Content Blocks elements to classic TCA format'
)]
class RevertFromContentBlocksCommand extends Command
{
private const CTYPE_MAPPING = [
'myvendor_hero' => 'myext_hero',
'myvendor_accordion' => 'myext_accordion',
];
private const COLUMN_MAPPING = [
'myvendor_hero_subheadline' => 'tx_myext_subheadline',
'myvendor_hero_hero_image' => 'tx_myext_hero_image',
];
protected function configure(): void
{
$this
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Show what would be changed')
->addOption('copy-data', null, InputOption::VALUE_NONE, 'Copy column data');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$dryRun = $input->getOption('dry-run');
$copyData = $input->getOption('copy-data');
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tt_content');
$io->title('Reverting Content Blocks to Classic Format');
// Update CTypes
foreach (self::CTYPE_MAPPING as $oldCType => $newCType) {
$count = $connection->count('*', 'tt_content', ['CType' => $oldCType]);
$io->writeln(" $oldCType → $newCType: $count records");
if (!$dryRun && $count > 0) {
$connection->update('tt_content', ['CType' => $newCType], ['CType' => $oldCType]);
}
}
// Copy column data if requested
if ($copyData && !$dryRun) {
foreach (self::COLUMN_MAPPING as $oldColumn => $newColumn) {
try {
$connection->executeStatement(
"UPDATE tt_content SET $newColumn = $oldColumn WHERE $oldColumn IS NOT NULL AND $oldColumn != ''"
);
} catch (\Exception $e) {
$io->warning("Column copy failed: " . $e->getMessage());
}
}
}
$io->success($dryRun ? 'Dry run completed' : 'Migration completed');
return Command::SUCCESS;
}
}
Template Variable Changes
When reverting, update Fluid template variable names:
| Content Blocks | Classic |
|---|---|
{data.fieldname} |
{data.tx_myext_fieldname} |
{data.my_image} → auto-resolved |
Use DataProcessor + {processedImages} |
{data.collection_items} → auto-resolved |
Use DatabaseQueryProcessor + {items} |
{cb:assetPath()} |
Static path: EXT:my_ext/Resources/Public/... |
{cb:languagePath()} |
LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf: |
Complete Revert Checklist
## Pre-Revert
- [ ] Document all Content Blocks config.yaml files
- [ ] Map field identifiers to TCA column names
- [ ] Backup database
- [ ] Create branch for revert work
## For Each Content Type
- [ ] Create TCA PHP file(s)
- [ ] Add columns to ext_tables.sql
- [ ] Create TypoScript rendering
- [ ] Move/update Fluid templates (update variable names!)
- [ ] Move translations to locallang.xlf
- [ ] Move icons to Resources/Public/Icons/
## Data Migration
- [ ] Update CType values in database
- [ ] Copy data from Content Blocks columns to classic columns
- [ ] Test data displays correctly
- [ ] Verify file relations work
## Cleanup
- [ ] Remove ContentBlocks folder
- [ ] Remove friendsoftypo3/content-blocks from composer.json
- [ ] Run: composer update
- [ ] Remove old columns via Install Tool (after verification)
- [ ] Clear all caches
## Testing
- [ ] All content elements render correctly
- [ ] All record types editable in backend
- [ ] File relations display correctly
- [ ] Translations work
- [ ] No PHP errors in log
Removing Content Blocks Dependency
# After successful revert and testing
ddev composer remove friendsoftypo3/content-blocks
# Clear caches
ddev typo3 cache:flush
# Update database (remove orphaned columns)
# Go to Admin Tools → Maintenance → Analyze Database Structure
# Select "Remove" for the old Content Blocks columns
References
Credits & Attribution
This skill is part of the webconsulting.at TYPO3 skills collection, adapted from the official Content Blocks documentation.