Skip to content
TYPO3Dev Companion

TYPO3 Development Installation

Skill: typo3-development-installation

Bring the local development installation of a TYPO3 extension, sitepackage or project package into existence, or boot and repair the one the repository declares: the container, DDEV where it declares one, the unattended install, seeded demo content, and a site that will not come up.

Markdown source#

markdown
---
name: typo3-development-installation
description: 'Bring the local development installation of a TYPO3 extension, sitepackage or project package into existence, or boot and repair the one the repository declares: the container, DDEV where it declares one, the unattended install, seeded demo content, and a site that will not come up.'
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 Development Installation

Produce an installation the developer of this package can run, in an order where
each step decides what the next one can be. Keep this skill as routing and
workflow; never retain layout keys, environment defaults, command options or
package names — each of those belongs to a tool that releases on its own cycle
and none of them can be re-asked from here.

## Where this starts

Work through [references/base.md](references/base.md) first. One of its answers
is this workflow's entry condition rather than a failure: an installation lookup
that reports there is no installation to describe is the task, not the
disconnected server the base tells you to stop for. Stop for an error, continue
for that answer, and say which of the two came back.

Then, before anything is created:

- `typo3_server_scope` for whether an installation and a console can be reached
  at all. That is what separates a repository with nothing installed from one
  whose installation is merely not running. Skip it only where the base's
  installation lookup already described a booted installation, because that
  answer contains this one; everywhere else it is run, since a prescription that
  gets skipped teaches the next reader to skip the steps that matter.
- The calls in the base that read the installation are asked again once it
  exists. Asked before, they are unanswerable; asked after, they are what says
  the work succeeded.

The repository decides which of two shapes this task has, and only the first
step differs. A repository that already declares an installation — an
environment configuration, a document root, a site configuration, a lock file —
is booted from what it declares. One that declares none has an installation
created for it.

## Boot what the repository already declares

- Read the environment configuration whole before running anything. The scope
  answer names the interpreter and the commands the manifests declare; the
  lifecycle the environment runs by itself — the tasks bound to each stage, the
  configured data sources — lives in that file, and where the answer does not
  carry them, the file is the only place they are readable. Starting the
  environment runs them whether or not they were read.
- Read the repository's own instructions beside it. A project that ships a boot
  procedure has usually written down which data it is meant to be filled with,
  and that is not derivable from the code.
- Run the declared steps in the declared order, and change nothing that already
  works. Booting is not repairing, and a rewritten configuration that boots the
  same way is a change nobody asked for.
- Where a step fails, the finding is which declared step failed and on what —
  not a second procedure written beside the one the repository has.

## Create one where none is declared

1. **Make the package's own manifest the Composer root package.** It has to be
   able to install TYPO3 beneath itself, into a directory that is ignored. What
   that takes is the `extra` block the TYPO3 Composer installer reads plus the
   plugins the install has to be allowed to run: ask
   `typo3_documentation_lookup` at the version being installed, and read the
   installed installer package where the documentation is thinner than the
   question. Three properties of that step survive any version: a layout key
   that is no longer honoured is a warning rather than an error, so what the
   install printed is the evidence that the layout took; a package the core
   requires itself is not required again, because its own version line has
   nothing to do with the TYPO3 version and stating one makes the resolver fail;
   and a package that is its own Composer root is installed from the repository
   root rather than into the extension directory below the document root, so an
   empty directory there is not a broken installation.
2. **Declare the container.** Its project type and its document root follow from
   the layout decided above, not the other way round. Two things then have to be
   verified rather than assumed: that the environment fails its start when a
   provisioning task fails, because an install that failed behind a green start
   is the expensive failure of this step; and that a command which rewrites the
   environment configuration has not dropped what was set by hand, which is what
   reading the file back after such a command is for.
3. **Install non-interactively.** The console's setup command answers its own
   questions from a fixed set of environment variables, and `typo3_hint_lookup`
   with `id=environment-runtime-readers` names them. Ask
   `typo3_documentation_lookup` for the options at the version installed, and
   check two things in what it answers: the value a connection option accepts is
   not necessarily the value written into the settings afterwards, and the
   command refuses a database that already holds tables. The second is what
   makes an install script re-runnable or not — it needs its own guard on what a
   previous run left behind, and forcing the settings does not remove a schema.
4. **Seed the content the package is to be developed against**, where the task
   needs one. `typo3_hint_lookup` owns this: `id=sitepackage-initial-content`
   for which of the two setup commands imports it and what makes a package count
   as a distribution, `id=initial-content-import-once` for why a changed file
   does not arrive a second time, `id=initial-content-references` for what the
   import remaps and what it leaves pointing at a stranger. What this workflow
   adds is where to look when it lands: a seeded installation answering
   not-found at the project root is a site configuration whose base is not this
   installation's URL, which is the importer's doing and not the package's. Read
   what actually landed with `typo3_configuration_lookup`, correct it in the
   installation's own site configuration, and verify it again there.
5. **Decide what the install wrote into the repository.** The installation's
   configuration, its writable state and its document root land in the Composer
   root, which is the versioned repository itself. `typo3_hint_lookup` with
   `id=project-configuration-files` and `id=project-build-and-scripts` says
   which of them belong to the project and which are generated; the ignore rules
   follow from that answer and are written before the first commit, not after
   the first accidental one.

## The environment's settings against the installation's own

Where the local environment generates settings into the installation, the
generated file and the installation's own are one boundary with two owners, and
`typo3_hint_lookup` with `id=project-configuration-files` is what owns it. This
workflow adds the case that breaks it: such a generator knows only the services
it provides itself, so an installation deliberately put on something else — a
database the environment does not run, or none — has the generated file merged
over what the install wrote, and it can no longer connect. Taking the file over
is the documented way out, and it is a step of the install rather than a repair
afterwards. Establish what the merged result actually is with
`typo3_configuration_lookup` rather than from the files, because the merge is
what the installation runs on.

## Prove it, and how far depends on who wrote the sequence

A repository that was booted from what it already declares is proved by the site
answering: the backend, and the frontend on the URL the installation is
configured for. Nothing here is torn down to establish that. Booting is not
authoring, there is no new sequence to test, and an installation that was asked
for and then destroyed is a change nobody asked for.

Where the sequence was written in this session, it is proved from the state it
will be started in again, and every step below is part of the work:

1. Start from the state a colleague's clone is in — no installed dependencies,
   no installation, no container — and let the declared sequence run unattended.
   Anything that needed a hand is not part of the setup yet.
2. Prove that the site answers on both sides: the backend, and the frontend on
   the URL the installation is configured for.
3. Start it a second time without cleaning up. A setup that is not idempotent is
   a setup somebody will run twice.
4. Report the exact commands run, what each one printed, and what the
   installation now is: the document root, the console that reaches it, the URL
   that answered, and the database it is on. `typo3_task_guide` carries what a
   finished setup owes its user beyond that, credentials included; report what
   it names rather than a second version of it.
5. Draft the message for what the setup added to the repository with
   `typo3_commit_message_guide` and `workflow="project"`. The manifest, the
   container declaration and the ignore rules are that repository's own files,
   which is the workflow that argument names.

## Where this stops

This skill owns the installation a package is developed in: the Composer root
package that installs TYPO3 beneath it, the container the repository declares,
the non-interactive install, the content it is seeded with, and what the install
writes into the repository. It does not own hosting, deployment or backups, and
it does not own what runs against the installation once it answers.

The installation a suite boots is not this one, and the difference is what each
is for rather than how it is laid out. This workflow produces a site somebody
opens in a browser and clicks through, which is why the package's own manifest
becomes the Composer root. A package whose TYPO3 is installed below a build
directory, with the package linked in and no site to visit, is a test fixture
and belongs to `typo3-extension-testing` — a repository can have both, and
asking which one the task needs is the first thing that decides the layout.

Tests and static checks are `typo3-extension-testing`, and the crossing is
explicit in both directions. Going out: state the verified point — the document
root, the console command, the URL that answered, the database — stop before
editing that owner's files, and activate it. Coming in: a suite that needs a
served site and has none is this workflow first, up to that same verified point,
and then back.

References#

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 connected or not, and neither side notices. So the first
call below is also the check.

- No `typo3_` tool in this session, or a first call that errors: stop, say this
  workflow needs the server and it is not there, and name what came back.
- Do not fall back to general TYPO3 knowledge or start reading the checkout.
  That answer carries this workflow's order and confidence and none of its
  evidence, and nothing in it says which of the two it is.
- Continue only when asked to after saying so, and repeat it in the answer and
  in every finding a lookup would have carried.

## The order

This is the order, and it is an order rather than a list because each step
decides what the next one is worth. A convention fetched after the code has been
read confirms a view instead of testing it. A command recommended before the
project's own are known is a guess that sounds like advice. Where a step below
carries a condition for skipping it, that condition is narrow on purpose: a
prescription that gets skipped teaches the next reader to skip the ones that
matter too.

1. **`typo3_project_describe`** — the installation, its TYPO3 and PHP version,
   the extensions that are the project's own, its sites, and the commands this
   repository actually declares. Every later answer is filtered by that version,
   and a check the repository does not declare is a wrong answer however
   sensible it sounds. It ends with the whole procedures this server carries, as
   ids: that list is the only place they are named to a client that renders no
   resource list, and each one is a `typo3_rule_lookup` with that `documentId`
   rather than a search.
2. **`typo3_extension_describe`** for each extension in scope — what it
   registers, and what it ships beside that: its manual, its README, its test
   layers, its XLF files with the source language each one declares. What it
   does *not* ship is answered too, and that is the half no file listing can
   give you.

   Where step 1 reported none — a core checkout is the case, since it names the
   project's own extensions and not TYPO3's — that answer is this step, and
   there is nothing to call. Say so. A step passed over in silence cannot be
   told from one that was dropped.
3. **`typo3_task_guide`** with a short English task, the paths it touches, the
   target version and the change type — 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 brief is built
   from the paths as well as the task text, and no skill knows which paths the
   caller is holding: a skill that covers the task is not that brief, and
   skipping the step costs the hints and the core checks those paths match.
   Where the guide's own answer is what named this skill, this is one call for
   an answer already in the session. That is 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 this step is still
   owed. A brief that carried everything the lookup matched says so — "these are
   everything typo3_hint_lookup matches for these paths" — and there the call is
   made and asking again returns the same hints. One that stopped short says
   that instead and names the ids it left, and those are what is owed: fetch
   them by id rather than repeating the query. Read the sentence rather than the
   populated `hints` key, which is present either way and does not tell the two
   apart.
5. **`typo3_changelog_lookup` with `type: deprecation`**, at each major the
   package declares, bounded by `tag` and with the query omitted. Those three
   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, and the core titled those entries about its own code. Words taken
   from what step 2 reported are therefore matched against titles they were
   never written in, and the sweep comes back empty however right the query
   looks.

   Step 2 picks the tags instead. `ext:core`, `ext:frontend`, `ext:form` and the
   rest name the system extension a change is **in** — one call for each one the
   package requires, renders through or registers into, which is more than its
   manifest lists — and `TCA`, `TypoScript`, `Fluid`, `YAML`, `Backend`,
   `Frontend` name the surface, one for each kind of file it ships. An extension
   key of your own is not among them and matches nothing. Every call also
   returns every tag that version and type carry, so the second call onwards is
   read off the first rather than guessed at.

   Step 2 is what the answers are checked against, which is the other half the
   words were doing. Verify each identifier that comes back in the checkout — a
   deprecation nothing here calls is not a finding — and carry the
   `FullyScanned` / `PartiallyScanned` tag into the answer, because it says
   whether the Extension Scanner can find the remaining call sites or whether
   that reading is yours. Bounded this way the sweep is still writable before a
   file is opened, which is why it is a step of the order: one left to the
   reading reaches only what a finding stumbled into, and the deprecation that
   decides whether the package survives the next major is not usually the one a
   finding walks past.

   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 — 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 and section paths, never
   the text of a page, so a PHP identifier has no page to be titled after. It
   reaches whatever its own words happen to spell instead. An identifier goes to
   `typo3_changelog_lookup` under its own name, which reaches the entries
   writing it however the change was titled, and then 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 task that produces no change does not reach this step at all. The property
   is what the task produces, and a triage, a reproduction and a review are
   illustrations of it rather than the list it is read off. The sweep asks what
   a package will have to stop calling, and a task that writes nothing is not
   going to call anything. Followed literally in a triage of one issue it costs
   one call per declared major per tag and none of them bears on the report.

   The exemption ends where the workflow produces a change. A review asked to
   make the change is that other workflow, and it starts this order again
   holding the files it is about to write.

   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 and it is empty before it is run. That condition is worth stating
   because this is the most expensive step of the order: one call per declared
   major per tag. Which side a change falls on is read off the files it touches
   and never off the task it started as — one PHP file edited along the way puts
   it back among the ordinary ones, and a skip there costs the deprecation no
   finding would have walked into.

   A report names the step it did not reach, under either exemption. A step
   passed over in silence cannot be told from one that was dropped, which is
   what step 2 already asks of itself.

**Then** read the checkout. Not before: listing the files first makes everything
after the listing look optional, and the conventions arrive as a footnote to a
verdict that has already formed.

## When the lookups run out

A behaviour question that survives the lookups above is read out of the
installed source rather than guessed at. What answers it is the class that
implements the behaviour and the one it inherits from. That reading is the step
after the lookups, and what it replaces is changing the code until it works.
What it settles is what this installation does and never what TYPO3 supports. So
a finding says the question could not be settled beyond the version installed,
and an answer built on the reading names the version it holds for.

## Two kinds of lookup, and neither stands in for the other

`typo3_backend_module_lookup`, `typo3_icon_lookup`, `typo3_label_lookup`,
`typo3_fluid_namespace_list` and `typo3_configuration_lookup` report what is
registered, what a path resolves to, what a value really is at runtime. They
establish the facts of this installation and they are never a verdict on it.
`typo3_hint_lookup` and `typo3_documentation_lookup` say whether those facts are
right. A subsystem confirmed by its own runtime lookup can still break every
rule that governs it, so it is not established until both were asked.

## A rule is read in both directions

It says what new code should do, and it says what this checkout is already doing
wrong. A file that has 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 for costing it. Before
reporting one, find what it is there for — the manual, the README, the
changelog, the setting it is driven by, the versions the package declares it
supports — and say so. Where a purpose is documented, what you have is a
trade-off to name with its cost and its alternative, not a defect; where you
cannot find one, the finding says that it could not be established rather than
that none exists. This is the other direction of the rule above, and skipping it
turns a review into 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 that was read, at its path and its line; a
command that was run, with what it printed; a mechanism traced into an installed
package. Say which of the three it is. Leaving it unsaid gives a finding read
out of a CI file the weight of one with a verified line, and the reader has no
way to separate them again.

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, and the linter the
repository already declares is the cheapest evidence in it. A change is not run
under that instruction, and an unknown — a test suite, a shell pipeline, a
console command — is named in the answer as evidence that is available rather
than run unasked. What a check prints is not the finding: the configuration that
makes it fail is still what the finding is about, and the run is what takes that
finding from derived to established.

## What this server does not know

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

## Query it in English

The knowledge is written in English and matched lexically, so a query in another
language reaches the loanwords the two happen to share and nothing else.
Translate the subject before calling and the answer back afterwards, whatever
language you are speaking with the user.