---
title: "Configuration reference"
description: "An operation makes a checkout of a branch, an address of its own, a PHP version and an empty database."
canonical: configuration.html
navigation-title: "Configuration"
---

<a id="configuration"></a>

- [Start with a profile](#start-with-a-profile)
- [Complete example](#complete-example)
- [Editors](#editors)
- [Basic project settings](#basic-project-settings)
- [PHP and Node versions](#php-and-node-versions-1)
- [Review, issue and commit links](#review-issue-and-commit-links)
- [Worktree moments are not DDEV hooks](#worktree-moments-are-not-ddev-hooks)
- [Configuration precedence and branches](#configuration-precedence-and-branches)

<a id="configuration-reference"></a>

# Configuration reference

An operation makes a checkout of a branch, an address of its own, a PHP
version and an empty database. Everything beyond that is written in
`.ddev/branchery.yaml` or does not happen: without the file, no dependencies
are installed, no application is set up and no data is copied.

Start from a shipped profile, then override only what this project needs:

.ddev/branchery.yaml

```yaml
profile: typo3-app
```

`ddev branchery config:example --profile=typo3-app --write` writes exactly
that, and `--full` prints the shipped file it names, to read and to copy out
of.

| Key | What it says | Where |
| --- | --- | --- |
| `profile` | the shipped configuration this builds on | [Start with a profile](#configuration-profiles) |
| `docroot`, `bin` | what is served and where the binaries are | [Basic project settings](#configuration-basic) |
| `entrypoints` | the pages a worktree is opened at | [Basic project settings](#configuration-basic) |
| `php`, `node` | the versions, or where they are written down | [PHP and Node versions](#php-and-node-versions) |
| `install`, `configure`, `setup`, `migrate`, `finish` | what runs at each moment of a build | [Lifecycle moments](moments.md#configuration-moments) |
| `copy` | what travels from the source into a fork | [Copied files](copied-files.md#copied-files) |
| `data` | where the data comes from and what comes with it | [Data source and site addresses](data.md#data-source-and-site-addresses) |
| `links` | where review, issue and commit lead | [Review, issue and commit links](#configuration-links) |

<a id="configuration-profiles"></a>

<a id="start-with-a-profile"></a>

## Start with a profile

| Name | What it is |
| --- | --- |
| `typo3-app` | a TYPO3 instance, or an extension that builds one |
| `typo3-core` | the TYPO3 core repository, served from its own root |
| `symfony` | a Symfony application |
| `composer` | `composer install`, and nothing else |

They live in the image under `defaults/`, written in the grammar below, and
are the worked examples this documentation would otherwise have to repeat. What
a project writes beside `profile:` stands over them.

<a id="configuration-example"></a>

<a id="complete-example"></a>

## Complete example

.ddev/branchery.yaml

```yaml
profile: typo3-app     # the shipped configuration this is built on
docroot: web           # what is served; "" is the checkout itself
bin: .build/bin        # where this project's binaries are
php: "8.3"             # the version this is served with

# What the login of a worktree is made with, where the shipped
# "#Password1" will not do -- an application whose policy asks for more
# characters, or fewer. A development login and never a secret.
password: "#Password1"

# The pages worth opening, in the order they are offered. Written empty,
# the list says this project has none although its profile has.
entrypoints:
  - Backend: /typo3
  - Storybook: /storybook

# What opens a worktree on the machine reading the page, in the order they
# are offered. Written at all, these stand instead of the ones Branchery
# looks for; written empty, this project offers none.
editors:
  - VS Code: vscode://vscode-remote/wsl+{distribution}{path}
  - Zed: zed://file{path}

# Where the work in a checkout is talked about. The number comes out of the
# last commit's own trailers -- "Change-Id" and "Resolves: #12345" -- and is
# put where the placeholder is.
links:
  review: https://review.typo3.org/q/{change}
  issue: https://forge.typo3.org/issues/{issue}
  commit: https://github.com/typo3/typo3/commit/{commit}

# What travels from the source into a new worktree. A list is what travels;
# "except" keeps everything git ignores and takes these out.
copy:
  except:
    - node_modules

# Where a new worktree's data comes from, and what has to travel with it in
# order to be readable at all.
data:
  from: source         # the checkout this one was cut from; "none" for an empty database
  needs:
    - config/sites
    - config/system/settings.php
  addresses:
    - config/sites

# A moment is the lines it runs, in order. "inherit: profile" is where the
# shipped configuration does its own work, so this installs what typo3-app
# installs and builds the assets after. This is what most projects need.
install:
  - inherit: profile
  - npm ci
  - npm run build
  - composer: dump-autoload --classmap-authoritative

# Left out, the moment is what stands here and no more.
setup:
  - ./bin/install-fresh.sh
```

<a id="configuration-editors"></a>

<a id="editors"></a>

## Editors

Nothing is configured for this in most projects. Branchery looks for the mark an
editor leaves in the checkout it has been opened in — `.vscode` for VS Code,
`.idea` for PhpStorm — and offers the ones it finds, at the address that
opens a directory in them.

An address is a template, and one whose placeholders this machine cannot fill is
not an address here:

- **`{path}`**

  the worktree's own directory, as the host sees it
- **`{distribution}`**

  the WSL distribution the project lies in, and empty on every other host
- **`{windows}`**

  the same directory under the name Windows knows it by,
  `\\wsl$\<distribution>\...`, and empty where there is no distribution

That is what makes `vscode://vscode-remote/wsl+{distribution}{path}` fall away
on a Linux host without anything being asked about which host it is — and it is
also all the detection there is. Whether the editor is installed at all is not
something a page in a browser or a process in a container can see; an address
with no program behind it does nothing when it is pressed.

Write `editors:` to say it yourself. A project that writes the key has said
what it offers, and nothing is looked for any more.

Every key is optional, and every one of them is refused if it is spelled wrong
-- a key that silently did nothing would leave a worktree missing exactly the
step the file was written for.

> [!NOTE]
> The file is separate from `.ddev/config.yaml`: DDEV rewrites its own file
> when `ddev config` runs and would drop keys it does not know. Do not put
> project configuration in the generated `config.branchery.yaml` either; an
> add-on update replaces that file.

<a id="configuration-basic"></a>

<a id="basic-project-settings"></a>

## Basic project settings

`profile` names the shipped configuration this project builds on. Everything
written beside it overrides or extends that profile as described below.

`docroot` is the directory served for the worktree, relative to its checkout.
An empty string serves the checkout root. `bin` is the relative directory that
contains project commands and is exposed to configuration tasks as
`BRANCHERY_BIN`.

`entrypoints` are the pages a worktree is worth opening at, each written as
the word it is offered under and the path it stands at. Each is drawn in the row
that states its address, in the order they are written. A path and not a whole
address: the worktree's own is put in front of it. Every build ends by asking each
of them, and one that answers an error is a warning of the build.

The TYPO3 profiles offer the backend at `/typo3`. A project that serves it
somewhere else writes its own list, which stands instead of the profile's rather
than beside it — so it names everything the project offers:

```yaml
entrypoints:
  - Backend: /admin
```

`php` and `node` select runtimes and are described under
[PHP and Node versions](#php-and-node-versions).

<a id="php-and-node-versions"></a>

<a id="php-and-node-versions-1"></a>

## PHP and Node versions

`php:` and `node:` are a version, or the file in the checkout it stands in:

```yaml
php: "8.3"

node:
  read: Build/.nvmrc
```

`read` names the file and nothing more. A file made to hold a version — a
`.nvmrc`, a `.node-version` — holds one line and is read without being told
how: the first line that is neither blank nor a remark, without the `v` such a
file may or may not carry. A word rather than a number, `lts/iron`, is handed
on as it stands.

`match` is for a file that holds more than the version, and it is the only
reason to write one:

```yaml
php:
  read: Build/Scripts/runTests.sh
  match: 'PHP_VERSION="(\d+\.\d+)"'
```

That is what `typo3-core` says, because a core branch names the version it
tests itself with. A version written out is read before anything is created, so
a branch asking for a version the web image has no pool for is refused while
nothing exists yet. What was read, and out of which file, is said in the log of
the operation either way — and so is a file that held no version.

Most projects need neither spelling for Node: a checkout that says which version
it wants in the usual places — `.nvmrc`, `.node-version`, or the `engines`
of the root `package.json` — is read without being asked to, and `node:` is
for a project that keeps the file somewhere else. `typo3-core` is such a
project, which is where the lines above come from: its `.nvmrc` is under
`Build/`.

Nothing is served with Node, so nothing about it is refused ahead of time. The
version decides what a line like `npm ci` runs as — it is put in front of the
path for every line of the configuration, and it is `$BRANCHERY_NODE` and
`$BRANCHERY_NPM` for a line that would rather name it. Where nothing says
which version, the web container's own applies. A version that is not in the
cache is fetched once, into a cache that belongs to the project rather than to
the worktree: the next branch that asks for it has it without a download, and so
does this one after a restart.

<a id="configuration-links"></a>

<a id="review-issue-and-commit-links"></a>

## Review, issue and commit links

`links` says where the numbers in a checkout lead. Branchery reads the last
commit's trailers, `Change-Id` and `Resolves: #12345`, and draws the review
and the issue beside the worktree's own address. `links.commit` is how one of
the checkout's commits is addressed; where it says nothing, the sha stands as
text.

.ddev/branchery.yaml

```yaml
links:
  review: https://review.typo3.org/q/{change}
  issue: https://forge.typo3.org/issues/{issue}
  commit: https://github.com/typo3/typo3/commit/{commit}
```

The shape is the forge's: `…/commit/{commit}` on GitHub, GitLab and Gitea,
`…/commits/{commit}` on Bitbucket. The review is asked for by `Change-Id`
because Gerrit's change number is nowhere in the commit. An address without its
placeholder is refused. `typo3-core` answers for itself, since it is the TYPO3
repository.

<a id="configuration-not-hooks"></a>

<a id="worktree-moments-are-not-ddev-hooks"></a>

## Worktree moments are not DDEV hooks

DDEV's hooks fire at the moments of a *project*: `post-start`,
`post-import-db`, `pre-composer`. Branchery's moments are those of a
*worktree* being built, and DDEV has no hook for that — so a project's
`post-start` does not run when a worktree is created, and it should not: it
fires once for the project and usually assumes the project's own docroot. Where
the same work is wanted for every worktree, it belongs here.

<a id="configuration-precedence"></a>

<a id="configuration-precedence-and-branches"></a>

## Configuration precedence and branches

Branchery looks for the file in the worktree first and in the project checkout
second. A file committed to a branch therefore travels with it: a branch whose
build is different carries the difference in its own commit, every worktree of
it is right without anybody remembering a setting — and so is every branch made
from that one, because the file comes along with the checkout it was cut from.

A file it names under `profile:` is the same file in the same grammar, and
this one is laid over it: a key written here stands, a key left out is the
shipped one's. `links`, `data` and `copy` are laid over key by key, since
their keys are written one at a time — a project that says where its issues are
tracked keeps the review address it is built on. A list written empty is an
answer and not a silence: `copy: {except: []}` is how a project takes back
what the configuration it names keeps out of a fork.

Anything the file says that Branchery does not understand is refused, not
ignored. Every operation that touches such a worktree stops and says which key
it did not understand, the list itself keeps working, and where it is the
project's own file that is unusable the interface says so at the top of every
page.
