Build your first module 1: scaffold the module
This seven-part tutorial builds Event RSVP, a complete Drupal 11 module that lets visitors respond to event content. You write every file yourself, and by the end you have used routes, controllers, forms, services, the database layer, configuration, a block plugin, cache tags, permissions, access checks, and automated tests. This first part creates the module skeleton and installs it.
What you’ll build
Section titled “What you’ll build”In this part: an installable (and so far empty) module named event_rsvp, consisting of one directory and one info file. Over the whole series the module grows into:
- an RSVP form on event pages, storing responses in a custom database table
- a report page for administrators, listing who responded
- a block showing a live attendance count that stays correct under full page caching
- a settings form that controls which content types accept RSVPs
- permissions and a custom access check
- a kernel test and a functional test that prove all of it works
A module is many small files under one directory, not one large file. Each tutorial part adds its own piece to the event_rsvp module: part 1 the info file, part 2 routing and a controller, part 3 a form and a storage service, part 4 a settings schema, part 5 a block plugin, part 6 a permissions file and access check, part 7 the tests.
Prerequisites
Section titled “Prerequisites”- A local Drupal 11 site installed with the standard profile, per Set up your environment and Install Drupal core.
- Drush 13, available as
vendor/bin/drushin your project (see Essential tools). Commands are shown in DDEV form (ddev drush ...); switch them to native with the Personalize control in the top bar. - A terminal open at your project root (the directory containing
composer.jsonandweb/). - The administrator account you created during installation.
Step 1: Create the module directory
Section titled “Step 1: Create the module directory”Drupal discovers modules by scanning a fixed set of directories. Your own modules belong in web/modules/custom/; modules you install with Composer land in web/modules/contrib/. The directory name is the module’s machine name, and everything else keys off it: file names, the PHP namespace, configuration names.
mkdir -p web/modules/custom/event_rsvpMachine names are lowercase, with underscores, and must be unique across every module on the site. Choose carefully: renaming a module later means renaming its namespace, its tables, and its configuration.
Step 2: Describe the module in event_rsvp.info.yml
Section titled “Step 2: Describe the module in event_rsvp.info.yml”The info file is the only file a module is required to have. It must be named <machine name>.info.yml, so create web/modules/custom/event_rsvp/event_rsvp.info.yml:
name: Event RSVPtype: moduledescription: 'Lets visitors RSVP to event content. Companion module for the "Build your first module" tutorial.'package: Documentation examplescore_version_requirement: ^11dependencies: - drupal:nodeconfigure: event_rsvp.settingsEvery key, and why it is there:
nameanddescriptionare what site builders see on the Extend page/admin/modules.type: moduledistinguishes this from themes and profiles, which share the info-file format.packagegroups the module under a heading on the Extend page.core_version_requirement: ^11is a Composer-style version constraint. Drupal refuses to install the module on a core version outside it.dependencieslists modules that must be installed first. Thedrupal:prefix names the project the module ships with, sodrupal:nodemeans “the Node module from Drupal core”. Drupal offers to install dependencies for you when you install the module.configure: event_rsvp.settingsnames the route of the module’s settings form. Once that route exists (you define it in part 2 and build the form in part 4), the Extend page shows a Configure link next to the module. Until then Drupal hides the link, because the Extend page checks access to the route and treats a missing route as forbidden.
Step 3: Lay out the source directory
Section titled “Step 3: Lay out the source directory”Create the src/ directory next to the info file:
mkdir web/modules/custom/event_rsvp/srcsrc/ is where all of the module’s PHP classes live. Drupal autoloads them with PSR-4: the namespace Drupal\event_rsvp maps to web/modules/custom/event_rsvp/src/, so the class Drupal\event_rsvp\Controller\RsvpReportController lives in src/Controller/RsvpReportController.php. No registration step exists or is needed; create the file in the right place and Drupal can load the class.
This is the layout you build over the rest of the series:
Directoryevent_rsvp/
- event_rsvp.info.yml this part
- event_rsvp.routing.yml part 2
- event_rsvp.services.yml part 3
- event_rsvp.install part 3
- event_rsvp.links.menu.yml part 4
- event_rsvp.permissions.yml part 6
Directoryconfig/
Directoryinstall/
- event_rsvp.settings.yml part 4
Directoryschema/
- event_rsvp.schema.yml part 4
Directorysrc/
- RsvpStorage.php part 3
DirectoryAccess/
- RsvpAccessCheck.php part 6
DirectoryController/
- RsvpReportController.php part 2
DirectoryForm/
- RsvpForm.php part 3
- RsvpSettingsForm.php part 4
DirectoryHook/
- EventRsvpHooks.php part 3
DirectoryPlugin/
DirectoryBlock/
- RsvpCountBlock.php part 5
Directorytests/
Directorysrc/
DirectoryFunctional/
- RsvpFormTest.php part 7
DirectoryKernel/
- RsvpStorageTest.php part 7
One thing is deliberately missing from that tree: a event_rsvp.module file. Since Drupal 11.1, hooks are methods on classes in src/Hook/, marked with the #[Hook] PHP attribute, and this module never needs a .module file at all. You write your first hook class in part 3. The one exception is the .install file: install-time hooks such as hook_schema() must stay procedural, and part 3 covers that too.
Step 4: Install the module
Section titled “Step 4: Install the module”- Go to Administer → Extend
/admin/modules. - Find “Event RSVP” under the “Documentation examples” package.
- Check it and select Install.
ddev drush pm:install event_rsvpInstalling does more than flip a switch. Drupal records the module in the core.extension configuration, registers its services and hooks, creates any database tables its hook_schema() defines, and imports any configuration it ships in config/install/. The module has none of those yet, but parts 3 and 4 rely on this exact sequence, and you will reinstall the module there to trigger it again.
Troubleshooting
Section titled “Troubleshooting”The module does not appear on the Extend page
Section titled “The module does not appear on the Extend page”The info file’s name must be exactly the machine name plus .info.yml, in a directory with the same machine name: event_rsvp/event_rsvp.info.yml. Check for typos in both, and confirm the directory sits under web/modules/custom/. After moving or renaming files, run ddev drush cache:rebuild (ddev drush cr) so Drupal rescans the file system.
Installing fails with a YAML parse error
Section titled “Installing fails with a YAML parse error”YAML is indentation-sensitive and forbids tabs. Compare your file character by character against the listing in step 2, paying attention to the two-space indent under dependencies.
Installing fails with “core version requirement” errors
Section titled “Installing fails with “core version requirement” errors”Your site runs a core version outside ^11. This tutorial targets Drupal 11; check your version with ddev drush status.
Next steps
Section titled “Next steps”- Part 2: routes, controllers, and DI gives the module its first page.
- Anatomy of a Drupal project explains the directory layout around
web/modules/custom/. - Essential tools covers the Drush commands used throughout this series.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.