一键导入
debug
Diagnose and fix common Drupal code-level issues (hooks, services, cache, entities, routes, permissions)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Diagnose and fix common Drupal code-level issues (hooks, services, cache, entities, routes, permissions)
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when reviewing Drupal code for standards compliance across PHP, JavaScript, CSS, Twig, YAML, SQL, and markup — dynamically loads relevant standards per file type using Drupal's official community guidelines.
How to write, modify, and style React components for Drupal Canvas and Drupal CMS. Covers the technology stack (React 19, Tailwind CSS 4, CVA variants, cn utility), component.yml prop and slot definitions, enum naming, image and video prop types, formatted text fields, color scheme variants, theme tokens from global.css, required folder structure (index.jsx + component.yml), and matching source site visual styles including extracting design tokens, dark backgrounds, gradient text, glass/blur effects, and updating global.css theme. Use when building, editing, or fixing any component in src/components/.
Managing content in Drupal CMS via JSON:API. Covers listing, fetching, creating, updating, and deleting pages and content entities using npm run content commands. Includes page component structure (uuid, component_id, inputs, parent_uuid, slot), image uploading and media target_id handling, formatted HTML text fields, UUID generation, input format reference mapping component.yml prop types to JSON formats, menu item listing (menu_items--main, menu_items--footer, menu_items--social-media are read-only via API — must use admin UI for create/update/delete), page path alias management, and common pitfalls like wrong media IDs, link props as objects instead of strings, plain strings for HTML fields, target_id as integer instead of string, langcode errors, and JSON:API read-only mode. Use when composing pages, adding components to pages, uploading images, or interacting with the Drupal CMS content API.
Search and fetch Canvas and Drupal CMS documentation pages. Takes a query describing what you need to know (e.g., "code components", "page regions", "known issues", "data fetching") and returns the full content of matching documentation pages. Invoke with /canvas-docs-explorer <query>. Use this whenever you need to understand how Canvas or Drupal CMS works, verify platform behavior, check for limitations, or find configuration options.
Use when setting up a new Drupal CMS project with Canvas for component development. Covers DDEV, JSON:API write mode, Canvas OAuth client, permissions, page regions, menus, project scaffolding, .env, CSS layer fix, and Storybook validation. Every step is idempotent — safe to re-run.
Step-by-step guide for scaffolding a new React component from scratch. Covers copying an existing component as a base, creating the required index.jsx and component.yml files, naming conventions (snake_case machineName, no project prefixes), creating the matching Storybook story file, reusing existing components via imports, composability patterns, and internal component setup (status false). Use when a needed component does not yet exist in src/components/ and must be created.
| name | debug |
| description | Diagnose and fix common Drupal code-level issues (hooks, services, cache, entities, routes, permissions) |
| version | 1.0.0 |
You are a Drupal 11 code-level debugging assistant. You help diagnose why custom code isn't working — hooks not firing, services not found, cache not invalidating, entities not loading, routes returning 403/404, etc.
This skill complements the ddev skill. Use ddev for environment issues (containers, ports, PHP errors). Use this skill for Drupal code logic issues.
Ask the user or detect from context:
| Category | Symptoms |
|---|---|
| Hook not firing | alter/presave/insert hook has no effect |
| Service not found | ServiceNotFoundException, class not found |
| Cache not invalidating | Stale content after save, changes not visible |
| Entity query issues | Empty results, access denied, wrong data |
| Route/controller 404 | Page not found for custom route |
| Permission/access 403 | Access denied unexpectedly |
| Plugin not discovered | Block/widget/formatter not appearing |
| Config issues | Missing config, schema mismatch, import fails |
| AJAX/form issues | Form rebuild fails, AJAX callback errors |
| Event subscriber not firing | Subscriber registered but never called |
Checklist:
# 1. Is the module enabled?
ddev drush pm:list --type=module --status=enabled | grep MODULE_NAME
# 2. Is the hook class registered in services.yml?
ddev exec grep -r "Hook\\" web/modules/custom/MODULE_NAME/MODULE_NAME.services.yml
# 3. Is autowire enabled for the hook class?
# Verify: either explicit service entry with autowire:true
# OR _defaults autowire with resource: src/ scanning
# 4. Is src/Hook/ excluded from autowiring?
# Check services.yml exclude list — src/Hook/ should NOT be excluded
# 5. Clear cache (hook discovery is cached)
ddev drush cr
# 6. Verify class is autoloaded
ddev exec drush php:eval "var_dump(class_exists('Drupal\\MODULE_NAME\\Hook\\CLASSNAME'));"
Common causes:
src/Hook/ listed in exclude: in services.yml — remove it from excludesautowire: true on the service definitionDrupal\{module}\Hook\{ClassName}# 1. Verify the hook function exists and has correct name
ddev exec grep -n "function MODULE_NAME_hook_name" web/modules/custom/MODULE_NAME/MODULE_NAME.module
# 2. Check for typos in hook name
# hook_form_alter, NOT hook_form_alters
# 3. Check module weight (maybe another module overrides)
ddev exec drush php:eval "print_r(\Drupal::moduleHandler()->getModuleList());"
# 1. Verify the form ID you're targeting
# Add temporary debug to see the actual form_id:
ddev exec drush php:eval "
// Enable Devel if available for form ID inspection
var_dump(\Drupal::hasService('devel.dumper'));
"
# 2. Check if you're using form_alter vs form_FORM_ID_alter
# hook_form_alter fires for ALL forms
# hook_form_user_login_form_alter fires only for that form
# 1. Check if the service ID exists in the container
ddev exec drush php:eval "var_dump(\Drupal::hasService('MODULE_NAME.service_name'));"
# 2. List all services from the module
ddev exec drush php:eval "
\$container = \Drupal::getContainer();
\$ids = \$container->getServiceIds();
foreach (\$ids as \$id) {
if (str_contains(\$id, 'MODULE_NAME')) { echo \$id . PHP_EOL; }
}
"
# 3. Check services.yml syntax
ddev exec php -r "var_dump(yaml_parse_file('/var/www/html/web/modules/custom/MODULE_NAME/MODULE_NAME.services.yml'));"
# 4. Regenerate autoloader
ddev composer dump-autoload
# 5. Clear all caches
ddev drush cr
Common causes:
use statement for the interface@logger.channel.MODULE_NAME without defining the channel)# 1. Check that render array has #cache metadata
# Missing #cache = Drupal uses default (permanent cache, no tags)
# 2. Verify cache tags are being set correctly
# After saving an entity, check if the right tags are invalidated:
ddev exec drush php:eval "
\$tags = ['node:123', 'node_list'];
\Drupal::service('cache_tags.invalidator')->invalidateTags(\$tags);
echo 'Tags invalidated: ' . implode(', ', \$tags);
"
# 3. Disable render cache temporarily for debugging
# In settings.local.php:
# $settings['cache']['bins']['render'] = 'cache.backend.null';
# $settings['cache']['bins']['page'] = 'cache.backend.null';
# $settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';
# 4. Clear specific cache bin
ddev drush cache:clear render
ddev drush cache:clear dynamic_page_cache
ddev drush cache:clear page
Checklist for render arrays:
// Every render array MUST have:
$build['#cache'] = [
'tags' => ['node:' . $node->id(), 'node_list'], // WHAT invalidates it
'contexts' => ['user.permissions', 'url.path'], // WHAT varies it
'max-age' => Cache::PERMANENT, // HOW LONG to cache
];
Common causes:
#cache tags — Drupal doesn't know when to invalidate#cache contexts — same cached version shown to all usersmax-age: 0 everywhere (kills performance)# Config changes not taking effect
ddev drush cr # Always first
ddev drush config:status # Check for overrides
ddev drush config:get MODULE.settings # Verify actual values
# 1. Debug the query
ddev exec drush php:eval "
\$query = \Drupal::entityTypeManager()
->getStorage('node')
->getQuery()
->accessCheck(TRUE)
->condition('type', 'article')
->condition('status', 1)
->range(0, 5);
\$ids = \$query->execute();
var_dump(\$ids);
"
# 2. Check accessCheck impact — compare with/without
ddev exec drush php:eval "
// With access check (respects permissions)
\$with = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
->accessCheck(TRUE)->condition('type', 'article')->count()->execute();
// Without access check (all entities, regardless of perms)
\$without = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
->accessCheck(FALSE)->condition('type', 'article')->count()->execute();
echo \"With access: \$with, Without access: \$without\";
"
# 3. Check if entities exist at all
ddev exec drush php:eval "
\$storage = \Drupal::entityTypeManager()->getStorage('node');
\$count = \$storage->getQuery()->accessCheck(FALSE)->count()->execute();
echo \"Total nodes: \$count\";
"
Common causes:
->accessCheck(TRUE) — required in Drupal 11, throws deprecation/errorfield_name not field_name.value for simple fields)# 1. Check if route exists
ddev exec drush eval "var_dump(\Drupal::service('router.route_provider')->getRouteByName('MODULE.route_name'));"
# 2. List all routes from module
ddev exec drush eval "
\$routes = \Drupal::service('router.route_provider')->getAllRoutes();
foreach (\$routes as \$name => \$route) {
if (str_contains(\$name, 'MODULE_NAME')) {
echo \$name . ' => ' . \$route->getPath() . PHP_EOL;
}
}
"
# 3. Rebuild router
ddev drush cr
# 4. Check routing.yml syntax
ddev exec php -r "var_dump(yaml_parse_file('/var/www/html/web/modules/custom/MODULE_NAME/MODULE_NAME.routing.yml'));"
Common causes:
_controller or _form in defaults# 1. Check user permissions
ddev exec drush php:eval "
\$account = \Drupal::currentUser();
echo 'User: ' . \$account->getAccountName() . PHP_EOL;
echo 'Roles: ' . implode(', ', \$account->getRoles()) . PHP_EOL;
echo 'Has permission: ' . var_export(\$account->hasPermission('PERMISSION_NAME'), true);
"
# 2. Check the route's access requirements
ddev exec drush eval "
\$route = \Drupal::service('router.route_provider')->getRouteByName('MODULE.route_name');
print_r(\$route->getRequirements());
"
# 3. Check if permission is defined
ddev exec grep -r "PERMISSION_NAME" web/modules/custom/MODULE_NAME/*.permissions.yml
Common causes:
_access: 'TRUE' missing quotes in routing.yml (must be string 'TRUE')# 1. Clear plugin cache
ddev drush cr
# 2. Check plugin manager can find it
ddev exec drush php:eval "
// For blocks:
\$definitions = \Drupal::service('plugin.manager.block')->getDefinitions();
foreach (\$definitions as \$id => \$def) {
if (str_contains(\$id, 'MODULE_NAME') || str_contains(\$id, 'my_block')) {
echo \$id . ' => ' . \$def['class'] . PHP_EOL;
}
}
"
# 3. Verify class is autoloaded
ddev exec drush php:eval "var_dump(class_exists('Drupal\\MODULE\\Plugin\\Block\\MyBlock'));"
# 4. Check for PHP syntax errors
ddev exec php -l web/modules/custom/MODULE_NAME/src/Plugin/Block/MyBlock.php
Common causes:
src/Plugin/Block/ for blocks)#[Block(...)] attribute#[Block] attribute missing id: parameter# 1. Check config status
ddev drush config:status
# 2. Check specific config
ddev drush config:get MODULE_NAME.settings
# 3. Check schema validation
ddev exec drush php:eval "
\$typed_config = \Drupal::service('config.typed');
\$config = \Drupal::config('MODULE_NAME.settings');
\$definition = \$typed_config->getDefinition('MODULE_NAME.settings');
var_dump(\$definition);
"
# 4. Check for missing schema
# If you see "No schema" warnings, create config/schema/MODULE_NAME.schema.yml
Common causes:
config/install/ but module was already installed (reinstall module)$config['MODULE.settings']['key'] = value)# 1. Check browser console for JS errors
# Open DevTools > Console, look for 400/500 responses
# 2. Check Drupal AJAX response
ddev exec drush watchdog:show --type=php --count=10
# 3. Verify callback method exists and returns correct type
# AJAX callback must return a render array or AjaxResponse
Common causes:
AjaxResponse)#ajax wrapper ID doesn't match element in form'#prefix' => '<div id="wrapper-id">' on the target element# 1. Check subscriber is registered
ddev exec drush php:eval "
\$dispatcher = \Drupal::service('event_dispatcher');
\$listeners = \$dispatcher->getListeners('kernel.request');
foreach (\$listeners as \$listener) {
if (is_array(\$listener)) {
echo get_class(\$listener[0]) . '::' . \$listener[1] . PHP_EOL;
}
}
"
# 2. Check services.yml has event_subscriber tag
ddev exec grep -A 3 "SubscriberClass" web/modules/custom/MODULE_NAME/MODULE_NAME.services.yml
Common causes:
tags: [{ name: event_subscriber }] in services.ymlgetSubscribedEvents() returns wrong event name# Is the module enabled?
ddev drush pm:list | grep MODULE_NAME
# Is the class autoloaded?
ddev exec drush php:eval "var_dump(class_exists('Drupal\\MODULE\\ClassName'));"
# Is the service registered?
ddev exec drush php:eval "var_dump(\Drupal::hasService('module.service'));"
# What config exists?
ddev exec drush config:list | grep MODULE_NAME
# Recent errors?
ddev exec drush watchdog:show --severity=Error --count=20
# PHP syntax check?
ddev exec php -l web/modules/custom/MODULE_NAME/src/ClassName.php
# Nuclear option — clear everything
ddev drush cr && ddev composer dump-autoload