# TYPO3 v13 Backend Module Modernization
**Purpose:** Comprehensive guide for modernizing TYPO3 backend modules to v13 LTS standards
**Source:** Real-world modernization of nr_temporal_cache backend module (45/100 → 95/100 compliance)
**Target:** TYPO3 v13.4 LTS with PSR-12, modern JavaScript, and accessibility compliance
---
## Critical Compliance Issues
### 1. Extension Key Consistency
**Problem:** Mixed extension keys throughout templates and JavaScript breaks translations and routing
**Common Violations:**
- Template translation keys using `EXT:wrong_name/` instead of `EXT:correct_name/`
- JavaScript alert messages with hardcoded wrong extension names
- Variable substitution using wrong extension prefix
**Detection:**
```bash
# Find all extension key references
grep -rn "EXT:temporal_cache/" Resources/Private/Templates/
grep -rn "EXT:temporal_cache/" Resources/Public/JavaScript/
# Verify correct key in ext_emconf.php
grep "\$EM_CONF\[" ext_emconf.php
```
**Example Violations:**
```html
```
```javascript
// WRONG: Hardcoded wrong extension name in alert
alert('Error in Temporal Cache extension');
// CORRECT: Use TYPO3 Notification API with correct name
Notification.error('Error', 'Failed in nr_temporal_cache');
```
**Impact:** Broken translations, 404 errors on static assets, module registration failures
**Severity:** 🔴 Critical - Breaks basic functionality
**Fix Priority:** Immediate - Fix before any other modernization work
---
### 2. JavaScript Modernization (ES6 Modules)
**Problem:** Inline JavaScript in templates is deprecated, not CSP-compliant, and hard to maintain
**TYPO3 v13 Standard:** All JavaScript must be ES6 modules loaded via PageRenderer
**Before (DEPRECATED):**
```html
```
**After (MODERN v13):**
**Step 1: Create ES6 Module** (`Resources/Public/JavaScript/BackendModule.js`)
```javascript
/**
* Backend module JavaScript for nr_temporal_cache
* TYPO3 v13 ES6 module
*/
import Modal from '@typo3/backend/modal.js';
import Notification from '@typo3/backend/notification.js';
class TemporalCacheModule {
constructor() {
this.initializeEventListeners();
}
initializeEventListeners() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.init());
} else {
this.init();
}
}
init() {
this.initializeHarmonization();
this.initializeKeyboardNavigation();
}
initializeHarmonization() {
const selectAllCheckbox = document.getElementById('select-all');
const contentCheckboxes = document.querySelectorAll('.content-checkbox');
const harmonizeBtn = document.getElementById('harmonize-selected-btn');
if (!harmonizeBtn) return;
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', (e) => {
contentCheckboxes.forEach(checkbox => {
checkbox.checked = e.target.checked;
});
this.updateHarmonizeButton();
});
}
contentCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', () => this.updateHarmonizeButton());
});
harmonizeBtn.addEventListener('click', () => this.performHarmonization());
}
async performHarmonization() {
const selectedUids = Array.from(document.querySelectorAll('.content-checkbox:checked'))
.map(cb => parseInt(cb.dataset.uid));
if (selectedUids.length === 0) return;
const harmonizeBtn = document.getElementById('harmonize-selected-btn');
const harmonizeUri = harmonizeBtn.dataset.actionUri;
// Use TYPO3 Modal instead of confirm()
Modal.confirm(
'Confirm Harmonization',
`Harmonize ${selectedUids.length} content elements?`,
Modal.SeverityEnum.warning,
[
{
text: 'Cancel',
active: true,
btnClass: 'btn-default',
trigger: () => Modal.dismiss()
},
{
text: 'Harmonize',
btnClass: 'btn-warning',
trigger: () => {
Modal.dismiss();
this.executeHarmonization(harmonizeUri, selectedUids);
}
}
]
);
}
async executeHarmonization(uri, selectedUids) {
try {
const response = await fetch(uri, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: selectedUids, dryRun: false })
});
const data = await response.json();
if (data.success) {
// Use TYPO3 Notification API instead of alert()
Notification.success('Harmonization Successful', data.message);
setTimeout(() => window.location.reload(), 1500);
} else {
Notification.error('Harmonization Failed', data.message);
}
} catch (error) {
Notification.error('Error', 'Failed to harmonize content: ' + error.message);
}
}
initializeKeyboardNavigation() {
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + A: Select all
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
const selectAll = document.getElementById('select-all');
if (selectAll && document.activeElement.tagName !== 'INPUT') {
e.preventDefault();
selectAll.checked = true;
selectAll.dispatchEvent(new Event('change'));
}
}
});
}
}
// Initialize and export
export default new TemporalCacheModule();
```
**Step 2: Load Module in Controller** (`Classes/Controller/Backend/TemporalCacheController.php`)
```php
private function setupModuleTemplate(ModuleTemplate $moduleTemplate, string $currentAction): void
{
$moduleTemplate->setTitle(
$this->getLanguageService()->sL('LLL:EXT:nr_temporal_cache/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab')
);
// Load JavaScript module
$moduleTemplate->getPageRenderer()->loadJavaScriptModule(
'@netresearch/nr-temporal-cache/backend-module.js'
);
// Add DocHeader buttons
$this->addDocHeaderButtons($moduleTemplate, $currentAction);
// ...
}
```
**Step 3: Remove Inline JavaScript from Templates**
```html
```
**Validation:**
```bash
# Ensure NO inline JavaScript remains
grep -rn "FooterAssets" Resources/Private/Templates/
grep -rn "