---
title: "TYPO3 Distribution Content"
description: "Skill: typo3-distribution-content"
canonical: index.html
navigation-title: "TYPO3 Distribution Content"
---

<a id="typo3-distribution-content"></a>

# TYPO3 Distribution Content

- [Markdown source](#markdown-source)
- [References](#references)
  - [Where every task starts](#where-every-task-starts)

**Skill:** `typo3-distribution-content`

Ship the content of a TYPO3 site inside an extension, as a distribution — the
initial content a package carries, the export artifact and the files beside it,
and the site configuration that travels with them. Use for initial content,
data.xml, Initialisation, seeding a page tree, and a distribution that has to
come up on a fresh installation.

<a id="markdown-source"></a>

## Markdown source

```markdown
---
name: typo3-distribution-content
description: Ship the content of a TYPO3 site inside an extension, as a distribution — the initial content a package carries, the export artifact and the files beside it, and the site configuration that travels with them. Use for initial content, data.xml, Initialisation, seeding a page tree, and a distribution that has to come up on a fresh installation.
compatibility: Needs the typo3-dev-companion MCP server, which owns every lookup this workflow routes to and publishes this skill together with the references/base.md it opens on. Install it from github.com/TYPO3/dev-companion and run typo3-dev-companion install in the project. A copy taken out of that repository's skills directory alone has neither the tools nor that base file.
---

# TYPO3 Distribution Content

Produce the content a distribution ships, in an order where each step decides
what the next one can be. Keep this skill as routing and workflow. Never keep
command options, table names, package names or version numbers. Each of those
belongs to a tool that releases on its own cycle. You cannot ask any of them
again from here.

## Where this starts

Work through [references/base.md](references/base.md) first. Two of its answers
are this workflow's entry condition rather than findings. Those are the
installation you produce the content in, and the extension that carries it. You
can export nothing before both exist. Where the installation is the one that is
missing, say so. Bring it into existence in the workflow that owns that before
you come back.

Then read what an extension ships content as, once, before you write anything:
`typo3_hint_lookup` with `id=sitepackage-initial-content`. It names the three
other ids this workflow turns on. Fetch each of them at the step below that
needs it rather than now.

## Seed what exists nowhere yet

A distribution's first content has nothing to export from. So the artifact
begins as records a script writes into the installation. `typo3_hint_lookup`
with `id=datahandler-seeding` owns that. It says what has to boot, which user
the write runs as, and what one call resolves for itself.

What this workflow adds is that you **read the seed back out of the installation
before you export it**. A record takes the defaults its configuration gives it,
which are not the defaults its columns carry. One of those decides whether a
visitor gets a page at all. So query the installation for what landed: the tree,
the elements on it, the relations that hang off them.

Where it is wrong, correct the script rather than the records. The script is
what the next version of the artifact comes out of. A record you fix by hand is
a correction the next export loses.

## Export it

`typo3_hint_lookup` with `id=impexp-artifact` owns the export. It says which
command writes it and what the package has to require for the import to exist at
all. It says what the command leaves out without a word. It says where the
command puts the file, which is not where the argument said.

`typo3_documentation_lookup` at the version you ship is the other half. Ask for
the page on how you create a distribution. That page has already decided which
records belong in the artifact and which are the receiving installation's own.

What this workflow adds is that **a command that reports success is not evidence
that the export worked**. It reports the same success for an artifact without
its images and for one without a whole table. Both failures are invisible until
somebody installs the package. So check the artifact, not the message. Every
table the tree holds is a part of it. The part that carries files is there, with
one file per referenced file beside it.

## Place it in the package

The artifact and the directory of files that belongs to it both move into the
extension. They take the names the import looks for;
`id=sitepackage-initial-content` states them. Neither arrives there on request.
The export keeps only the last segment of the name you gave it. So to put both
in place is a step of this workflow and not an argument to the command.

## Ship the site configuration beside the export, not inside it

A site configuration can travel two ways, and they are not equivalent. One of
them loses the address the site answers on, because the receiving installation
cannot know it. `typo3_hint_lookup` with `id=initial-content-references` owns
which route does what, and what survives an import at all.

What this workflow adds is what the intact route copies: **the whole directory
the site keeps its configuration in**. It does not copy only the one file in it
that is obviously configuration. Whatever else the installation put beside it is
part of the site and has to travel with it. The rendering that defines the site
is among that. Left behind, the receiving installation resolves the site, finds
every page, and renders nothing. That reads as a broken import and is not one.

Only the root page gets rewritten to the record the import created. Anything
else in that configuration that names a record by number points at a stranger on
the second installation. So a configuration written for a distribution names as
few of them as it can. Where one is unavoidable, the package says so where
somebody reads it.

## Prove it on an installation that has never seen this package

The artifact is write-only on the installation it came from. The import runs
once and the installation remembers it. So a second import there proves nothing,
and a read of the document back is reasoning rather than verification.
`typo3_hint_lookup` with `id=initial-content-import-once` owns why, and names
the cheap form of the same proof.

The proof is an installation that has never had this package, with the package
active before the install runs. Check three things there. A session usually
stops at the first one alone.

1. The records arrived: the tree, the content on it, and the files it references
   as files rather than as rows.
2. The site answers on the address that installation names for itself, and it
   renders. A site that resolves and renders nothing passes every check you make
   from the console.
3. Every page the artifact carries answers, not only the one at the root. A page
   shipped invisible and a reference that points at a stranger both survive an
   import that reports nothing wrong.

Read what the receiving installation got with `typo3_configuration_lookup`
rather than off the files you gave it. The merged result is what it runs on.

Then report what the package is: which records it ships, which files, and which
site it configures. Name the installation you ran the three checks above on.
Draft the message for it with `typo3_commit_message_guide` and
`workflow="project"`. The artifact, its files and the site configuration are
that repository's own files, which is the workflow that argument names.

## Where this stops

This skill owns the content a distribution ships and the package that carries
it. That is the seed of what exists nowhere yet, and the export. It is where the
artifact and its files sit in the extension, and the site configuration beside
them. It is the installation that proves the result. It owns both directions of
that one thing: the write of the content and its shipment. Neither half is
complete without the other.

It does not own what makes up the content. The templates, the elements an editor
fills in, and the extension that renders them are the sitepackage's. The
crossing is explicit in both directions. On the way out, state what the artifact
verifiably contains and stop before you edit that owner's files. On the way in,
a package whose content has to ship is this workflow from the seed onwards.

It does not own an installation either. To bring one into existence, and to
repair one that came up wrong, belong to the installation workflow. This one
uses two of them and creates neither. Where you cannot get the second
installation, say that the proof did not run. Do not substitute a read of the
artifact for it.
```

<a id="references"></a>

## References

<a id="where-every-task-starts"></a>

### Where every task starts

```markdown
# Where every task starts

## Nothing starts until the server answers

A skill is a file the installer left behind. It loads and reads the same whether
the tools behind it are there or not, and neither side notices. So the first
call below is also the check.

- A client may carry this server's name in each tool's name:
  `mcp__<server>__typo3_project_describe`. So a search for the bare name comes
  back empty where the server is there. A search for a tool's schema needs the
  same form. A `select:` on the bare names returns nothing where the tools are
  there. Look for the qualified form before you read an empty result as an
  answer about the server.
- No `typo3_` tool in this session, or a first call that errors: stop. Say that
  this workflow needs the server and it is not there, and name what came back.
- Do not fall back to general TYPO3 knowledge, and do not start to read the
  checkout. That answer carries this workflow's order and confidence and none of
  its evidence. Nothing in it says which of the two it is.
- Continue only when the user asks you to after you said so. Repeat it in the
  answer and in every finding a lookup would have carried.

## The order

This is an order rather than a list. Each step decides what the next one is
worth. Where a step below carries a condition to skip it, that condition is
narrow on purpose. A skipped prescription teaches the next reader to skip the
ones that matter too.

1. **`typo3_project_describe`** — the repository and whether it holds an
   installation yet. It reports the TYPO3 and PHP version, the project's own
   extensions, its sites, and the commands this repository declares. That
   version filters every later answer. A check the repository does not declare
   is a wrong answer however sensible it sounds.

   The answer ends with the whole procedures this server carries, as ids. That
   list is the only place a client that renders no resource list sees their
   names. Each one is a `typo3_rule_lookup` with that `documentId` rather than a
   search.
2. **`typo3_extension_describe`** for each extension in scope. It says what the
   extension registers, and what it ships beside that. That is its manual, its
   README, its test layers, and its XLF files with the source language each one
   declares. It also says what the extension does *not* ship, and that is the
   half no file listing gives you.

   Where step 1 reported no extension, that answer is this step, and there is
   nothing to call. Say so. A core checkout is that case, because step 1 names
   the project's own extensions and not TYPO3's.
3. **`typo3_task_guide`** with a short English task, the paths it touches, the
   target version and the change type. It answers the workflow this task belongs
   to and the checks that come with it.

   Run it in every session, this skill's own tasks included. The guide builds
   the brief from the paths as well as the task text. No skill knows which paths
   the caller holds.

   A skill that covers the task is not that brief. A skipped step costs the
   hints and the core checks those paths match. Where the guide's own answer
   named this skill, this is one call for an answer already in the session. The
   price of a step there is nothing to decide about.
4. **`typo3_hint_lookup`** for each subsystem in scope, with its concrete paths.
   One query per subsystem. A single broad query is not subsystem evidence.

   Where step 3 ran with those paths, its answer says whether you still owe this
   step. A brief that carried everything the lookup matched says so: "these are
   everything typo3_hint_lookup matches for these paths". There the guide made
   the call, and the same query returns the same hints. A brief that stopped
   short says that instead and names the ids it left. You owe those: fetch them
   by id rather than repeat the query.

   Read the sentence rather than the populated `hints` key. That key is present
   either way and does not tell the two apart. `omittedHints` is that sentence
   as data. It is empty where the brief carried everything, and it holds the ids
   the brief left where it stopped short.
5. **`typo3_changelog_lookup` with `type: deprecation`**, at each major the
   package declares. Omit the query and raise `limit` to carry that major whole.
   Those two are the changelog's own axes, and the extension's vocabulary is not
   among them. An entry carries a query only when its title carries every word
   of it at once. The core titled those entries about its own code.

   That is one call per declared major, and what comes back is the major. Every
   entry carries its own index tags. `ext:core`, `ext:frontend`, `ext:form` and
   the rest name the system extension a change is **in**. `TCA`, `TypoScript`,
   `Fluid`, `YAML`, `Backend`, `Frontend` name the surface.

   Step 2 picks the package's entries out of that answer by those tags. The tags
   are the system extensions it requires, renders through or registers into, and
   the kinds of file it ships. That costs no further call. An extension key of
   your own is not among them and matches nothing. `tag` narrows one question
   inside a major rather than composes the sweep out of eleven.

   You check the answers against step 2, which is the other half the words did.
   Verify each identifier that comes back in the checkout. A deprecation nothing
   here calls is not a finding.

   Carry the `FullyScanned` / `PartiallyScanned` tag into the answer. It says
   whether the Extension Scanner can find the remaining call sites or whether
   that reading is yours. Bounded this way, you can write the sweep before you
   open a file. That is why it is a step of the order rather than something the
   reading stumbles into.

   **What its silence is worth.** A changelog records change events. So a
   pattern nothing has touched for ten majors has no entry at all. An empty
   sweep is therefore not an answer about what still works. "Does this still
   work in version N" goes to `typo3_documentation_lookup` at that version. Ask
   it here, and whenever the reading raises it again.

   That is a question for a documented surface: a ViewHelper, a TCA type, a
   TypoScript setting. The manual matches page titles, section paths and what
   each manual declares by name, never the text of a page. Declared is a
   property, a class or method the manual documents, a console command. You
   reach one by its own name where the query writes that name the way code
   does. You also reach it where the query is nothing but the name. A PHP
   identifier the manual does not declare has no page named after it.

   An identifier goes to `typo3_changelog_lookup` under its own name. That
   reaches the entries that write it, however the core titled the change. Then
   it goes to the class below. Where the manual has no page for a surface
   either, that is a result and not an answer. Undocumented is not unsupported.

   **A second declared major.** A package that declares more than one asks a
   second question of every deprecation the sweep returns. Is the replacement on
   the lower one? The entry's `issue` is a query of its own, and it reaches
   every entry filed under that number. The Feature the core announced the
   replacement in is among them.

   The version the core released it in settles that question. Where the number
   reaches no sibling, nobody wrote an entry for the replacement.
   `typo3_rule_lookup` with
   `documentId="extension/compatibility/a-declared-major-that-is-not-installed"`
   is the reading that closes it.

   **Where you do not owe the sweep.** A task that produces no change does not
   reach this step at all. The property is what the task produces. A triage, a
   reproduction and a review illustrate it; they are not the list you read it
   off. The sweep asks what a package will have to stop calling. A task that
   writes nothing is not going to call anything.

   The exemption ends where the workflow produces a change. A review asked to
   make the change is that other workflow. It starts this order again with the
   files it is about to write. To carry somebody else's patch onto current code
   is on the same side. It writes commits. The sweep says whether the code that
   moved under the patch deprecated something the patch calls.

   Skip the sweep only where the change touches no TYPO3 API: a code style
   fixer, a CI file, an `.editorconfig`. A deprecation is a statement about API
   the package calls. So a change that calls none has nothing for the sweep to
   land on. The sweep is empty before it runs.

   That condition is worth a statement, because this step is the largest answer
   the order asks for. It is one call per declared major, with that major's
   deprecations whole. You read which side a change falls on off the files it
   touches, never off the task it started as. One PHP file edited along the way
   puts it back among the ordinary ones.

   A skip there costs the deprecation no finding would have walked into. How
   small the change is decides nothing either. Three statements can call a
   deprecated API as easily as three hundred.

   A test file is one of those wherever it sits. It calls the API it exercises
   and the framework around it, and both deprecate. A fixture is exempt where it
   is data the suite reads, and not where it is a class.

**Before the reading**, write down what the order established. That is the
version that filters every later answer, the packages in scope, and the commands
this repository declares. Write down which steps what discharged. Those are
answers already in the session rather than a second reading. What the files show
belongs to the report at the other end. A caller who cannot see what an answer
rests on cannot tell it from one that rests on nothing.

**Then** read the checkout. Not before. A file list first makes everything after
the list look optional. The conventions then arrive as a footnote to a verdict
that has already formed.

**Before the first edit**, name the files this change will create, change or
delete. A deletion is the caller's to ask for, and this is somebody else's
checkout. It is the one act nothing here can put back.

**Last**, the report names every step of this order it did not reach, and what
stood in for it. That is an answer already in the session, a condition that made
the step empty, or an exemption. A reader cannot tell a step passed over in
silence from one somebody dropped.

## When the lookups run out

A behaviour question that survives the lookups above is one you read out of the
installed source. Do not guess at it. The class that implements the behaviour
and the one it inherits from answer it. That reading is the step after the
lookups. It replaces a change to the code until it works.

A first change that did not work is evidence about the reading. So the second
attempt at one failure reads the source rather than changes the code again.

What it settles is what this installation does and never what TYPO3 supports. So
a finding says that you could not settle the question beyond the version
installed. An answer built on the reading names the version it holds for.

## What each runtime lookup adds after the extension answer

`typo3_extension_describe` in step 2 says what one package registers. The
lookups below say what the installation resolved. That is a different fact even
where the words are the same. So step 2 has made none of these calls:

- `typo3_backend_module_lookup` — the tree position, the labels, the access
  level, the routes and the navigation component the parent module supplies.
  Step 2 lists the modules the package declares. A declaration cannot show that
  inheritance.
- `typo3_icon_lookup` — whether any installed package registers an identifier.
  That validates the ones a template uses. Step 2 lists the identifiers this
  package contributes.
- `typo3_label_lookup` — the labels as the installation resolves them, with its
  overrides applied. Step 2 lists the package's XLF files and the source
  language each declares, never what a unit says here.
- `typo3_fluid_namespace_list` — the prefixes any template may use without a
  declaration, from every package at once. Step 2 lists the package's own
  declarations. So an empty list there is no evidence that no package registers
  a prefix globally.
- `typo3_configuration_lookup` — the resolved configuration value, after every
  extension has had its say. For a form data group it gives the order the
  providers really run in. Step 2 answers nothing about that surface at all.
  What a registration declares is not what the installation resolves.
- `typo3_service_lookup` — the class the container really injects for a service
  id, an interface or a tag. Decorations and overrides count. Step 2 lists what
  the package's own `Services.yaml` declares, never what won.
- `typo3_schema_lookup` — the columns TYPO3 derives for a table from its TCA. It
  gives the type, the nullability and the default each one gets. Step 2 lists
  the tables the package registers and nothing about their shape.
- `typo3_flexform_lookup` — the data structure the installation resolves a
  `type=flex` field to, sheet by sheet, with listeners and migrations applied.
  Step 2 lists the content elements a package registers, never the structure
  each one's form builds.
- `typo3_record_lookup` — the rows of any table the installation has TCA for. It
  says how many there are and where they sit. It says what one column holds
  across them and which rows depart from its default. Step 2 has no row in its
  answer at all.

None of these says whether what it reports is right. `typo3_hint_lookup` and
`typo3_documentation_lookup` do. A subsystem its own runtime lookup confirmed
can still break every rule that governs it. So it is not established until you
asked both.

## A rule reads in both directions

It says what new code should do, and it says what this checkout already does
wrong. A file that settled into the opposite of a rule is a finding, not a local
style to preserve. Consistency with a project's own habit establishes nothing
about whether the habit is right.

## What the code is for is evidence, and the repository states it

A mechanism that costs something is not a defect because it costs. Before you
report one, find what it is there for and say so. That is the manual, the
README, the changelog, the setting that drives it, or the declared versions.

Where the documentation states a purpose, what you have is a trade-off, not a
defect. Name it with its cost and its alternative. Where you cannot find one,
the finding says that you could not establish one, not that none exists. If you
skip this, your review is a list of everything the author did on purpose.

## What a finding rests on is part of the finding

Three things carry one. A file you read, at its path and its line. A command you
ran, with what it printed. A mechanism you traced into an installed package. Say
which of the three it is. If you leave it unsaid, a finding from a CI file
weighs as much as one with a verified line.

Where one of the project's own commands would settle it, run it.
`typo3_project_describe` marks each command it lists **check**, **change** or
**unknown**, read off the declared body. A check reports and hands the code back
as it was. So even a task told not to change files runs it. The linter the
repository already declares is the cheapest evidence in it.

Do not run a change under that instruction. Name an unknown in the answer as
evidence that is available, and do not run it unasked. An unknown is a test
suite, a shell pipeline, a console command.

What a check prints is not the finding. The configuration that makes it fail is
still what the finding is about. The run takes that finding from derived to
established.

## What this server does not know

It does not read your working tree. You establish which files changed, which
branch you are on, and whether a path or an identifier still exists there. Then
pass the concrete paths back, because that turns a general convention into an
answer about this code.

## Query it in English

The knowledge is English and the match is lexical. So a query in another
language reaches the loanwords the two happen to share and nothing else.
Translate the subject before the call and the answer back afterwards, whatever
language you speak with the user.
```
