Starting your Development

There are two main cases when developing within Contao: either you want to customize your project-specific web application, or you want to create a re-usable extension - either for your own purposes or for others. In either case, the principles are the same. However, when creating an extension for Contao, there are some differences in structure and procedure. This articles covers the former case and also assumes the usage of the Contao Managed Edition. Another article explains how to create a reusable extension.

The purpose of this article is to show the directory structure of Contao and explain what goes where - and what to do initially for certain customization tasks.


After a fresh install of Contao, your project will have a certain initial file & directory structure (which is similar to the structure of a pure Symfony project using the symfony/skeleton for example).

File/Directory Explanation
assets/ JavaScript and CSS assets of the Contao framework and third parties.
config/ Application configuration files.
files/ Public or protected files manged by Contao’s file manager.
system/ Legacy folder for Contao 3 compatibility.
templates/ Customized Contao & Twig templates.
var/ Transient files like the application cache and log files.
vendor/ Composer’s vendor folder containing all dependencies (including Contao).
web/ Public entry points; contains symlinks to other public ressources.
composer.json composer.json of your project defining your dependencies and autoloading.

When customizing your web application, the following files and folders will usually be of interest. Some of those will need to be created manually:

File/Directory Explanation
config/ Application configuration.
contao/ Contao configuration and translations.
src/ Your own PHP code: controllers, event listeners for hooks and other services.
templates/ Templates for your own modules and elements, or customized existing templates.
composer.json Add dependencies, customize autoloading if required.

Contao 4.4 still uses the Symfony 3 directory structure. The config/ folder will be in app/config/ and the contao/ folder will be in app/Resources/contao/ instead.

Application Configuration

The configuration of a Symfony application is done within the config/ directory through various YAML files. The Contao Managed Edition automatically loads the following configurations, if present:

File Explanation
.env Parameters like database and SMTP server credentials.1
.env.local Loaded if both .env and .env.local are found. Overrides parameters for local development.
.env.dist Loaded if .env is not found. Contains default parameters for the application.
config/config.yaml Configuration of any bundle/package/extension.
config/config_dev.yaml Configuration for the dev environment.
config/config_prod.yaml Configuration for the prod environment.
config/parameters.yaml Parameters like database and SMTP server credentials.1
config/routes.yaml Definition of application specific routes.2
config/services.yaml since 4.9 Definition of services.3

Contao versions prior to 4.9 only support the *.yml file extension.

1 Contao still supports the legacy way of defining parameters in a Symfony application through the parameters.yaml. However it is best-practice to use the .env files instead. See also Symfony’s documentation for more information about the .env* files.

2 Contao versions 4.6, 4.7 and 4.8 only support the routing.yml file. Starting with Contao 4.9 all 4 variants (routes.yaml, routing.yaml, routes.yml and routing.yml) are supported. Prior to Contao 4.6 you will need to implement an App\ContaoManager\Plugin that implements the RoutingPluginInterface.

3 While Contao versions prior to 4.9 do not load a config/services.yml automatically, you can still import it in your config/config.yml via

    - { resource: services.yml }

Contao Configuration & Translations

Contao has its own configuration files in the form of PHP arrays, as well as translation files in the form of either PHP arrays or in an XLIFF format. These files are generally defined within the contao/ folder of your project’s root directory (or app/Resources/contao/ in Contao 4.4).

File/Directory Explanation
contao/config/config.php Registering modules, content elements, models, hooks, crons, etc.
contao/dca/ Data Container Array customizations and definitions.
contao/languages/ Contao translations - contains sub directories for each language.
contao/languages/de/ German translations.
contao/languages/en/ English translations (also serves as the fallback).
contao/languages/…/ etc.

Have a look at the DCA documentation on how to handle DCA files and the translation documentation on how to handle translation files. The content of the config.php depends on the use-case and its documentation is covered in the applicable topics of the framework documentation.

Autoloading, Services and Controllers

Any custom class that is needed for the web application - like a Model, a Content Element or a class for a Hook for example - needs to be loaded. Typically the code for customizing the web application for Contao will be put in the \App namespace. The default composer.json of the recent Contao Managed Edition versions already contains the appropriate autoloading directive:

    "autoload": {
        "psr-4": {
            "App\\": "src/"

This is actually not necessary since Contao 4.9 as the Contao Managed Edition automatically adds the necessary autoloading for the App\ namespace. However you can still add your own autoloading directives of course.

During development it is advisable to not optimize the Autoloader by Composer, otherwise new classes that you created will not be available immediately. If you used the -o/--optimize-autoloader option previously, or the Contao Manager (which optimizes the Autoloader by default), execute composer install or composer dump-autoload without the parameter again. Then in the production environment you should use the parameter again for performance reasons.

Any of these classes can also be registered as Symfony services, which is necessary if you want to use dependency injection and service tagging. Registering Contao hooks, content elements, front end modules, cron jobs and Data Container callbacks is done via service tags.

For the application development it is recommended to use autowiring and auto-configuration, which makes it easy to create new services and tag them automatically (through annotations or class interfaces) - and thus enables quick development of Contao content elements, hooks, callbacks etc.

# config/services.yaml
        autowire: true
        autoconfigure: true

        resource: ../src

        resource: ../src/Controller
        tags: ['controller.service_arguments']
# config/routes.yaml
    resource: ../src/Controller
    type: annotation

The above services.yaml and routes.yaml also contain configurations for using controllers and routes. You will need to create the src/Controller/ folder, otherwise there will be an error during cache warmup. If you do not plan to use any controllers, simply remove the routes.yaml and the the respective service registration from the services.yaml.

Once this is configured, hooks, callbacks, content elements and front end modules for example can be created without having to configure them in separate files by using annotations for service tagging.

All this is not needed, if you only need to change or extend the Data Container Array definition of a table, or want to change a translation for example.

Starting with Contao 4.9 (Managed Edition), any class within the App\ namespace within src/ will be automatically registered as a service, with autowiring and autoconfiguration enabled. Controllers as services will work as well. However, any class extending from a class from the legacy Contao framework will not be automatically registered as a service (as well as any class that would cause an error during compilation). You can still provide your own services.yaml in order to adjust the service registration to your needs. Keep in mind that you still need to provide your own routes.yaml in order to register your routes.

Next: create your first DCA adjustment.