Module and Theme Development
What to know
A custom module needs an .info.yml file with name, type: module, and core_version_requirement. Drupal 10 uses PSR-4 autoloading: src/ maps to the module namespace. Routing lives in *.routing.yml, services in *.services.yml, and hooks in a .module file or, in modern code, as attributes on OOP hook classes.
Hooks are the extension mechanism: hook_form_alter(), hook_entity_presave(), hook_theme(). Preprocess functions (template_preprocess_node(), mytheme_preprocess_page()) prepare variables before Twig renders. Twig templates use {{ variable }}, {% if %}, and filters; |t translates, |raw bypasses escaping (a security smell).
A theme needs .info.yml declaring base theme, regions, and libraries. Libraries (*.libraries.yml) declare CSS/JS with dependencies, and are attached via {{ attach_library() }} or render arrays. Render arrays are the currency of theming: #theme, #markup, #attributes, #cache. Twig auto-escapes; PHP render arrays do not.
Traps
.info.ymlvs.info: only YAML is valid in modern Drupal.- Hook naming:
MODULENAME_form_alter, notmymodule_form_alterfor a different module's hook-use the module that owns the hook. - Template suggestion order: more specific suggestions win (
node--article--teaser.html.twigbeatsnode.html.twig). - Library attachment: forgetting
dependencies(e.g.,core/jquery) breaks JS silently. - Twig debug: enabling it shows suggestions but must be off in production.
Rule of thumb
If you need to change output, prefer preprocess + Twig override; if you need to change behavior, use a hook or event subscriber. Never edit core or contrib directly.
Worked example
To add a CSS class to article teasers: implement mytheme_preprocess_node() and, when $variables['node']->bundle() === 'article' and view mode is teaser, append to $variables['attributes']['class'].