.. _adr-002-ckeditor-integration:
===========================================================
ADR-002: Native CKEditor 5 vs Custom TYPO3 Image Plugin
===========================================================
:Status: Accepted
:Date: 2025-11-09
:Authors: Development Team
:Context: RTE CKEditor Image Extension for TYPO3
.. contents:: Table of Contents
:depth: 3
:local:
Context and Problem Statement
==============================
TYPO3 integrates CKEditor 5 as its Rich Text Editor (RTE), and images are a fundamental content element. CKEditor 5 provides comprehensive native image plugins (Image, ImageCaption, ImageToolbar, ImageResize, ImageStyle, LinkImage) with excellent WYSIWYG capabilities including:
- Inline editable captions
- Contextual toolbars on image click
- Visual resize handles
- Pre-defined image styles (alignment, sizing)
- Image linking capabilities
- Text alternative (alt) editing
However, TYPO3 has specific requirements for file handling through its File Abstraction Layer (FAL) that are incompatible with CKEditor 5's native image implementation.
**The Question**: Should we use CKEditor 5's native image plugins or implement a custom plugin specifically for TYPO3?
Decision Drivers
================
TYPO3 Core Requirements
------------------------
- **FAL Integration**: All files must be managed through TYPO3's File Abstraction Layer
- **Reference Tracking**: ``sys_file_reference`` database records for all file usage
- **Magic Image Processing**: TYPO3's automatic image optimization and variant generation
- **Security**: File access permissions and public/non-public file handling
- **Backend Integration**: File selector dialog and metadata management
User Experience Requirements
-----------------------------
- **WYSIWYG Editing**: Inline caption editing, visual resize, contextual toolbars
- **Accessibility**: Alt text, semantic HTML structure
- **Flexibility**: Image styles, linking, sizing options
- **Performance**: Optimized image delivery
Technical Requirements
-----------------------
- **Data Persistence**: FAL attributes must survive RTE → DB → RTE round-trips
- **Backend Rendering**: PHP-based frontend rendering with TypoScript integration
- **Compatibility**: Work across TYPO3 versions (v12/v13)
Considered Options
==================
Option 1: Use CKEditor 5 Native Image Plugins
----------------------------------------------
Use the official CKEditor 5 image feature set:
- ``@ckeditor/ckeditor5-image`` (base Image plugin)
- ``@ckeditor/ckeditor5-image/imagecaption`` (ImageCaption)
- ``@ckeditor/ckeditor5-image/imagetoolbar`` (ImageToolbar)
- ``@ckeditor/ckeditor5-image/imageresize`` (ImageResize)
- ``@ckeditor/ckeditor5-image/imagestyle`` (ImageStyle)
- ``@ckeditor/ckeditor5-link/linkimage`` (LinkImage)
**Pros**:
- ✅ Excellent WYSIWYG experience out-of-the-box
- ✅ Inline editable captions with proper UX
- ✅ Contextual balloon toolbar on image selection
- ✅ Visual resize handles with dimension control
- ✅ Pre-built image styles (alignment, sizing)
- ✅ Official support and documentation
- ✅ Regular updates and bug fixes
- ✅ Accessibility features built-in
- ✅ Tested across browsers and devices
**Cons**:
- ❌ **No FAL Support**: Uses direct image URLs (``src`` attribute), not FAL file references
- ❌ **No Reference Tracking**: Cannot create ``sys_file_reference`` records
- ❌ **No Magic Image Processing**: Cannot integrate with TYPO3's image processing pipeline
- ❌ **No Backend Integration**: Cannot use TYPO3's file selector dialog
- ❌ **Data Attribute Loss**: Cannot store FAL-specific metadata (``data-htmlarea-file-uid``, ``data-htmlarea-file-table``)
- ❌ **Security Issues**: Cannot enforce TYPO3's file access permissions
- ❌ **Frontend Rendering Incompatibility**: PHP rendering expects FAL attributes, not direct URLs
- ❌ **TYPO3 Core Explicitly Disables It**: Core configuration removes the plugin
Option 2: Custom TYPO3 Image Plugin (Selected)
-----------------------------------------------
Implement a custom CKEditor 5 plugin (``typo3image``) that:
- Uses custom model element (``typo3image`` instead of ``imageBlock``)
- Stores FAL attributes in the model
- Integrates with TYPO3's file selector dialog
- Provides backend rendering through PHP controllers
- Supports magic image processing
**Pros**:
- ✅ **Full FAL Integration**: Stores file UID and table references
- ✅ **Reference Tracking**: Creates proper ``sys_file_reference`` records
- ✅ **Magic Image Processing**: Full integration with TYPO3's image pipeline
- ✅ **Backend Integration**: Uses TYPO3's file selector dialog
- ✅ **Data Persistence**: All FAL attributes preserved through save/load cycles
- ✅ **Security**: Respects TYPO3's file access permissions
- ✅ **Frontend Rendering**: PHP controllers handle all rendering logic
- ✅ **TypoScript Integration**: Full control via TypoScript configuration
- ✅ **Feature Control**: Can implement exactly what TYPO3 needs
**Cons**:
- ❌ Must implement WYSIWYG features manually (captions, toolbar, resize, styles)
- ❌ Higher development and maintenance effort
- ❌ Requires deep CKEditor 5 plugin architecture knowledge
- ❌ Must keep up with CKEditor 5 API changes
- ❌ More complex than using native plugins
Option 3: Hybrid Approach (Native Plugins + FAL Bridge)
--------------------------------------------------------
Use native CKEditor 5 plugins but add a bridge layer to convert to/from FAL.
**Pros**:
- ✅ WYSIWYG features from native plugins
- ✅ Potential FAL integration through conversion
**Cons**:
- ❌ **Model Incompatibility**: Native ``imageBlock`` model doesn't support FAL attributes
- ❌ **Data Loss Risk**: Conversion between incompatible models prone to errors
- ❌ **Plugin Conflicts**: Native plugins expect specific model schema
- ❌ **Maintenance Nightmare**: Must bridge two incompatible architectures
- ❌ **TYPO3 Core Still Disables Native Plugin**: Cannot be used without core modifications
Decision Outcome
================
**Chosen option**: Option 2 - Custom TYPO3 Image Plugin
The custom plugin approach is the **only viable option** for TYPO3 integration.
Evidence and Technical Justification
=====================================
1. TYPO3 Core Explicitly Disables Native Image Plugin
------------------------------------------------------
**File**: ``typo3/sysext/rte_ckeditor/Configuration/RTE/Default.yaml``
.. code-block:: yaml
editor:
config:
removePlugins:
- image # ← Native Image plugin is disabled
**Source**: TYPO3 Core Documentation
"By default, images in CKE are disabled within configuration typo3/sysext/rte_ckeditor/Configuration/RTE/Default.yaml with removePlugins: - image"
This is **not accidental** — it's a deliberate architectural decision by the TYPO3 core team.
2. FAL Requirements Incompatible with Native Plugins
-----------------------------------------------------
Required FAL Data Attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: html
**Why These Matter**:
- ``data-htmlarea-file-uid``: Links to ``sys_file`` record for reference tracking
- ``data-htmlarea-file-table``: Specifies FAL table (always ``sys_file``)
- These attributes trigger ``sys_file_reference`` creation in TYPO3 backend
- Without them, TYPO3 cannot track where files are used
- Reference index breaks, file deletion safety checks fail
Native Plugin Model
~~~~~~~~~~~~~~~~~~~
.. code-block:: javascript
// CKEditor 5 native imageBlock model
editor.model.schema.register('imageBlock', {
allowAttributes: ['src', 'alt', 'srcset', 'sizes']
// ❌ No support for data-* attributes
// ❌ No FAL metadata storage
});
3. Magic Image Processing Requirements
---------------------------------------
**Frontend Rendering Flow**:
.. code-block:: text
1. RTE saves:
2. TYPO3 Backend: Creates sys_file_reference record
3. Frontend Rendering (PHP):
a. Load file from FAL via UID
b. Apply TypoScript configuration
c. Generate processed image variant
d. Output:
**TypoScript Hook**:
.. code-block:: typoscript
:caption: setup.typoscript:12
lib.parseFunc_RTE {
tags.img = TEXT
tags.img {
preUserFunc = Netresearch\RteCKEditorImage\Controller\ImageRenderingController->renderImageAttributes
}
}
**Why Native Plugins Can't Support This**:
- Native plugins use direct ``src`` URLs, not FAL UIDs
- ``ImageRenderingController->renderImageAttributes()`` expects FAL data attributes
- No way to look up file in FAL without UID
- Cannot apply magic image processing without FAL context
4. Backend Integration Requirements
------------------------------------
**TYPO3 File Selector Dialog**:
.. code-block:: javascript
:caption: typo3image.js:349
externalPlugins: {
typo3image: {
route: "rteckeditorimage_wizard_select_image" // ← TYPO3 backend route
}
}
**Flow**:
1. User clicks "Insert Image" button
2. Custom plugin opens TYPO3's file selector dialog
3. User selects file from FAL storage
4. Dialog returns ``{ fileUid: 123, fileTable: 'sys_file', ... }``
5. Plugin creates ``typo3image`` model element with FAL attributes
**Native Plugin Limitation**:
- Native ``uploadImage`` plugin expects file upload or URL input
- No integration point for TYPO3's file selector
- Cannot access FAL metadata
- Cannot select existing files from storage
5. Database Reference Tracking
-------------------------------
**sys_file_reference Table**:
.. code-block:: sql
CREATE TABLE sys_file_reference (
uid_local int, -- Points to sys_file.uid
uid_foreign int, -- Points to tt_content.uid
tablenames varchar, -- 'tt_content'
fieldname varchar -- 'bodytext'
);
**Created by**:
.. code-block:: php
:caption: Classes/Database/RteImagesDbHook.php:18
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][]
= RteImagesDbHook::class;
**Why This Matters**:
- TYPO3's "Used on" feature shows where images are referenced
- Prevents deleting images still in use
- File usage statistics
- Broken link detection
- **Requires FAL UIDs** — native plugins provide only URLs
Implementation: Custom Plugin Architecture
===========================================
Model Schema
------------
.. code-block:: javascript
:caption: typo3image.js:871-894
editor.model.schema.register('typo3image', {
inheritAllFrom: '$blockObject',
allowAttributes: [
'fileUid', // FAL: sys_file.uid
'fileTable', // FAL: table name (sys_file)
'src', // Image URL (for editing preview)
'alt', // Accessibility
'title', // Image title
'width', // Display dimensions
'height', // Display dimensions
'enableZoom', // Click-to-enlarge
'noScale', // Skip image processing
'quality', // Processing quality multiplier
'caption', // Image caption text
'htmlA', // Link wrapper attributes
// ... more FAL-specific attributes
]
});
Upcast Converter (HTML → Model)
--------------------------------
.. code-block:: javascript
:caption: typo3image.js:1061-1077
editor.conversion.for('upcast').elementToElement({
view: {
name: 'img',
attributes: {
'data-htmlarea-file-uid': true // ← FAL attribute required
}
},
model: (viewElement, { writer }) => {
return writer.createElement('typo3image', {
fileUid: viewElement.getAttribute('data-htmlarea-file-uid'),
fileTable: viewElement.getAttribute('data-htmlarea-file-table'),
src: viewElement.getAttribute('src'),
// ... extract all FAL attributes
});
}
});
Downcast Converter (Model → HTML)
----------------------------------
.. code-block:: javascript
:caption: typo3image.js:1113-1129
editor.conversion.for('dataDowncast').elementToElement({
model: 'typo3image',
view: (modelElement, { writer }) => {
const img = writer.createEmptyElement('img', {
'src': modelElement.getAttribute('src'),
'data-htmlarea-file-uid': modelElement.getAttribute('fileUid'),
'data-htmlarea-file-table': modelElement.getAttribute('fileTable'),
// ... output all FAL attributes
});
return toWidget(figure, writer);
}
});
Frontend Rendering
------------------
.. code-block:: php
:caption: ImageRenderingController.php (conceptual)
public function renderImageAttributes(string $content, array $conf): string
{
// Extract data-htmlarea-file-uid from HTML
$fileUid = $this->extractFileUid($content);
// Load file from FAL
$file = $this->resourceFactory->getFileObject($fileUid);
// Apply magic image processing
$processedImage = $this->imageService->applyProcessingInstructions($file, [
'width' => $width,
'height' => $height,
'crop' => $cropData,
]);
// Generate
tag with processed URL
return sprintf('
',
$processedImage->getPublicUrl(),
$alt
);
}
Consequences
============
Positive
--------
- ✅ **Full TYPO3 Integration**: Complete FAL support, reference tracking, magic image processing
- ✅ **Security**: Respects TYPO3's file permission system
- ✅ **Flexibility**: Can implement exactly what TYPO3 needs, no compromises
- ✅ **TypoScript Control**: Full configuration via TypoScript
- ✅ **Future-Proof**: Can add TYPO3-specific features without CKEditor limitations
- ✅ **Backward Compatibility**: Works with existing TYPO3 content and workflows
Negative
--------
- ❌ **WYSIWYG Features Missing**: Must implement caption editing, contextual toolbar, visual resize, image styles manually
- ❌ **Development Effort**: Significant implementation work for WYSIWYG features
- ❌ **Maintenance Burden**: Must track CKEditor 5 API changes and update accordingly
- ❌ **Feature Parity Challenge**: Native plugins have better UX, must match quality
- ❌ **Knowledge Requirements**: Deep CKEditor 5 plugin architecture expertise needed
Current Limitations
-------------------
The custom plugin currently lacks some WYSIWYG features present in native plugins:
1. **Caption Not Inline Editable**: Uses dialog instead of click-to-edit
- Root cause: ``toWidget()`` wrapper prevents nested editables
- **Status**: Implementation gap - can be addressed with proper editable nesting
2. **No Contextual Toolbar**: No balloon toolbar on image selection
- ✅ **IMPLEMENTED** in feature/wysiwyg-caption-fixes branch via WidgetToolbarRepository
3. **No Visual Resize**: No drag handles for resizing
- Root cause: **WidgetResize is NOT available in TYPO3's CKEditor 5 build** (confirmed in typo3image.js:1576)
- **Status**: Architectural limitation - cannot be implemented without CKEditor build changes
- Alternative: Resize functionality available via context toolbar buttons
4. **No Image Styles**: No pre-defined alignment/sizing options
- ✅ **IMPLEMENTED** via balloon toolbar (alignment buttons: left/center/right/block)
**Summary**: Most gaps are implementation issues that can be addressed. Visual resize handles are an **architectural limitation** due to WidgetResize unavailability in TYPO3's CKEditor 5 build.
Compliance
==========
- **TYPO3 Core Architecture**: Follows FAL (File Abstraction Layer) patterns
- **CKEditor 5 Plugin API**: Implements official plugin architecture
- **Security**: File access control and permission checking
- **Performance**: Optimized image processing pipeline
- **Accessibility**: Semantic HTML structure support
References
==========
- `TYPO3 FAL Documentation `__
- `TYPO3 RTE Configuration `__
- `CKEditor 5 Image Feature `__
- `CKEditor 5 Plugin Development `__
- TYPO3 Core RTE Default Configuration: ``typo3/sysext/rte_ckeditor/Configuration/RTE/Default.yaml``
Future Considerations
=====================
While native CKEditor 5 plugins cannot be used directly, their **implementation patterns** can guide our custom plugin development:
- Study ``ImageCaption`` source for inline editable caption UX
- Study ``ImageToolbar`` source for contextual balloon toolbar
- Study ``ImageResize`` source for visual resize handle implementation
- Study ``ImageStyle`` source for style dropdown integration
**Goal**: Achieve feature parity with native plugins while maintaining full TYPO3 FAL integration.
Revision History
================
.. list-table::
:header-rows: 1
:widths: 20 20 60
* - Date
- Version
- Changes
* - 2025-11-09
- 1.0
- Initial ADR documenting why native CKEditor 5 plugins cannot be used with TYPO3 FAL