$npx -y skills add edutrul/drupal-ai --skill drupal-twigDrupal Twig templates — best practices, auto-escaping, translation, library attaching, debugging, and SDC components.
| 1 | # Drupal Twig Best Practices |
| 2 | |
| 3 | ## Core Rules |
| 4 | |
| 5 | - Variables are auto-escaped — never use `|escape` unless you know the source |
| 6 | - Use `{% trans %}` for all user-facing strings |
| 7 | - Use `{{ attach_library() }}` for CSS/JS — never inline |
| 8 | - Never access services or run queries in Twig |
| 9 | |
| 10 | ## Translation |
| 11 | |
| 12 | ```twig |
| 13 | {# Simple string #} |
| 14 | {% trans %}Hello world{% endtrans %} |
| 15 | |
| 16 | {# With variable #} |
| 17 | {% trans %}Hello {{ name }}{% endtrans %} |
| 18 | |
| 19 | {# Plural #} |
| 20 | {% trans %} |
| 21 | @count item |
| 22 | {% plural count %} |
| 23 | @count items |
| 24 | {% endtrans %} |
| 25 | ``` |
| 26 | |
| 27 | ## Attaching Libraries |
| 28 | |
| 29 | ```twig |
| 30 | {{ attach_library('my_module/my-library') }} |
| 31 | {{ attach_library('mytheme/component-name') }} |
| 32 | ``` |
| 33 | |
| 34 | ## Safe Markup |
| 35 | |
| 36 | ```twig |
| 37 | {# Already sanitized by Drupal — safe to use |raw #} |
| 38 | {{ content.body }} |
| 39 | {{ content|raw }} |
| 40 | |
| 41 | {# Render a single field #} |
| 42 | {{ content.field_image }} |
| 43 | |
| 44 | {# Render with a specific view mode #} |
| 45 | {{ node|view('teaser') }} |
| 46 | ``` |
| 47 | |
| 48 | ## Debugging |
| 49 | |
| 50 | ```twig |
| 51 | {# Dump variables (requires Twig debug enabled) #} |
| 52 | {{ dump(content) }} |
| 53 | {{ dump(node) }} |
| 54 | {{ dump(_context|keys) }} |
| 55 | ``` |
| 56 | |
| 57 | ## Common Variables |
| 58 | |
| 59 | ```twig |
| 60 | {# Node template #} |
| 61 | {{ node.title.value }} |
| 62 | {{ node.bundle() }} |
| 63 | {{ node.id() }} |
| 64 | {{ node.isPublished() }} |
| 65 | |
| 66 | {# Check if field is set #} |
| 67 | {% if content.field_image|render %} |
| 68 | {{ content.field_image }} |
| 69 | {% endif %} |
| 70 | |
| 71 | {# Field items loop #} |
| 72 | {% for item in node.field_tags %} |
| 73 | {{ item.entity.name.value }} |
| 74 | {% endfor %} |
| 75 | ``` |
| 76 | |
| 77 | ## SDC Components (Drupal 10.3+) |
| 78 | |
| 79 | ```twig |
| 80 | {# Use a component #} |
| 81 | {% include 'mytheme:button' with { |
| 82 | label: 'Click me', |
| 83 | url: path('entity.node.canonical', {node: node.id()}), |
| 84 | variant: 'primary', |
| 85 | } %} |
| 86 | ``` |
| 87 | |
| 88 | ## Template Suggestions Hook |
| 89 | |
| 90 | ```php |
| 91 | #[Hook('theme_suggestions_node_alter')] |
| 92 | public function themeSuggestionsNodeAlter(array &$suggestions, array $variables): void { |
| 93 | $node = $variables['elements']['#node']; |
| 94 | $view_mode = $variables['elements']['#view_mode']; |
| 95 | $suggestions[] = 'node__' . $node->bundle() . '__' . $view_mode; |
| 96 | } |
| 97 | ``` |