Skip to main content

Installation

Follow this page to create a Drupal module or theme from Eddy, starting in an empty directory. The overview then takes your extension through its first build, its checks and its first deployment to Drupal.org.

Before you start​

Make sure your machine has the following:

  • PHP 8.3 or newer, with SQLite support. A Drupal 12 build needs PHP 8.5.
  • Composer
  • Git
  • curl and tar
  • Node.js with npm, when you keep ESLint, Stylelint, CSpell or Jest
  • Ahoy or Make, when you choose a command wrapper
  • Google Chrome with a matching chromedriver, or with Node.js to download one, when you keep FunctionalJavascript tests. Docker replaces both after you set WEBDRIVER_BACKEND=selenium (see FunctionalJavascript tests).

The installer itself needs only PHP, curl and tar: you download it with curl, and it expands the template with tar. The other tools build, check and test the site. To publish your extension, you also need a GitHub account and push access to your project's repository on Drupal.org.

Overview​

  1. Run the installer in a new, empty directory and answer its questions.
  2. If you already have extension code, copy it into the directory.
  3. Build the site to check that everything works.
  4. Check coding standards.
  5. Run the tests.
  6. Create your extension's repository on GitHub.
  7. Commit your project and push it to that repository.
  8. Configure branch protection.
  9. Set up deployment to Drupal.org.
Starting from GitHub instead

Eddy is a GitHub template, so you can also create your repository with Use this template, clone it and run the installer in the clone. The installer then takes the repository for the README badges from the clone's origin remote. A clone doesn't record the Eddy release it came from. Without that record, the first update can't tell your changes from the template's, so every difference comes up for a decision.

Run the installer​

Download the installer into a new, empty directory and run it there:

curl -fsSL https://eddy.drevops.com/install > install.php && php install.php

The installer downloads the Eddy release shown in this site's footer and expands it into the directory. Then it asks about your extension, renames and rewrites the template's files to match and removes what you didn't select. At the end, it deletes itself unless you answer no to Remove this script. php install.php --version prints the release it installs.

The directory counts as empty while it holds only install.php and hidden entries such as .git, so an empty repository you've cloned works too. Create your GitHub repository without a README or a license file, because either one makes the directory non-empty. In a directory with other files, the installer works on the copy of Eddy already there when the directory holds your_extension.info.yml (see Install from a download). Otherwise, it stops and changes nothing.

To install from another archive, such as a fork's, set the INSTALL_TEMPLATE_ARCHIVE environment variable to its URL or path. The installer reads it only in an empty directory. The archive is a .tar.gz file with the files in 1 top-level directory, the layout that GitHub builds.

php install.php asking about the extension in a terminal, then installing it

The installer asks the following questions:

QuestionWhat it decidesDefault
Extension nameThe human-readable name, such as My Extension.My Extension
Machine nameThe machine name that files, classes and services are renamed after, such as my_extension. It starts with a lowercase letter and holds only lowercase letters, digits and underscores. Any other name stops the installer with an error.The extension name in lowercase, with underscores for spaces
Extension typeModule or theme.Module
Target Drupal versionsThe Drupal majors your extension supports: 10, 11 and 12. They decide the CI jobs, core_version_requirement, the README badges and the version a local build uses. See Drupal versions.11
Command wrapperAhoy, Makefile, both or neither. Without a wrapper, you run the tooling commands yourself.Neither
ToolsThe tools to keep: PHPCS, PHPStan, Rector, Twig CS Fixer, ESLint, Stylelint, CSpell, Jest, PHPUnit, FunctionalJavascript tests and Renovate.All 11
Keep example lifecycle scripts4 example lifecycle scripts, 1 per phase, that print a marker line when their phase runs.No
Remove this scriptWhether install.php deletes itself when it's done.Yes
Proceed with installationThe last check before the installer rewrites the template's files.Yes
Skip the prompts

Every answer can come from an environment variable instead, which helps in scripts and for AI agents. php install.php --help lists the variables. The list includes INSTALL_TEMPLATE_ARCHIVE, INSTALL_BRANCHES and INSTALL_GITHUB_REPOSITORY, which no question asks for. Set a variable in front of php install.php, such as INSTALL_BRANCHES=1.x,2.x php install.php.

Install from a download​

The installer also works on a copy of Eddy that you download yourself. That copy can be any release, such as the latest release, or the current code from Code > Download ZIP on GitHub. Expand the download into a new directory, then run the installer there to rewrite the files in place:

php install.php

Install the development version​

To try changes that aren't released, run the installer of the main branch from the development site:

curl -fsSL https://drevops-eddy-docs.netlify.app/install > install.php && php install.php

What the installer changes​

The installer works on the directory in place:

  • It renames the your_extension.* files, classes and tests after your machine name and replaces the placeholders inside them.

  • It keeps only what your extension type uses. A theme drops these parts of the example module:

    • The .module, .install, routing, services and menu links files
    • The service class and the settings form with its configuration schema
    • The Unit, Kernel and Functional tests that cover them
    • The module dependencies in the .info.yml

    A theme gets these instead:

    • base theme: false and its global library under libraries in the .info.yml
    • A .theme file that adds the element its JavaScript fills, with a Unit test of that hook
    • FunctionalJavascript tests that run with the theme as the default theme

    A module drops the .theme file and its Unit test.

  • It points the linter and test configurations at the custom directory of your extension's type only, web/modules/custom or web/themes/custom. To ship an extension of the other type with yours, see Bundled extensions.

  • It removes what you didn't select:

    • A Drupal major: its CI jobs, its README badge and its part of core_version_requirement. Without Drupal 10, phpunit.d10.xml goes too.
    • A tool: its configuration and its CI steps. Dropping PHPUnit drops FunctionalJavascript tests too, since they run on PHPUnit.
    • A command wrapper: its file and its command permission in .claude/settings.json.
    • The example lifecycle scripts, unless you keep them.

    The README keeps a badge for each PHP version your selected majors support, so a Drupal 10-only extension has no PHP 8.5 badge.

  • It turns README.dist.md and CONTRIBUTING.dist.md into your README.md and CONTRIBUTING.md, and downloads a placeholder logo.png with your extension name on it.

  • It deletes Eddy's own files: .eddy/, MIGRATION.md, SECURITY.md, LICENSE.txt, logo.svg, the .github/workflows/eddy-*.yml workflows and the Claude Code skills that maintain Eddy. scripts/eddy-tooling stays, because it installs the commands that your project runs. Drupal.org adds LICENSE.txt to every release it packages and asks projects not to commit one. GitHub reads a license only from a license file, so the README's license badge states GPL-2.0-or-later itself.

  • It uncomments the export-ignore lines of .gitattributes, so the release archives that Drupal.org packages leave out the development files, such as composer.dev.json, scripts and tests.

  • It sets the branch filters of the workflows from the first of these that applies:

    1. The branches in INSTALL_BRANCHES, such as 1.x,2.x
    2. The push branches of the .github/workflows/test.yml that your project commits, when you run the installer on a copy of Eddy in the root of an existing project
    3. 1.x

    A repository created with Use this template commits the template's own test.yml, which doesn't count. Such a repository gets 1.x unless you set INSTALL_BRANCHES. Deployment explains how to build and deploy other branches.

  • It sets extra.eddy.drupal-version in composer.dev.json to the Drupal major that a local build uses by default.

  • It records the Eddy release you installed as extra.eddy.version in composer.dev.json, so a later update can tell your changes from the template's. A branch download or the development version records its commit instead, such as 5.0.0-3-g1a2b3c4. A clone records nothing.

The installer names your GitHub repository in the README badges. It takes the repository from the first of these that applies:

  1. INSTALL_GITHUB_REPOSITORY, such as owner/my_extension
  2. The GitHub origin remote, when you run the installer in the root of a clone
  3. Your machine name, as both the owner and the name

The overview creates the repository after the install, so set INSTALL_GITHUB_REPOSITORY to its planned name when you run the installer. Otherwise, correct the badges in README.md before your first commit.

Next steps​

With the questions answered, build the site:

make build

Confirm that the site works: provision, the last of these commands, prints the site URL and a one-time login link. The link logs you in as admin.

Then work through the rest of the overview: coding standards, tests, the GitHub repository, branch protection and deployment.