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.

Structure

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/DirectoryExplanation
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.
public/Public entry points; contains symlinks to other public resources. Prior to Contao version 4.13, the folder was called web/
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).
composer.jsoncomposer.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/DirectoryExplanation
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.
translations/Symfony translations. since 5.3 Can also be used for Contao translations.
composer.jsonAdd 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:

FileExplanation
.envDefaults for environment variables or environment variables that are agnostic to the environment. This is typically comitted to your project’s repository and thus should not contain any sensitive data.
.env.localLoaded if .env exists. Defines or overrides environment variables for the current environment (e.g. database and SMTP credentials). This should be added to your .gitignore as it typically contains sensitive data.
config/config.yamlConfiguration of any bundle/package/extension.
config/config_dev.yamlConfiguration for the dev environment.
config/config_prod.yamlConfiguration for the prod environment.
config/parameters.yamlParameters like database and SMTP server credentials.1
config/routes.yamlDefinition of application specific routes.2
config/services.yamlsince 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

imports:
    - { 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/DirectoryExplanation
contao/config/config.phpRegistering 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
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: ../src

If you want to implement regular controllers and define their routes via PHP attributes, you will also need to register these routes via the automatically loaded config/routes.yaml:

# config/routes.yaml
app.controller:
    resource: ../src/Controller
    type: attribute

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.