Module Structure: Understanding the .info.yml Filefor Drupal 11 , and 10

Last updated :  

Welcome to your first real step into Drupal module development! If you've never built a Drupal module before — or you've only ever used Drupal as a site builder, clicking around the admin UI — this lesson is exactly where you should start. By the end of it, you'll understand the single file that turns an ordinary folder into a real, working Drupal module.

We're not going to build this module from scratch. Instead, we're going to open up a real, official module that ships with Drupal's own Examples project — called page_example — and read it together, line by line. Learning to read working code is one of the fastest ways to become a confident Drupal developer, and every lesson in this course follows the same approach: real code first, explanation second.

What exactly is a "module" in Drupal?

Think of Drupal core as a car's engine and chassis — it can drive, but it doesn't do much on its own. A module is a self-contained package of PHP, YAML, and other files that adds a specific capability to that engine: a new page, a new field type, an integration with another service, custom business logic — anything. Drupal itself is built almost entirely out of modules; when you enable "Comment" or "Search" from the admin UI, you're enabling a module someone else wrote. By the end of this course, you'll be writing your own.

Every module lives in its own folder, and every module folder needs exactly one special file to be recognized by Drupal at all. That file is what this lesson is about.

What you'll learn in this lesson

  • What a module's .info.yml file is, and why Drupal refuses to load a module without one
  • How to read YAML syntax — the format almost all Drupal configuration is written in
  • What each key in an .info.yml file actually controls, including the one that decides whether your module even shows up as compatible
  • Where to find your module in Drupal's admin UI once it has a valid .info.yml file
Before you start: this course assumes you have a local Drupal 11 site running (via DDEV, Lando, or similar) with the Drupal Examples module downloaded. Every lesson points at real files inside that project, and every screenshot comes from actually visiting the live site — not a mockup. If your own site's admin UI looks slightly different, that's normal; Drupal's core theme can vary by version and configuration.

Finding your way around: the Extend page

Before we look at the file itself, let's orient ourselves. Every module that Drupal can discover — whether it's currently switched on or not — shows up on one admin page: Extend, found at /admin/modules. This is the page you'll return to constantly throughout this course, so it's worth knowing what it looks like before you've filtered or searched for anything.

The unfiltered Extend page listing all modules on a Drupal 11 site

That's a lot of modules! Most of them ship with Drupal core itself (grouped under "Core" above) — things like Block, BigPipe, and Automated Cron. Your own modules, and third-party ones you install, will appear in their own groups further down the page, using a label controlled by a key you're about to meet: package.

The one file every module needs

Now for the main event. Every Drupal module — no exceptions — needs a file named {machine_name}.info.yml sitting directly inside its folder, where {machine_name} is the module's internal identifier (almost always the same as the folder name itself). For the module we're studying, that folder is named page_example, so the file is named page_example.info.yml.

Here's roughly what that module's folder looks like on disk — you don't need to memorize this, just notice that the .info.yml file sits right at the top level, not buried in a subfolder:

modules/page_example/
├── page_example.info.yml      ← the file this lesson is about
├── page_example.routing.yml
├── page_example.permissions.yml
├── page_example.links.menu.yml
├── src/
│   └── Controller/
│       └── PageExampleController.php
└── templates/
    └── description.html.twig

You'll meet every one of those other files later in this course — routing in the very next lesson. For now, all our attention is on that one .info.yml file.

A quick word about YAML

If you've never seen YAML before: it's a plain-text data format built around indentation and colons, designed to be easy for humans to read and write — no curly braces, no closing tags. Drupal uses it everywhere: module metadata, routing definitions, configuration exports, permissions. The one rule that trips up almost every newcomer is that YAML is picky about indentation — it uses spaces (never tabs), and how far a line is indented determines what it belongs to. Keep that in the back of your mind; we'll point it out again when it matters.

The source file

Path (relative to the Examples module's root): modules/page_example/page_example.info.yml

name: Page Example
type: module
description: 'Demonstrates how to display a page at a given URL.'
package: Example modules
core_version_requirement: ^10.3 || ^11.0
dependencies:
  - drupal:node
  - examples:examples

# Information added by Drupal.org packaging script on 2024-09-09
version: '4.0.4'
project: 'examples'
datestamp: 1725887893

Twelve lines. That's genuinely all it takes to register a module with Drupal. Let's go through every key.

How it works, key by key

name

name: Page Example

This is the human-readable label for your module — the text an administrator actually sees on the Extend page. It does not need to match the machine name (the folder/file prefix, page_example); it just needs to be clear enough that someone scanning a list of forty modules knows what this one does. Think of it the way you'd name an app on your phone's home screen, not the way you'd name a variable in code.

type

type: module

This tells Drupal what kind of extension this .info.yml file describes. The only other values Drupal accepts here are theme and profile — you'll never write those in this course, but it's worth knowing they exist, since themes (which control a site's visual design) use this exact same file format. Get this key wrong, or leave it out, and Drupal won't just misbehave — it will fail to load your extension at all.

description

description: 'Demonstrates how to display a page at a given URL.'

A one-sentence summary that appears directly under the module's name on the Extend page. Notice it's wrapped in single quotes — in YAML, quoting a string is usually optional, but it becomes necessary once the text contains punctuation or special characters that YAML's parser might otherwise misread. When in doubt, quote it; it never hurts.

package

package: Example modules

This controls which collapsible section your module appears under on the Extend page — you can see "Example modules" as one of those section headers once you enable a few Examples modules. Every module in the Drupal Examples project uses the exact same package name so they all cluster together. When you build your own module later in this course, you'll typically use your own project or company name here. Leave it out entirely, and Drupal files your module under a generic "Other" heading — technically fine, but not very helpful to whoever administers the site.

core_version_requirement

core_version_requirement: ^10.3 || ^11.0

This is the single most important compatibility key in the whole file, and the one most likely to bite you later if you copy an old tutorial verbatim. It uses Composer version constraint syntax — the same syntax you'd see in a composer.json file:

  • ^10.3 means "Drupal 10.3.x, or any later 10.x release" — but not 11.0.
  • || ^11.0 means "OR Drupal 11.0.x, or any later 11.x release."

Put together: this module works on Drupal 10.3 and up, all the way through the 11.x series. Drupal checks this constraint the moment you try to enable the module, and again whenever the cache is rebuilt. If your running core version doesn't satisfy it, the module is marked incompatible and simply can't be enabled — no error dialog, no crash, it just won't appear as an option. If you've seen older Drupal tutorials use core: 8.x instead, that's the pre-8.7 syntax; it's obsolete and won't work on Drupal 10 or 11.

dependencies

dependencies:
  - drupal:node
  - examples:examples

A YAML list (each line starting with -) of other modules that must already be present and enabled before this one can be turned on. Each entry follows the pattern project:module_machine_name:

  • drupal:node — the core Node module (project namespace drupal, since it ships with Drupal core itself).
  • examples:examples — the parent umbrella module of the whole Examples project (project namespace examples, since it's a separate downloaded package).

Drupal's installer reads this list automatically. If a dependency is missing or disabled, the Extend page grays out the "Install" checkbox and tells the administrator exactly which dependency is unsatisfied — you never have to write that validation logic yourself.

The packaging-script keys: version, project, datestamp

# Information added by Drupal.org packaging script on 2024-09-09
version: '4.0.4'
project: 'examples'
datestamp: 1725887893

Here's a detail that surprises a lot of newcomers: you never type these three lines yourself. They're injected automatically by Drupal.org's packaging script the moment a release is built and published — version is the tagged release number, project is the machine name of the Drupal.org project, and datestamp is a Unix timestamp of when that package was built. If you write your own custom module, its .info.yml simply won't have these keys until (and unless) you eventually publish it as a proper release. Seeing them in a downloaded module's code, as we are here, is a reliable sign you're looking at packaged, not locally-authored, code.

Common beginner mistake: mixing tabs and spaces, or getting the indentation of the dependencies list wrong. Because YAML has no closing braces to catch the error for you, a badly-indented file often just silently fails to parse the way you expect, rather than throwing an obvious error. If a module refuses to appear on the Extend page at all, a malformed .info.yml is one of the first things worth checking.

See it for yourself

Theory is only half the lesson — let's go confirm this actually works. On your own DDEV site, visit /admin/modules and type "Page Example" into the Filter box at the top of the page.

The Extend page filtered to show the Page Example module under the Example modules package

There it is: the exact name and description values from the YAML file, filed under the exact package heading we saw in the code — filtered down from that long unfiltered list you saw earlier. Every value on this page traces back to a specific key in the twelve-line file you just read. That's the whole relationship this lesson set out to teach: the YAML file is the single source of truth for how Drupal describes your module to the world.

Quick check: without scrolling back up, can you say which key controls the checkbox group heading ("Example modules") this module appears under? If you said package, you've got it.

Key takeaways

  • The .info.yml file is mandatory — Drupal will not discover or load a module that's missing one, no matter what other files exist in the folder.
  • The filename must exactly match the module's machine name: page_example.info.yml for a module living in the page_example/ folder.
  • core_version_requirement using Composer constraint syntax (like ^10.3 || ^11.0) is the modern compatibility key for Drupal 10 and 11 — the old core: 8.x key no longer works.
  • Dependencies must use the project:module_machine_name format, and Drupal's installer enforces them for you automatically.
  • The package key controls grouping on the Extend admin page — use a consistent name across a project's modules so they're easy to find together.
  • The version, project, and datestamp keys are added automatically by Drupal.org's packaging pipeline. Never add them by hand to a module you're writing yourself.

Coming up next

So Drupal now knows your module exists, what it's called, and which version of core it's compatible with. But knowing a module exists is different from knowing what happens when someone actually types a URL into their browser and hits enter. In the next lesson, we'll open up page_example.routing.yml and see exactly how Drupal decides what to show at a given web address — the very first step toward writing code that a real visitor will actually see.