29 KiB
Executable File
VITEC Headless JSON Architecture — Software Architecture and Interface Specification
| Document identifier | EVO‑VITEC‑HL‑001 |
| Version | 1.0 |
| Status | Released |
| Date | 2026‑07‑09 |
| Applies to | evomedien/vitec on TYPO3 v14.3 (headless) |
| Owner | evomedien — VITEC relaunch |
This document is drafted in the style of, and adopts the terminology conventions of, ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for users) and ISO/IEC 25010 (product quality). The key words shall, should and may are to be interpreted as normative requirements, recommendations and permissions respectively.
Foreword
The VITEC web platform is a headless TYPO3 installation: the CMS does not render HTML pages, it emits JSON that is consumed by a separate React front end. This specification describes the architecture, the public JSON interface, and the engineering conventions that keep the JSON output consistent across all content types and maintainable across TYPO3 and extension upgrades.
It supersedes, as the authoritative reference, the informal tutorial
Documentation/HeadlessIntegration.md, which is retained as an informative how‑to.
Introduction
The platform combines the generic headless page renderer (friendsoftypo3/headless)
with three sources of content JSON:
- Custom plugins (product, use case/success story, market, solution, downloads, datasheets, events, news) rendered by dedicated UserFunc classes;
- Layout containers (b13/container based column grids and a card carousel) rendered by a DataProcessor;
- Content Blocks (
friendsoftypo3/content-blocks) serialised automatically bynb-headless-content-blocks.
All three are unified under a single content‑element envelope so that the front end can consume every element with one predictable shape.
1 Scope
1.1 In scope
This document specifies:
- the runtime environment and the software stack (Clause 5);
- the JSON rendering pipeline and its layers (Clause 6);
- the public JSON interface — envelope, payloads, structured data (Clause 7);
- the catalogue of content types and their JSON keys (Clause 8);
- the mandatory conventions for implementing and extending renderers (Clause 9);
- maintainability and upgrade‑safety requirements (Clause 10);
- conformance criteria (Clause 11).
1.2 Out of scope
Front‑end (React) implementation, hosting/deployment, the editorial (backend) TCA form design except where it determines JSON output, and non‑headless (Fluid) rendering paths.
2 Normative references
The following documents are referred to in the text. For dated references, only the edition cited applies.
- ISO/IEC/IEEE 42010, Software, systems and enterprise — Architecture description
- ISO/IEC 25010, Systems and software Quality Requirements and Evaluation (SQuaRE) — Product quality model
- ISO/IEC/IEEE 26514, Systems and software engineering — Design and development of information for users
- ISO 8601‑1, Date and time — Representations for information interchange
- IETF RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format
- IETF RFC 2119, Key words for use in RFCs to indicate requirement levels
- schema.org vocabulary (informative), https://schema.org
3 Terms and definitions
3.1 headless — operating mode in which TYPO3 returns JSON instead of HTML;
enabled per site by headless: 1 and the headless Site Sets.
3.2 content element — a tt_content record; the atomic unit of page content.
3.3 CType — the content‑element type identifier stored in tt_content.CType
(e.g. vitec_productlist, news_pi1, vitec_cols_50_50).
3.4 envelope — the invariant outer JSON structure shared by every content element (Clause 7.2).
3.5 payload — the domain‑specific JSON produced for one content element and placed inside the envelope (Clause 7.3).
3.6 renderer — a UserFunc class under Classes/UserFunc/ that produces a payload.
3.7 serializer — a service class under Classes/Service/ that converts a domain
record into JSON, used by one or more renderers.
3.8 resolver / processor — ContentElementResolver and ContainerChildrenProcessor;
they normalise a raw tt_content row into an envelope and resolve nested elements.
3.9 container — a b13/container CType that owns child content elements via
tx_container_parent and emits them as items.
3.10 Content Block — a declaratively defined content element
(friendsoftypo3/content-blocks), serialised by nb-headless-content-blocks.
4 Symbols and abbreviated terms
| Term | Meaning |
|---|---|
| FAL | File Abstraction Layer (TYPO3 file handling) |
| IRRE | Inline Relational Record Editing (type: inline) |
| MM | Many‑to‑many junction table |
| CB | Content Block |
| TS | TypoScript |
| CE | Content element |
5 Runtime environment (architecture context)
5.1 Software stack
| Component | Version | Role |
|---|---|---|
| TYPO3 CMS | ^14.3 | Core CMS |
| PHP | 8.x (per TYPO3 14) | Runtime |
friendsoftypo3/headless |
^5.0@rc | Page‑to‑JSON renderer, lib.contentElement |
friendsoftypo3/content-blocks |
^2.4 | Declarative content elements |
netzbewegung/nb-headless-content-blocks |
^0.0.23 | Content Blocks → JSON |
b13/container |
^3.1 | Nested column containers |
georgringer/news |
^14.0 | News records and plugins |
evomedien/vitec |
^1.0 | This project’s custom extension |
Note
friendsoftypo3/headlessis pinned to a release candidate (^5.0@rc). This is an upgrade‑sensitivity point; see 10.4.
5.2 Site configuration
The headless mode is activated in config/sites/vitec/config.yaml:
base: /
headless: 1
frontendBase: ''
dependencies:
- friendsoftypo3/headless
- friendsoftypo3/headless-mixed
- nb-headless-content-blocks/headless-content-blocks
- georgringer/news
The Site Sets listed under dependencies load, in order, the headless TypoScript
base, the mixed‑mode overrides, the Content Blocks JSON integration and the News
integration. The VITEC Set (EXT:vitec/Configuration/Sets/Vitecset) layers the custom
definitions on top. The headless page response carries
Content-Type: application/json; charset=utf-8. Slug routing is configured with route
enhancers for products (tx_vitec_domain_model_product.slug) and news detail
(path_segment).
5.3 Architectural principles (rationale)
- P1 — One envelope. Every content element, regardless of source, is exposed with the same outer shape so the front end has a single rendering contract.
- P2 — Payload isolation. Domain JSON is produced in PHP, fully decoupled from TypoScript, so business logic is testable and versionable.
- P3 — Dual entry. Each renderer works both as a top‑level plugin and as a nested child of a container (Clause 9.3).
- P4 — Fail soft. A failing element yields empty output, never a broken page (Clause 9.5).
6 Rendering pipeline
The JSON for one page is assembled top‑down through the following layers.
HTTP request (Accept: application/json, headless:1)
│
▼
[L1] Page renderer friendsoftypo3/headless
│ builds { meta, content[], … , jsonLd }
▼
[L2] Content‑element envelope lib.contentElement / lib.contentElementWithHeader
│ per CType: id, type, colPos, appearance, content{header,…}
▼
[L3] Payload injection USER cObj → Classes/UserFunc/*JsonRenderer::render
│ places domain JSON under content.<key>
▼
[L4] Containers tt_content.vitec_cols_* = JSON
│ ContainerChildrenProcessor → items[].contentElements[]
▼
[L5] Normalisation & nesting ContentElementResolver / PLUGIN_RENDERERS
▼
[L6] Structured data (JSON‑LD) PageJsonLdRenderer + StructuredDataService
6.1 L1 — Page renderer
friendsoftypo3/headless converts the requested page into a JSON document containing
page metadata, the ordered array of content elements, navigation and the JSON‑LD
graph. VITEC does not replace this layer; it contributes elements to content[]
(L2–L5) and the jsonLd field (L6).
6.2 L2 — Content‑element envelope
Each CType is bound to a headless library object:
tt_content.<ctype> < lib.contentElementWithHeader
lib.contentElement provides id, type, colPos, categories, appearance.
lib.contentElementWithHeader additionally provides, under content, the standard
header fields: header, subheader, headerLayout, headerPosition, headerLink
(link resolved via typolink). All VITEC plugins and all News CTypes inherit
lib.contentElementWithHeader, giving a uniform header section in both backend and
JSON (see the companion header convention).
6.3 L3 — Payload injection
The domain payload is added as a USER content object under content.fields.<key>:
tt_content.vitec_productlist < lib.contentElementWithHeader
tt_content.vitec_productlist {
fields {
content {
fields {
products = USER
products.userFunc = Evomedien\Vitec\UserFunc\ProductListJsonRenderer->render
}
}
}
}
render() returns a JSON string that the headless JSON cObject embeds verbatim at
content.products. The key is plural for list plugins and singular for detail
plugins (Clause 9.6).
6.4 L4 — Containers
Column containers are defined as a self‑contained JSON object, not via
lib.contentElement:
tt_content.vitec_cols_50_50 = JSON
tt_content.vitec_cols_50_50.fields {
id … type … appearance … header … subheader …
gap = TEXT # tx_vitec_gap (whole‑grid gap)
items = JSON
items.dataProcessing.10 = Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor
}
tt_content.vitec_cols_33_66 < tt_content.vitec_cols_50_50
tt_content.vitec_container < tt_content.vitec_cols_50_50 # single column, no gap
tt_content.vitec_cards_carousel < tt_content.vitec_cols_50_50 # + carousel settings
RULE (normative) Container CTypes shall be derived with the copy operator
<, never the reference operator=<. Att_content → tt_contentreference is not recognised as an independent renderer by the headless content mapper and silently falls back to raw output (see 10.5, and Annex B‑1).
6.5 L5 — Normalisation and nested plugins
ContainerChildrenProcessor queries children by tx_container_parent, groups them by
colPos and emits, per column, a flex configuration plus the resolved children. Each
child is normalised by the same envelope logic as ContentElementResolver. When a
child is itself a VITEC plugin, it is resolved through the shared PLUGIN_RENDERERS
map (Clause 7.4).
6.6 L6 — Structured data (JSON‑LD)
PageJsonLdRenderer (bound at page…fields.jsonLd) assembles a schema.org @graph
via StructuredDataService, encoded with JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE.
Node types and their triggers:
| Node | Emitted when |
|---|---|
Organization |
every page (from site settings seo.organization.*) |
WebSite |
only on the site root page |
BreadcrumbList |
from the rootline (spacer/folder/recycler doktypes skipped) |
NewsArticle |
on news‑detail page layouts (pages.layout ∈ {13,14,15}) |
FAQPage |
when the page contains vitec_faq elements → vitec_faq_items collection |
ExhibitionEvent |
from vitec_eventlist elements → tx_vitec_domain_model_event (windowed by the FlexForm daysinadvance) |
Product, VideoObject |
via ProductShowJsonRenderer for product detail pages |
FAQ and event nodes are collected from the respective tables; event image URLs are
currently built with a hard‑coded /fileadmin prefix (see 10.3 / Annex B‑5).
7 JSON interface specification
7.1 Encoding
Output shall be RFC 8259 JSON, UTF‑8. Timestamps shall be Unix epoch seconds (integer); where ISO 8601 strings are required by schema.org they are produced inside the JSON‑LD layer.
7.2 Content‑element envelope
Every element in content[] conforms to:
{
"id": 123, // tt_content.uid
"type": "vitec_productlist", // tt_content.CType
"colPos": 0,
"appearance": {
"layout": "0",
"frameClass": "default",
"spaceBefore": "",
"spaceAfter": ""
},
"content": { // present for lib.contentElement(WithHeader) CTypes
"header": "…", "subheader": "…",
"headerLayout": 2, "headerPosition": "", "headerLink": "https://…",
"<key>": { /* payload, Clause 7.3 */ }
}
}
Elements produced by the resolver/processor (container children, inline story CEs) use a lean variant of the envelope:
{ "id": 456, "type": "text", "colPos": 211, "sorting": 1,
"appearance": { … }, "data": { /* non‑system fields */ } }
7.3 Payload keys
| Kind | Key | Cardinality |
|---|---|---|
| List plugin | plural noun (products, usecases, news → items) |
array |
| Detail plugin | singular noun (product, usecase, market, solution) |
object |
| Container | items |
array of {config, contentElements} |
7.4 Container payload
{
"type": "vitec_cols_33_66",
"header": "…", "headerLayout": 2, "headerLink": "…",
"gap": "3",
"items": [
{ "config": { "colPos": 251, "align": "stretch", "justify": "flex-start" },
"contentElements": [ /* normalised children, recursively */ ] },
{ "config": { "colPos": 252, "align": "center", "justify": "space-between" },
"contentElements": [ … ] }
]
}
gapis a parent‑level property (whole‑grid gap).align/justifyare per‑column and are read from the parent record (tx_vitec_col{N}_align/justify); they therefore apply to all children of that column. These container‑only fields shall not appear in a child’sdata(enforced byCONTAINER_FIELDSfiltering).
7.5 Success Story (use case) detail payload
Produced by UsecaseSerializer::serializeDetail() — the reference implementation of
the centralised pattern (Clause 9.2):
{
"uid": 1, "title": "…", "slug": "…", "subtitle": "…", "teaser": "…",
"cardImage": { "url": "…", "srcset": [ … ] }, "customerLogo": { … },
"hero": { "bgImage": …, "smallImage": …, "video": …,
"overlayColor": "#000", "overlayOpacity": 0.4,
"layout": "fullscreen", "textTheme": "light" },
"contentElements": [ /* inline CEs; the vitec_columns block is resolved specially */ ],
"related": { "show": true, "market": {…}, "solutions": [ … ], "products": [ … ],
"categories": [ … ] },
"seo": { "title": "…", "description": "…", "canonical": "…",
"robots": { "noIndex": false, "noFollow": false },
"openGraph": { "title": …, "description": …, "image": … },
"twitter": { "title": …, "description": …, "image": … } },
"appearance": { "layoutVariant": "standard", "backgroundVariant": "none",
"accentColor": "", "featured": false, … }
}
SEO fields use fallback resolution (seo_* → title/teaser; og_* → seo_* → title;
twitter_* → og_*; images og_image → card_image → hero_bgimage) so the front end
always receives complete metadata.
7.6 News payload
Produced by NewsJsonRenderer under content.news:
{ "mode": "list", // "list" | "detail" | "error"
"items": [ { "uid": 1, "title": "…", "alternativeTitle": "…",
"pathSegment": "…", "detailUrl": "/news/…",
"canonicalUrl": "https://…", "teaser": "…", "bodytext": "…",
"datetime": 1625097600, "categories": [ … ], "media": [ … ] } ],
"settings": { "type": "news_pi1", "templateLayout": "1",
"templateLayoutLabel": "Compact List", "detailPid": 45, … } }
Ten News CTypes (news_pi1, news_newsdetail, news_newsliststicky, …) share one
renderer; news_newsdetail yields mode: "detail" with a single news object.
7.7 Content Blocks
Content Blocks (card, cta-banner, hero-section, intro-paragraph, video,
faq, columns) are serialised automatically by nb-headless-content-blocks: its
ContentBlocksJsonDataProcessor converts the resolved record via RecordToArray,
dropping system fields and recursing into files (→ URL + metadata), collections
(e.g. faq_items → array of child records) and typolinks. The output uses the lean
envelope ({id, type, colPos, sorting, appearance, data}). An optional per‑block
headless.php hook may transform the array (none are currently defined). Fields carry
the vitec_ vendor prefix in storage. The vitec_columns block is
the single exception: because it is authored inline inside a Success Story record,
its collection items are resolved explicitly by UsecaseSerializer::resolveColumnsElement()
into { header…, layout, columns: { left: [], right: [] } }, with the collection
storage table discovered from TCA (foreign_table) rather than hard‑coded.
8 Content‑type catalogue
| CType | TS pattern | Renderer / Processor | Payload key | Kind |
|---|---|---|---|---|
vitec_productlist |
< lib.contentElementWithHeader |
ProductListJsonRenderer | products |
list |
vitec_productshow |
< lib.…WithHeader |
ProductShowJsonRenderer | product |
detail |
vitec_usecaselist |
< lib.…WithHeader |
UsecaseListJsonRenderer → UsecaseSerializer | usecases |
list |
vitec_usecaseshow |
< lib.…WithHeader |
UsecaseShowJsonRenderer → UsecaseSerializer | usecase |
detail |
vitec_marketshow |
< lib.…WithHeader |
MarketShowJsonRenderer | market |
detail |
vitec_solutionshow |
< lib.…WithHeader |
SolutionShowJsonRenderer | solution |
detail |
vitec_downloadcard |
< lib.…WithHeader |
DownloadcardJsonRenderer | downloadcard |
detail |
vitec_downloadcardcollection |
< lib.…WithHeader |
DownloadcardcollectionJsonRenderer | downloadcardcollection |
list |
vitec_datasheets |
< lib.…WithHeader |
DatasheetsJsonRenderer | datasheets |
list |
vitec_eventlist |
=< lib.…WithHeader ⚠ |
EventlistJsonRenderer | eventlist |
list |
news_pi1 (+9 variants) |
=< lib.…WithHeader; variants < |
NewsJsonRenderer | news |
list/detail |
vitec_cols_50_50 |
= JSON |
ContainerChildrenProcessor | items |
container |
vitec_cols_33_66 / 66_33 / 33_33_33 / 25_25_25_25 |
< vitec_cols_50_50 |
ContainerChildrenProcessor | items |
container |
vitec_container |
< vitec_cols_50_50 |
ContainerChildrenProcessor | items |
container (1 col) |
vitec_cards_carousel |
< vitec_cols_50_50 |
ContainerChildrenProcessor | items + carousel |
container |
Content Blocks (vitec_card, …) |
Content Blocks + nb‑headless | — | (auto) | element |
vitec_columns (CB, inline) |
Content Blocks | UsecaseSerializer (special) | columns |
element |
⚠ = uses the reference operator =<; see Annex B‑1.
9 Conventions (normative)
These rules define the single, uniform way to implement and extend headless renderers. New code shall comply; existing code should be aligned when touched.
9.1 Renderer class shape
A payload renderer shall:
- reside in
Classes/UserFunc/and be named<Domain><Kind>JsonRenderer; - expose
#[AsAllowedCallable] public function render(string $content, array $conf): string; - expose
public function renderForRecord(array $row): stringfor reuse by containers/resolvers (Clause 9.3); - return a JSON string (never an array/object).
9.2 Serialisation ownership
Domain‑to‑JSON conversion should live in a Classes/Service/*Serializer class, and
the renderer should be a thin wrapper around it. UsecaseSerializer is the
reference implementation. New list/detail pairs shall share one serializer.
9.3 Dual‑entry pattern
render() shall handle top‑level invocation: use $this->cObj->data when it is the
plugin’s own row, otherwise perform page discovery (Clause 9.4). renderForRecord()
shall accept an explicit tt_content row and be free of page/context assumptions,
so it can be called by ContainerChildrenProcessor and ContentElementResolver.
9.4 Page‑id discovery
Renderers shall resolve the current page id in this order:
$id = $GLOBALS['TYPO3_REQUEST']?->getAttribute('frontend.page.information')?->getId() ?? 0;
if ($id <= 0) { $id = (int)($GLOBALS['TSFE']->id ?? 0); } // fallback
Reliance on $GLOBALS['TSFE']->id alone is prohibited (it is frequently null in
the JSON cObject context).
9.5 Fail‑soft error handling
The body of renderForRecord() shall be wrapped in
try { … } catch (\Throwable $e) { return ''; }. Diagnostic output may be emitted
only when a FlexForm debug flag is set. A failing element shall not propagate an
exception to the page.
9.6 Naming
- List payload keys shall be plural; detail keys shall be singular.
- JSON property names shall be
camelCasefor computed/composed fields; raw passthrough fields indataretain their database names.
9.7 Nested‑plugin registration
A plugin that may appear inside a container shall be registered in the
PLUGIN_RENDERERS map in both ContentElementResolver and
ContainerChildrenProcessor, as CType => [RendererClass::class, 'jsonKey'].
Note
The duplicated map is a known maintenance hazard (10.3). Until it is centralised, both copies shall be kept in sync.
9.8 Header section
Every custom CE/plugin/container shall expose the standard header section (the core
headers palette; Content Blocks use a header_section palette). See the companion
"header convention". Header fields in JSON use the names header, subheader,
headerLayout, headerPosition, headerLink.
10 Maintainability and upgrade‑safety (ISO 25010)
This clause records the quality characteristics maintainability and portability and the concrete risks and rules that preserve them.
10.1 Modularity — current state
Strengths: uniform envelope, dual‑entry pattern, exception safety, a shared
PLUGIN_RENDERERS map, and the UsecaseSerializer reference pattern.
Weakness: substantial duplication across the inline renderers.
10.2 Reusability — duplication register
The following logic is duplicated across many renderers and should be extracted into shared services (target design in parentheses):
| Duplicated logic | Occurrences | Target service |
|---|---|---|
FAL image/srcset resolution |
Product(List/Show), Market, Solution, Event, Datasheets | FalImageResolver |
| FAL video resolution | Product, Usecase, hero | FalImageResolver::video() |
sys_category MM query |
≥ 9 renderers | CategoryResolver |
| Custom MM (product↔download, …) | ≥ 5 renderers | RelationResolver::resolveMany() |
| Download file‑by‑convention | 5 renderers | ConventionFileResolver |
letterSequenceToRank() sort helper |
5 renderers | static utility |
| Page‑id discovery | all renderers | PageIdResolver::resolve() |
RULE When a shared resolver service exists, new renderers shall use it and shall not re‑implement the logic inline.
10.3 Analysability — single sources of truth
- The
PLUGIN_RENDERERSmap exists in two files (10.2/9.7); it should be promoted to one shared constant/class. - Table and field names are string literals scattered across renderers. New code
shall define table/field names as class constants (as
UsecaseSerializerand the processors already do) to localise upgrade impact.
10.4 Portability — upgrade‑sensitivity points
| Point | Risk | Mitigation |
|---|---|---|
friendsoftypo3/headless ^5.0@rc |
RC; lib.contentElement(WithHeader) shape may change |
Pin exact RC; re‑verify envelope after any bump; keep payloads decoupled (P2) |
nb-headless-content-blocks ^0.0.x |
pre‑1.0; CB→JSON shape and collection storage may change | vitec_columns resolution reads the table from TCA (foreign_table) — do not hard‑code CB tables |
georgringer/news ^14 |
10 News CTypes hard‑mapped in NewsJsonRenderer |
Keep the CType→layout map in one place; re‑verify on major news upgrade |
b13/container ^3.1 |
child linkage via tx_container_parent; page‑module grid required |
Documented limitation: containers cannot be authored inside IRRE (Annex B‑2) |
10.5 Modifiability — mandatory rules distilled
- Container CTypes shall use
<(copy), never=<(reference) — see Annex B‑1. - Table/field names shall be class constants, not inline literals.
- Renderers shall reuse shared resolver services once they exist.
- Magic numeric literals (e.g. a hard‑coded parent‑category uid) shall be replaced by named constants or configuration.
11 Conformance
An implementation conforms to this specification if, for every content type it exposes:
- C1 the output validates as RFC 8259 JSON and matches the envelope of 7.2;
- C2 the responsible renderer satisfies the class shape of 9.1 and the dual‑entry pattern of 9.3;
- C3 page‑id discovery follows 9.4 and error handling follows 9.5;
- C4 payload keys follow 9.6 and the header section follows 9.8;
- C5 container derivation follows the
<‑copy rule of 6.4/10.5(1); - C6 any container‑nestable plugin is registered per 9.7.
Deviations are recorded in Annex B and shall carry a remediation plan.
Annex A (normative) — Checklist: adding a new headless plugin
- Model/TCA/SQL — create the domain table and TCA; define table/field names as constants.
- Serializer — add
Classes/Service/<Domain>SerializerwithserializeListItem()and/orserializeDetail(); reuse existing resolver services. - Renderer — add
Classes/UserFunc/<Domain><Kind>JsonRendererper 9.1, delegating to the serializer; implementrender()(9.3/9.4) andrenderForRecord(). - TypoScript — in
Configuration/Sets/Vitecset/setup.typoscript:tt_content.<ctype> < lib.contentElementWithHeaderandcontent.fields.<key> = USER+.userFunc = …->render. - Header section — ensure the
headerspalette is present (9.8). - Nesting — if the plugin may sit inside a container, register it in
PLUGIN_RENDERERSin both the resolver and the processor (9.7). - Deploy —
vendor/bin/typo3 database:updateschema "*.add,*.change"thenvendor/bin/typo3 cache:flush. - Verify — fetch the page JSON; confirm envelope (C1), keys (C4) and nested output.
Annex B (informative) — Nonconformity and technical‑debt register
The following items were identified during architecture analysis. Items marked (to verify) were reported by static review and shall be confirmed before remediation.
- B‑1 —
=<on Event/News CTypes.vitec_eventlistandnews_pi1use the reference operator=< lib.contentElementWithHeader. Reference to alib.*object is used by headless itself and is generally safe; however, for consistency with 10.5(1) these are VERIFIED SAFE and left as-is (references to lib.* are idiomatic in headless; only tt_content-to-tt_content references are unsafe, and those are already<for containers). - B‑2 — Containers cannot be authored inline (IRRE). b13 container children live in
tx_container_parentand require the page‑module grid; they cannot be created inside an inline field. This is a platform limitation, not a defect. The Success Story "Columns" Content Block (vitec_columns) is the sanctioned inline alternative. - B‑3 — Duplicated
PLUGIN_RENDERERSmap inContentElementResolverandContainerChildrenProcessor(9.7/10.3). - B‑4 — Inline duplication of image/category/MM/page‑id logic (10.2).
- B‑5 — Hard‑coded literals — model/MM table names, a
/fileadminprefix for event JSON‑LD image URLs (PageJsonLdRenderer), and at least one magic parent‑category uid appear as inline literals across download/datasheet/structured‑data code (to verify and extract to constants).COLPOS_TO_COLUMNinContainerChildrenProcessormust be kept in sync with the container TCA colPos values. - B‑6 —
DownloadcardcollectionJsonRenderer: two misplaced thumbnail output lines referenced an undefined variable in the image resolver. FIXED 2026-07-09 (removed; the PDF-thumbnail method is unaffected). - B‑7 — v13→v14
CType/list_typecompatibility branches in some download/ datasheet renderers FIXED 2026-07-09: the unsatisfiable legacy OR branch was removed; the query now filters on the v14 CType only.
Annex C (informative) — Related documents
Documentation/HeadlessIntegration.md— informal how‑to (superseded as the authoritative reference by this document).- Header convention — the uniform header section across CEs, plugins and containers.
Configuration/Sets/Vitecset/setup.typoscript— the single TypoScript entry point.
End of document EVO‑VITEC‑HL‑001 v1.0.