Drupal 11 Developer Certification Cheat Sheet
Exam Details: 90 minutes | 60 questions | 65% passing score
This comprehensive cheat sheet covers all four domains of the Acquia Certified Drupal Developer exam.
Domain 1: Fundamental Web Development (10%)
HTML & CSS Essentials
1<!-- Semantic HTML5 Elements -->2<header> - Page/section header3<nav> - Navigation section4<main> - Main content area5<article>- Self-contained content6<aside> - Sidebar content7<footer> - Page/section footerCSS Specificity Order (Low → High):
- Element selectors (
h1,p) - Class selectors (
.class) - ID selectors (
#id) - Inline styles (
style="") !important
Flexbox Quick Reference:
1.container {2 display: flex;3 justify-content: center; /* Main axis */4 align-items: center; /* Cross axis */5 flex-wrap: wrap;6}JavaScript Fundamentals
1// Variable declarations2const CONSTANT = "Cannot reassign"; // Block-scoped, immutable3let mutable = "Can reassign"; // Block-scoped, mutable4var legacy = "Function-scoped"; // Avoid in modern JS5
6// Arrow functions7const greet = (name) => `Hello, ${name}`;8
9// Promises10fetch('/api/data')11 .then(response => response.json())12 .then(data => console.log(data))13 .catch(error => console.error(error));14
15// DOM manipulation16document.addEventListener('DOMContentLoaded', function() {17 const element = document.getElementById('myId');18 element.addEventListener('click', handler);19});20
21// jQuery equivalent22$(document).ready(function() {23 $('#myId').on('click', handler);24});Git Version Control
| Command | Purpose |
|---|---|
git branch feature | Create new branch |
git checkout -b feature | Create and switch to branch |
git stash | Temporarily save changes |
git stash pop | Restore stashed changes |
git merge feature | Merge branch (creates merge commit) |
git rebase main | Replay commits linearly |
git push origin main | Push to remote |
git fetch origin | Download without merge |
HTTP Basics
Status Codes:
- 2xx Success:
200 OK,201 Created - 3xx Redirect:
301 Moved Permanently,302 Found - 4xx Client Error:
400 Bad Request,404 Not Found,403 Forbidden - 5xx Server Error:
500 Internal Server Error,503 Service Unavailable
HTTP Methods:
- GET - Retrieve data (idempotent)
- POST - Create resource (not idempotent)
- PUT - Update/replace resource (idempotent)
- PATCH - Partial update
- DELETE - Remove resource (idempotent)
Domain 2: Site Building (30%)
Content Types & Fields
Default Content Types:
- Article (with tags, body, image)
- Basic page (simple body content)
Field Types:
| Type | Use Case |
|---|---|
| Text (plain) | Titles, short strings |
| Text (formatted) | Rich content with HTML |
| Number (integer/decimal) | Numeric values |
| Boolean | On/off toggles |
| Date/Time | Date and time values |
| Entity Reference | Link to other entities |
| File/Image | Media uploads |
| Link | URL references |
Cardinality: How many values a field can store (1, limited, unlimited)
Widgets vs Formatters:
- Widget = Form input element (edit form)
- Formatter = Display output (view page)
Display Modes
View Modes (output display):
- Default/Full - Complete content view
- Teaser - Summary for listings
- Search result - Search listings
- Custom modes for specific contexts
Form Modes (input display):
- Default - Standard edit form
- Register - User registration form
- Custom modes for simplified forms
Taxonomy
1Vocabulary (container)2└── Terms (items)3 ├── Parent term4 │ ├── Child term5 │ └── Child term6 └── Another termKey Concepts:
- Vocabulary = Category group (e.g., "Tags", "Categories")
- Term = Individual classification label
- Hierarchy = Parent/child relationships via "Parent term" setting
Blocks
Placement: Structure → Block layout
Visibility Conditions:
- Pages - Show on specific URLs
- Content types - Show on specific content types
- User roles - Show to specific roles
- Language - Show for specific language
Custom Block Types: Add custom fields to blocks (like content types)
Menus
Default Menus:
- Main navigation - Primary site nav
- Footer - Footer links
- Administration - Admin menu
- User account menu - Login/logout links
Creating Submenus: Set "Parent link" on menu items
Views
Display Types:
| Type | Creates |
|---|---|
| Page | Standalone URL path |
| Block | Placeable block |
| Feed | RSS/XML output |
| Attachment | Attaches to other displays |
Key Components:
- Fields - What to display
- Filter criteria - What to include (fixed)
- Exposed filters - User-controlled filtering
- Sort criteria - Order of results
- Contextual filters - Dynamic from URL/context
- Relationships - JOIN related entities
- Pager - Pagination settings
Views Admin Path: /admin/structure/views
Configuration Management
1# Export config2drush config:export # or drush cex3
4# Import config5drush config:import # or drush cim6
7# Single config export8drush config:get system.site9
10# Rebuild cache11drush cache:rebuild # or drush crSync Directory: sites/default/files/config_*/sync or ../config/sync
Config vs Content:
- Config entities - Exportable (views, content types, fields)
- Content entities - User data (nodes, users, terms)
Multilingual
Core Modules:
- Language - Base multilingual framework
- Interface Translation - Translate UI strings
- Content Translation - Translate content entities
- Configuration Translation - Translate config
Language Detection Methods:
- URL prefix (
/en/,/fr/) - Domain (
en.site.com) - Browser settings
- User preference
Domain 3: Front-end Development / Theming (25%)
Theme Structure
1themes/custom/mytheme/2├── mytheme.info.yml # Required: Theme metadata3├── mytheme.libraries.yml # CSS/JS libraries4├── mytheme.theme # PHP preprocess functions5├── mytheme.breakpoints.yml # Responsive breakpoints6├── config/7│ └── install/8│ └── mytheme.settings.yml9├── css/10│ └── style.css11├── js/12│ └── scripts.js13├── templates/ # Twig template overrides14│ ├── page.html.twig15│ └── node--article.html.twig16└── logo.svgTheme Info File
1# mytheme.info.yml2name: My Theme3type: theme4description: 'A custom Drupal theme'5core_version_requirement: ^10 || ^116base theme: starterkit_theme # or 'false' for no base7
8libraries:9 - mytheme/global10
11regions:12 header: 'Header'13 content: 'Content'14 sidebar: 'Sidebar'15 footer: 'Footer'Libraries Definition
1# mytheme.libraries.yml2global:3 version: 1.04 css:5 theme:6 css/style.css: {}7 js:8 js/scripts.js: {}9 dependencies:10 - core/jquery11 - core/drupal12
13slider:14 css:15 component:16 css/slider.css: {}17 js:18 js/slider.js: {}Twig Syntax
1{# Comment - not rendered #}2
3{{ variable }} {# Output #}4{{ variable|filter }} {# Apply filter #}5{% if condition %}...{% endif %} {# Control structure #}6
7{# Common Filters #}8{{ title|upper }} {# Uppercase #}9{{ body|raw }} {# Output raw HTML (careful!) #}10{{ "Hello"|t }} {# Translation #}11{{ items|length }} {# Count items #}12{{ date|date("Y-m-d") }} {# Format date #}13
14{# Control Structures #}15{% if user.logged_in %}16 Welcome, {{ user.name }}17{% else %}18 Please log in19{% endif %}20
21{% for item in items %}22 {{ item.title }}23{% empty %}24 No items found25{% endfor %}26
27{# Including Templates #}28{% include 'partial.html.twig' %}29{% include 'partial.html.twig' with {'title': 'Hello'} %}30
31{# Template Inheritance #}32{% extends 'base.html.twig' %}33{% block content %}...{% endblock %}34
35{# Drupal-specific #}36{{ attach_library('mytheme/slider') }}37{{ content|without('field_image') }}Template Suggestions
Enable Twig Debugging:
1# development.services.yml2parameters:3 twig.config:4 debug: true5 cache: falseCommon Suggestion Patterns:
1node.html.twig # All nodes2node--article.html.twig # Article content type3node--article--teaser.html.twig # Article teaser view4node--123.html.twig # Specific node ID5
6field.html.twig # All fields7field--body.html.twig # Body field8field--node--body.html.twig # Body field on nodes9
10block.html.twig # All blocks11block--system-branding-block.html.twigPreprocess Functions
1// mytheme.theme2
3/**4 * Implements hook_preprocess_HOOK() for page templates.5 */6function mytheme_preprocess_page(&$variables) {7 $variables['my_custom_var'] = 'Hello World';8}9
10/**11 * Implements hook_preprocess_HOOK() for node templates.12 */13function mytheme_preprocess_node(&$variables) {14 $node = $variables['node'];15 16 // Add variable for specific content type17 if ($node->bundle() === 'article') {18 $variables['is_article'] = TRUE;19 }20}21
22/**23 * Implements hook_theme_suggestions_HOOK_alter().24 */25function mytheme_theme_suggestions_node_alter(array &$suggestions, array $variables) {26 $node = $variables['elements']['#node'];27 // Add custom suggestion based on field value28 if ($node->hasField('field_layout')) {29 $suggestions[] = 'node__' . $node->field_layout->value;30 }31}Attributes Object
1{# Default attributes #}2<div{{ attributes }}>3
4{# Add classes #}5<div{{ attributes.addClass('my-class', 'another-class') }}>6
7{# Remove class #}8<div{{ attributes.removeClass('unwanted') }}>9
10{# Set attribute #}11<div{{ attributes.setAttribute('data-id', node.id) }}>12
13{# Check if has class #}14{% if attributes.hasClass('active') %}Domain 4: Back-end Development / Coding (35%)
Module Structure
1modules/custom/mymodule/2├── mymodule.info.yml # Required: Module metadata3├── mymodule.module # Hooks and procedural code4├── mymodule.install # Install/update hooks5├── mymodule.routing.yml # URL routes6├── mymodule.services.yml # Service definitions7├── mymodule.permissions.yml # Permissions8├── mymodule.libraries.yml # Frontend assets9├── config/10│ ├── install/ # Default config11│ └── schema/ # Config schema12├── src/13│ ├── Controller/ # Page controllers14│ ├── Form/ # Form classes15│ ├── Plugin/ # Plugin classes16│ │ └── Block/ # Custom blocks17│ ├── Service/ # Service classes18│ └── EventSubscriber/ # Event subscribers19└── templates/ # Module templatesModule Info File
1# mymodule.info.yml2name: My Module3type: module4description: 'Module description'5core_version_requirement: ^10 || ^116package: Custom7
8dependencies:9 - drupal:node10 - drupal:viewsRouting
1# mymodule.routing.yml2mymodule.my_page:3 path: '/my-path/{node}'4 defaults:5 _controller: '\Drupal\mymodule\Controller\MyController::content'6 _title: 'My Page'7 requirements:8 _permission: 'access content'9 node: \d+10 options:11 parameters:12 node:13 type: entity:nodeServices
1# mymodule.services.yml2services:3 mymodule.my_service:4 class: Drupal\mymodule\Service\MyService5 arguments: ['@entity_type.manager', '@current_user']6 7 mymodule.event_subscriber:8 class: Drupal\mymodule\EventSubscriber\MySubscriber9 tags:10 - { name: event_subscriber }Controller
1<?php2namespace Drupal\mymodule\Controller;3
4use Drupal\Core\Controller\ControllerBase;5use Drupal\node\NodeInterface;6
7class MyController extends ControllerBase {8
9 public function content(NodeInterface $node) {10 return [11 '#theme' => 'my_template',12 '#node' => $node,13 '#cache' => [14 'tags' => $node->getCacheTags(),15 ],16 ];17 }18}Form API
1<?php2namespace Drupal\mymodule\Form;3
4use Drupal\Core\Form\FormBase;5use Drupal\Core\Form\FormStateInterface;6
7class MyForm extends FormBase {8
9 public function getFormId() {10 return 'mymodule_my_form';11 }12
13 public function buildForm(array $form, FormStateInterface $form_state) {14 $form['name'] = [15 '#type' => 'textfield',16 '#title' => $this->t('Name'),17 '#required' => TRUE,18 ];19 20 $form['submit'] = [21 '#type' => 'submit',22 '#value' => $this->t('Submit'),23 ];24 25 return $form;26 }27
28 public function validateForm(array &$form, FormStateInterface $form_state) {29 if (strlen($form_state->getValue('name')) < 3) {30 $form_state->setErrorByName('name', $this->t('Name must be at least 3 characters.'));31 }32 }33
34 public function submitForm(array &$form, FormStateInterface $form_state) {35 $this->messenger()->addMessage($this->t('Hello, @name!', [36 '@name' => $form_state->getValue('name'),37 ]));38 }39}Entity API
1// Load entities2$node = Node::load($nid);3$nodes = Node::loadMultiple([1, 2, 3]);4
5// Entity query6$nids = \Drupal::entityQuery('node')7 ->condition('type', 'article')8 ->condition('status', 1)9 ->sort('created', 'DESC')10 ->range(0, 10)11 ->accessCheck(TRUE)12 ->execute();13
14// Entity type manager15$storage = \Drupal::entityTypeManager()->getStorage('node');16$node = $storage->load($nid);17
18// Create entity19$node = Node::create([20 'type' => 'article',21 'title' => 'My Title',22]);23$node->save();24
25// Field access26$title = $node->getTitle(); // or $node->label()27$body = $node->get('body')->value;28$node->set('field_name', 'value');Database API
1// Select query2$results = \Drupal::database()->select('node_field_data', 'n')3 ->fields('n', ['nid', 'title'])4 ->condition('type', 'article')5 ->condition('status', 1)6 ->orderBy('created', 'DESC')7 ->range(0, 10)8 ->execute()9 ->fetchAll();10
11// Insert12\Drupal::database()->insert('mytable')13 ->fields(['name' => 'value'])14 ->execute();15
16// Update17\Drupal::database()->update('mytable')18 ->fields(['name' => 'new_value'])19 ->condition('id', 1)20 ->execute();21
22// Delete23\Drupal::database()->delete('mytable')24 ->condition('id', 1)25 ->execute();Common Services
1// Current user2$account = \Drupal::currentUser();3$uid = $account->id();4$is_admin = $account->hasPermission('administer site configuration');5
6// Entity type manager7$storage = \Drupal::entityTypeManager()->getStorage('node');8
9// Config10$config = \Drupal::config('system.site');11$site_name = $config->get('name');12
13// Editable config14$config = \Drupal::configFactory()->getEditable('mymodule.settings');15$config->set('my_setting', 'value')->save();16
17// State API (environment-specific values)18$last_run = \Drupal::state()->get('mymodule.last_run');19\Drupal::state()->set('mymodule.last_run', time());20
21// Logger22\Drupal::logger('mymodule')->error('Error message: @error', [23 '@error' => $error_message,24]);25
26// Messenger27\Drupal::messenger()->addStatus('Success!');28\Drupal::messenger()->addWarning('Warning!');29\Drupal::messenger()->addError('Error!');30
31// Cache32$cache = \Drupal::cache();33$item = $cache->get('my_cache_key');34$cache->set('my_cache_key', $data, Cache::PERMANENT, ['node:1']);Security Best Practices
1// NEVER do this2echo $user_input; // XSS vulnerability!3$query = "SELECT * FROM users WHERE id = $user_id"; // SQL injection!4
5// DO this instead6use Drupal\Component\Utility\Html;7use Drupal\Component\Utility\Xss;8
9// Escape output10$safe = Html::escape($user_input);11
12// Filter HTML (allows some tags)13$filtered = Xss::filter($user_input);14
15// Database placeholders (automatic escaping)16$result = \Drupal::database()->query(17 'SELECT * FROM {users} WHERE uid = :uid',18 [':uid' => $user_id]19);20
21// Check access22if ($node->access('view')) { ... }23if ($account->hasPermission('administer nodes')) { ... }Hooks Reference
1/**2 * Implements hook_help().3 */4function mymodule_help($route_name, $route_match) {5 if ($route_name === 'help.page.mymodule') {6 return '<p>' . t('Help text here.') . '</p>';7 }8}9
10/**11 * Implements hook_cron().12 */13function mymodule_cron() {14 // Run periodic tasks15}16
17/**18 * Implements hook_ENTITY_TYPE_presave().19 */20function mymodule_node_presave(NodeInterface $node) {21 // Modify node before saving22}23
24/**25 * Implements hook_ENTITY_TYPE_insert().26 */27function mymodule_node_insert(NodeInterface $node) {28 // React after node is created29}30
31/**32 * Implements hook_form_alter().33 */34function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) {35 // Modify any form36}37
38/**39 * Implements hook_form_FORM_ID_alter().40 */41function mymodule_form_node_article_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {42 // Modify specific form43}Testing
1// Unit Test - No Drupal bootstrap2namespace Drupal\Tests\mymodule\Unit;3
4use Drupal\Tests\UnitTestCase;5
6class MyUnitTest extends UnitTestCase {7 public function testSomething() {8 $this->assertEquals(1, 1);9 }10}11
12// Kernel Test - Minimal Drupal13namespace Drupal\Tests\mymodule\Kernel;14
15use Drupal\KernelTests\KernelTestBase;16
17class MyKernelTest extends KernelTestBase {18 protected static $modules = ['mymodule'];19 20 public function testSomething() {21 // Has database access22 }23}24
25// Functional Test - Full Drupal26namespace Drupal\Tests\mymodule\Functional;27
28use Drupal\Tests\BrowserTestBase;29
30class MyFunctionalTest extends BrowserTestBase {31 protected static $modules = ['mymodule'];32 protected $defaultTheme = 'stark';33 34 public function testSomething() {35 $this->drupalGet('/my-path');36 $this->assertSession()->statusCodeEquals(200);37 }38}Drush Commands
1# Cache2drush cr # Clear all caches3drush cc render # Clear specific cache bin4
5# Config6drush cex # Export configuration7drush cim # Import configuration8drush cget system.site # View config item9
10# Database11drush sql-cli # Open database CLI12drush sql-dump > backup.sql # Export database13drush sql-query "SELECT..." # Run query14
15# User16drush user:create username --mail="email@test.com"17drush user:password username newpass18drush user:role:add administrator username19
20# Modules21drush pm:list # List modules22drush pm:enable mymodule # Enable module23drush pm:uninstall mymodule # Uninstall module24
25# Updates26drush updb # Run database updates27drush updatedb:status # Check pending updates28
29# Queue30drush queue:list # List queues31drush queue:run myqueue # Process queueQuick Reference Card
Key Admin Paths
| Path | Purpose |
|---|---|
/admin/content | Content management |
/admin/structure/types | Content types |
/admin/structure/views | Views |
/admin/structure/block | Block layout |
/admin/structure/menu | Menus |
/admin/structure/taxonomy | Taxonomy |
/admin/config/development/configuration | Config sync |
/admin/config/development/performance | Caching |
/admin/modules | Modules |
/admin/appearance | Themes |
/admin/people/permissions | Permissions |
Essential Drush Commands
1drush cr # Clear cache (most used!)2drush cex # Export config3drush cim # Import config4drush updb # Database updates5drush uli # Generate login linkFile Naming Quick Reference
| File | Purpose |
|---|---|
*.info.yml | Module/theme metadata |
*.module | Procedural PHP hooks |
*.theme | Theme preprocess functions |
*.routing.yml | URL routes |
*.services.yml | Service definitions |
*.libraries.yml | CSS/JS assets |
*.permissions.yml | Permissions |
*.install | Install/update hooks |
Exam Tips
- Time Management: 90 seconds per question average
- Read Carefully: Watch for "NOT" and "EXCEPT" in questions
- Multi-select: Count the correct answers; they may say "Select TWO"
- Code Syntax: Exact method names matter (
loadMultiplenotload_multiple) - Drupal 11 Focus: Know deprecated vs current methods
- Practice: Use the review queue to practice before the exam
Good luck on your certification! 🎓
