---
title: "One Manifest: TYPO3 Extensions No Longer Need an ext_emconf.php"
url: "https://b13.com/core-insights/one-manifest-typo3-extensions-no-longer-need-an-ext-emconfphp"
date: 2026-09-15
modified: 2026-09-18
lastUpdated: 2026-09-18
---

# One Manifest: TYPO3 Extensions No Longer Need an ext_emconf.php

For twenty years, every TYPO3 extension carried a file called `ext_emconf.php`. It told the Extension Manager what the extension was, which version it had and which TYPO3 it worked with. Then [Composer](https://getcomposer.org/) came along, and every extension carried a second file that said the same things in a different way. Since September 2026, `composer.json` is the only manifest a TYPO3 extension needs, in TYPO3 itself, on extensions.typo3.org and in the tooling around it.

This is not a story about deleting a file. It is a story about how many pieces have to move before one file can go, and about the people who moved them.

The Background
--------------

When Kasper Skårhøj split TYPO3 into a core and extensions with version 3.5 in 2003, three things arrived together: the extension concept, the Extension Manager to install extensions, and the TYPO3 Extension Repository, TER for short, to share them. The first revision of the Core’s version history from October 2003 already has an `ext_emconf.php` in every system extension. The Extension Manager set `$_EXTKEY` to the folder name, included the file, and knew everything it needed.

A year later the `.t3x` archive format followed, because PHP 4 could not open a zip file. A `.t3x` is a serialized PHP array with every file of the extension in it, compressed, with an MD5 in front. The Extension Manager unpacked it and wrote a fresh `ext_emconf.php` from the metadata inside. The file an author uploaded was never the file that arrived at the other end.

With TYPO3 4.0 in 2006, the Extension Manager started downloading one big list of every version of every extension from a mirror: `extensions.xml.gz`. TYPO3 14.3 still fetches that very file today.

A Brief History
---------------

### Two Manifests

TYPO3 6.0 added zip support next to the `.t3x`, and Composer support matured through TYPO3 7 and 8. From then on every public extension had a `composer.json` for Composer projects and an `ext_emconf.php` for classic installations and for TER. We all kept them in sync by hand, by script or by CI, and every now and then a release went out with two files disagreeing about their own version number.

Helmut Hummel carried TYPO3’s Composer integration through those years: the Composer installers, the package artifact that replaced `PackageStates.php` in Composer mode, and in 2021 the change that made TYPO3 prefer the extension key declared in `composer.json` over the folder name. TYPO3 11.4 stopped reading `ext_emconf.php` in Composer mode altogether. Classic mode kept it, because classic mode had nothing else to read yet.

**The extension key deserves its own paragraph.** For eighteen years it simply was the folder name under `typo3conf/ext/`. The Composer installer derived a key from the package name when nobody declared one, `vendor/my-ext` became `my_ext`, and placed the extension in a folder of that name so the old rule kept holding. Since 11.4, the declared key wins. Since 14.0, the declared key is the only one there is.

### The TER Side

Until 2020 there were two ways to publish an extension: the upload form on typo3.org, or a SOAP endpoint the Extension Manager used behind the scenes. In autumn 2020 Oliver Bartsch built a REST API for TER, and in the following weeks he and I wrote [tailor](https://github.com/typo3/tailor), a small command line tool that registers keys, sets versions and publishes releases from a pipeline. TYPO3 11.0 rebuilt the Extension Manager around that API and dropped `.t3x` uploads. Every extension released by a GitHub Action today goes through Oli’s API.

TYPO3 14 and 15
---------------

TYPO3 14 is where the Core stopped waiting. In 14.0 we made `composer.json` mandatory for extension detection in classic mode. Helmut moved title and description to `composer.json` in the same release, and in 14.2 the version, the state and the constraints followed, with `ext_emconf.php` deprecated for good. Christian Kuhn (a.k.a. Lolli) removed the deprecated code for TYPO3 15 in May 2026. Since then the Core does not read `ext_emconf.php` at all.

In 14.3.7 the Extension Manager switched from the `.t3x` to the zip TER serves, verified by a SHA-256 published in the extension index. That patch depended on a TER column that existed in the database but never reached the API, because it was missing from the TCA. Three layers of fail-soft code and no test had kept that invisible for months. “Shipped” and “working” are different words.

What Changed in September 2026
------------------------------

One place still demanded `ext_emconf.php`: the upload to [extensions.typo3.org](https://extensions.typo3.org). TYPO3 no longer wanted the file, TER refused a zip without it.

The TER as of 14 September 2026 closes that gap:

- **composer.json is the manifest.** TER reads name, description, authors, autoload and the `require`, `conflict` and `suggest` sections. Composer constraints such as `^13.4` are translated into the range notation the Extension Manager understands, `typo3/cms-core` becomes the TYPO3 dependency, and other packages are matched against the composer names registered on TER.
- **ext\_emconf.php is optional.** If it is present, TER reads only what `composer.json` cannot express: the category, the state and an author company. Uploads with only an `ext_emconf.php` work as before. Nothing that publishes today stops publishing.
- **The zip you upload is the zip TER serves**, for every version that requires TYPO3 14.3 or newer. TER stores the archive byte for byte and publishes the SHA-256 of that file, so what you hash locally is what the Extension Manager verifies. Versions for older cores keep the rebuilt zip with a generated `ext_emconf.php`, because TYPO3 v13 in classic mode finds extensions by looking for that file.

**What you upload is what gets installed, and one file describes your extension everywhere.**

tailor just followed in its latest release. It validates `composer.json` the way TER reads it, checks `ext_emconf.php` only for what `composer.json` does not say, and `set-version` no longer stops when there is no `ext_emconf.php` to update.

How a composer.json for TYPO3 Should Look
-----------------------------------------

This is the part worth bookmarking. A complete manifest first, then what each field does and who reads it.

```

{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"vendor/my-ext"</span>,
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"typo3-cms-extension"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"My Extension - Adds the one thing your editors asked for"</span>,
    <span class="hljs-attr">"license"</span>: <span class="hljs-string">"GPL-2.0-or-later"</span>,
    <span class="hljs-attr">"homepage"</span>: <span class="hljs-string">"https://github.com/vendor/my-ext"</span>,
    <span class="hljs-attr">"authors"</span>: [
        {
            <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Jane Doe"</span>,
            <span class="hljs-attr">"email"</span>: <span class="hljs-string">"jane@example.org"</span>,
            <span class="hljs-attr">"role"</span>: <span class="hljs-string">"Developer"</span>
        }
    ],
    <span class="hljs-attr">"support"</span>: {
        <span class="hljs-attr">"issues"</span>: <span class="hljs-string">"https://github.com/vendor/my-ext/issues"</span>,
        <span class="hljs-attr">"source"</span>: <span class="hljs-string">"https://github.com/vendor/my-ext"</span>,
        <span class="hljs-attr">"docs"</span>: <span class="hljs-string">"https://docs.typo3.org/p/vendor/my-ext/main/en-us/"</span>
    },
    <span class="hljs-attr">"require"</span>: {
        <span class="hljs-attr">"php"</span>: <span class="hljs-string">"^8.2"</span>,
        <span class="hljs-attr">"typo3/cms-core"</span>: <span class="hljs-string">"^13.4 || ^14.3"</span>
    },
    <span class="hljs-attr">"suggest"</span>: {
        <span class="hljs-attr">"typo3/cms-dashboard"</span>: <span class="hljs-string">"Adds a widget with the numbers your editors asked for"</span>
    },
    <span class="hljs-attr">"autoload"</span>: {
        <span class="hljs-attr">"psr-4"</span>: {
            <span class="hljs-attr">"Vendor\\MyExt\\"</span>: <span class="hljs-string">"Classes/"</span>
        }
    },
    <span class="hljs-attr">"extra"</span>: {
        <span class="hljs-attr">"typo3/cms"</span>: {
            <span class="hljs-attr">"extension-key"</span>: <span class="hljs-string">"my_ext"</span>
        }
    }
}

```

- `<strong>name</strong>` is your Packagist name. TER stores it, and if your extension has no composer name registered yet, TER registers this one, provided it is well-formed and no other extension has claimed it. Other extensions reference you by this name in their `require`, and TER resolves it back to your extension key when it reads their manifests.
- `<strong>type</strong>` must be `typo3-cms-extension`. TYPO3 detects an extension by this type since 14.0, the Composer installer places it by this type, and TER rejects an upload without it.
- `<strong>description</strong>` is one string that becomes two fields: everything before the first “ - “ is the title, everything after it the description. This is how the Extension Manager fills its list since 14.0 and how TER fills its listing now. Without the separator, the whole string is the title. If you also ship an `ext_emconf.php` with a `title`, TER falls back to it when your description has no separator, so existing listings do not change.
- `<strong>license</strong>` is required by Packagist anyway. TER stores it now and reports it in the FAIR metadata; before September every TER extension claimed the default license there.
- `<strong>authors</strong>`: the first entry becomes the author name and email TER shows. A company has no field in `composer.json`; it is one of the three things an `ext_emconf.php` can still add.
- `<strong>support.docs</strong>` is the documentation URL. TER uses it for the documentation status and the FAIR metadata.
- `<strong>require</strong>` is the heart of it. `typo3/cms-core` becomes the TYPO3 dependency, and `^13.4 || ^14.3` is translated to `13.4.0-14.99.99`, which the Extension Manager understands. `php` stays a PHP dependency, `typo3/cms-fluid-styled-content` becomes `fluid_styled_content`, and a third-party extension is resolved through its composer name on TER. A package TER does not know, a Symfony component for instance, is left out of the dependency list, because a classic installation could not install it anyway. Declare the TYPO3 versions you actually test, nothing more generous.
- `<strong>suggest</strong>` is mapped the same way, but the values are free text, as Composer defines them.
- `<strong>autoload</strong>` points PSR-4 to `Classes/`. Composer needs it, and TYPO3 generates the class loading information for classic mode from it.
- `<strong>extra.typo3/cms.extension-key</strong>` is the key. Not the folder name, not derived from the package name, declared. Mandatory in Composer mode since 11.4 and in classic mode since 14.0.

### What to Leave Out

- `<strong>version</strong>`**.**[Composer](https://getcomposer.org/doc/04-schema.md#version) advises against it for tagged packages, and TYPO3 does not need it. TER takes the version from the publish command and the archive name, and the Extension Manager writes the version it downloaded into `extra.typo3/cms.version` on install. If you declare it anyway, it has to match what you publish; `tailor set-version` keeps it in sync.
- `<strong>extra.typo3/cms.Package.providesPackages</strong>`**.** It tells a classic installation which Composer packages your extension ships in its own `vendor` folder. Mandatory in classic mode since 15.0, but the Extension Manager fills it in on download since 14.3. Set it only if you build classic bundles by hand.
- `<strong>replace</strong>`**,** `<strong>provide</strong>`**,** `<strong>config</strong>`**,** `<strong>scripts</strong>`**.** Nothing in the TYPO3 tool chain reads them for extensions.

When You Still Need an ext\_emconf.php
--------------------------------------

Two cases remain. Your extension supports TYPO3 v13 or older in classic mode, because those versions look for the file. Or you want a category and a state on extensions.typo3.org, which `composer.json` cannot express. Keep it minimal and in sync.

```
<span class="hljs-meta"><?php</span>

$EM_CONF[$_EXTKEY] = [
    <span class="hljs-string">'title'</span> => <span class="hljs-string">'My Extension'</span>,
    <span class="hljs-string">'description'</span> => <span class="hljs-string">'Adds the one thing your editors asked for'</span>,
    <span class="hljs-string">'category'</span> => <span class="hljs-string">'plugin'</span>,
    <span class="hljs-string">'state'</span> => <span class="hljs-string">'stable'</span>,
    <span class="hljs-string">'version'</span> => <span class="hljs-string">'2.1.0'</span>,
    <span class="hljs-string">'constraints'</span> => [
        <span class="hljs-string">'depends'</span> => [
            <span class="hljs-string">'typo3'</span> => <span class="hljs-string">'13.4.0-14.99.99'</span>,
        ],
    ],
];

```

Publish From Your Pipeline
--------------------------

```

- run: composer require --dev typo3/tailor
- run: ./vendor/bin/tailor <span class="hljs-built_in">set</span>-version <span class="hljs-variable">${{ github.ref_name }</span>} --no-docs
- run: ./vendor/bin/tailor ter:publish <span class="hljs-variable">${{ github.ref_name }</span>} --comment <span class="hljs-string">"<span class="hljs-variable">${{ github.event.release.body }</span>}"</span>
  env:
    TYPO3_API_TOKEN: <span class="hljs-variable">${{ secrets.TYPO3_API_TOKEN }</span>}

```

tailor packs the contents of your extension directory, validates the manifests the way TER reads them, and tells you what TER would have complained about before it uploads anything. Only the files at the root of the extension count: a fixture extension under `Tests/` with its own `ext_emconf.php` can no longer veto a release with its version number.

What Comes Next
---------------

The `.t3x` is still generated for every version on TER. It stays until the Extension Managers of 14.2 and older have left the stage, then it goes away for versions that require 14.3 or newer. In the Core, the Extension Manager is losing its last `ext_emconf.php` readers in a patch under review, and two changes for TYPO3 15 follow: the “Composer Support of Extensions” module from the v11 migration retires, and the `.t3x` download fallback goes, which removes the last class that knows how to write an `ext_emconf.php`.

Thanks
------

A change that spans two decades and three code bases is never one person’s work:

- Kasper Skårhøj, for the extension concept in 2003 that made all of this possible in the first place
- Robert Lemke, for initially coding the SOAP API for TER.
- Helmut Hummel, for a decade of Composer integration and for moving the manifest data into `composer.json` in TYPO3 14
- Christian Kuhn, for the deprecations and the cleanup that made TYPO3 15 the first version without `ext_emconf.php`
- Oliver Bartsch, for the TER REST API and for most of tailor
- Thomas Löffler, who keeps extensions.typo3.org running and reviewed and merged the TER change within days

For me, this closes a gap I first ran into while writing sync scripts for two manifests years ago. If your extensions still carry both files, now is a good time to look at your `composer.json`. And if you run into anything on the way, let me know on GitHub, or [get in touch](https://b13.com/lets-connect).

More Articles by Benni Mack
---------------------------

  [ Benni Mack 18 September 2026

What’s ext\_emconf.php? What’s TER? And that does it have to do with Composer? The success story of decade-long support and upgrade paths!

 ](https://b13.com/core-insights/core-insights/one-manifest-typo3-extensions-no-longer-need-an-ext-emconfphp)  [ Benni Mack 27 January 2026

With TYPO3 v14, Camino introduces a default theme that removes friction from first installs. Why this matters—and how TYPO3 laid the groundwork.

 ](https://b13.com/core-insights/core-insights/blog/camino-the-need-for-a-default-theme-in-typo3-is-real)  [ Benni Mack 21 February 2022

Composer and TYPO3 have had a long journey together. Now, work done in v11 is paving the way for v12—so that TYPO3 can become a fully-realized standard Composer project and consequently, an even more secure CMS.

 ](https://b13.com/core-insights/core-insights/typo3-and-composer-weve-come-a-long-way)  [ Benni Mack 21 January 2022

In 2021, many individuals and companies sponsored bug fixes and new features in TYPO3. We’re grateful for their support!

 ](https://b13.com/core-insights/core-insights/thank-you-to-our-2021-sponsors)  [ Benni Mack 22 February 2021 6 min. read

I’m pretty sure that the new Folder Tree in TYPO3 v11.1 feature will make editors daily lives with TYPO3 easier.

 ](https://b13.com/core-insights/core-insights/typo3-new-folder-tree-and-javascript-frameworks)  [ Benni Mack 10 February 2021 2 min. read

Let’s see if we can improve TYPO3’s Page Tree, from a non-developer perspective

 ](https://b13.com/core-insights/core-insights/typo3-page-tree)  [ Benni Mack 11 January 2021 1 min. read

Welcome to Core Insights by b13 - what is this place?

 ](https://b13.com/core-insights/core-insights/episode-1-hello-world)

 ![](https://b13.com/core-insights/_pixel?page=1894)