# Drupal Code Patterns

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-trebormc-drupal-ai-agents-drupal-code-patterns`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [trebormc](https://agentstack.voostack.com/s/trebormc)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [trebormc](https://github.com/trebormc)
- **Source:** https://github.com/trebormc/drupal-ai-agents/tree/main/.claude/skills/drupal-code-patterns

## Install

```sh
agentstack add skill-trebormc-drupal-ai-agents-drupal-code-patterns
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

## About the "Reference:" paths below

Some patterns point to the contrib Examples module for extended reference. These templates are SELF-SUFFICIENT — the references are optional extra reading. Before following one, check it exists:

```bash
ssh web test -d $DDEV_DOCROOT/modules/contrib/examples && echo "examples available" || echo "examples not installed — skip the references"
```

## Service Class

```php
logger->info('Processing: @input', ['@input' => $input]);
    return ['result' => $input];
  }

}
```

## Form (FormBase)

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/form_api_example`

```php
get('mymodule.my_service'));
  }

  public function getFormId(): string {
    return 'mymodule_my_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state): array {
    $form['name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      '#required' => TRUE,
      '#maxlength' => 255,
    ];
    $form['actions'] = [
      '#type' => 'actions',
      'submit' => ['#type' => 'submit', '#value' => $this->t('Submit')],
    ];
    return $form;
  }

  public function validateForm(array &$form, FormStateInterface $form_state): void {
    if (strlen($form_state->getValue('name')) setErrorByName('name', $this->t('Name must be at least 3 characters.'));
    }
  }

  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $this->myService->process($form_state->getValue('name'));
    $this->messenger()->addStatus($this->t('Form submitted successfully.'));
  }

}
```

## Block Plugin

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/block_example`

```php
get('mymodule.my_service'));
  }

  public function build(): array {
    return [
      '#theme' => 'mymodule_block',
      '#data' => $this->myService->getData(),
      '#cache' => [
        'contexts' => ['user'],
        'tags' => ['mymodule:data'],
        'max-age' => 3600,
      ],
    ];
  }

}
```

## Routing & Controllers

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/page_example`

### mymodule.routing.yml

```yaml
mymodule.example_page:
  path: '/mymodule/example/{parameter}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\ExampleController::content'
    _title: 'Example Page'
    parameter: 'default_value'
  requirements:
    _permission: 'access content'
    parameter: '\d+'

mymodule.form_page:
  path: '/mymodule/form'
  defaults:
    _form: '\Drupal\mymodule\Form\MyForm'
    _title: 'My Form'
  requirements:
    _permission: 'access mymodule'
```

### Controller

```php
get('mymodule.my_service'));
  }

  public function content(string $parameter): array {
    return [
      '#theme' => 'mymodule_example',
      '#data' => $this->myService->getData($parameter),
      '#cache' => [
        'contexts' => ['url.path'],
        'tags' => ['mymodule:data'],
        'max-age' => 3600,
      ],
    ];
  }

}
```

## Hooks

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/hooks_example`

```php
 [
      'variables' => ['title' => '', 'items' => []],
      'template' => 'mymodule-custom',
    ],
  ];
}
```

## Caching Best Practices

Every render array MUST have cache metadata:

```php
public function build(): array {
  return [
    '#markup' => $this->getData(),
    '#cache' => [
      'contexts' => ['user', 'url.path', 'url.query_args'],
      'tags' => ['node:1', 'node_list', 'mymodule:data'],
      'max-age' => 3600,
    ],
  ];
}
```

For dynamic user-specific content, use lazy builders:

```php
$build['dynamic_part'] = [
  '#lazy_builder' => ['mymodule.lazy_builder:build', [$entity_id]],
  '#create_placeholder' => TRUE,
];
```

For comprehensive caching strategies, use the **performance-audit** skill.

## Batch API

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/batch_example`

```php
function mymodule_batch_process(array $items, array &$context): void {
  if (!isset($context['sandbox']['progress'])) {
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['max'] = count($items);
  }
  $batch_size = 10;
  $slice = array_slice($items, $context['sandbox']['progress'], $batch_size);
  foreach ($slice as $item) {
    // Process item...
    $context['sandbox']['progress']++;
  }
  $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}

function mymodule_execute_batch(): void {
  $batch = [
    'title' => t('Processing items...'),
    'operations' => [['mymodule_batch_process', [range(1, 1000)]]],
    'finished' => 'mymodule_batch_finished',
    'progress_message' => t('Processed @current of @total.'),
  ];
  batch_set($batch);
}
```

## Queue API

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/queue_example`

```php
mail(
      'mymodule', 'notification', $data['to'], $data['langcode'], $data['params']
    );
  }

}
```

Adding items: `\Drupal::queue('mymodule.email_sender')->createItem($data);`

## AJAX Forms

**Reference:** `$DDEV_DOCROOT/modules/contrib/examples/modules/ajax_example`

```php
 'select',
      '#title' => $this->t('Category'),
      '#options' => ['fruits' => $this->t('Fruits'), 'vegetables' => $this->t('Vegetables')],
      '#ajax' => [
        'callback' => '::updateItemsCallback',
        'wrapper' => 'items-wrapper',
        'event' => 'change',
      ],
    ];
    $form['items_wrapper'] = [
      '#type' => 'container',
      '#attributes' => ['id' => 'items-wrapper'],
    ];
    $category = $form_state->getValue('category', 'fruits');
    $form['items_wrapper']['item'] = [
      '#type' => 'select',
      '#title' => $this->t('Item'),
      '#options' => $this->getItemsByCategory($category),
    ];
    $form['actions'] = [
      '#type' => 'actions',
      'submit' => ['#type' => 'submit', '#value' => $this->t('Submit')],
    ];
    return $form;
  }

  public function updateItemsCallback(array &$form, FormStateInterface $form_state): array {
    return $form['items_wrapper'];
  }

  private function getItemsByCategory(string $category): array {
    $items = [
      'fruits' => ['apple' => 'Apple', 'banana' => 'Banana'],
      'vegetables' => ['carrot' => 'Carrot', 'broccoli' => 'Broccoli'],
    ];
    return $items[$category] ?? [];
  }

  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $this->messenger()->addStatus($this->t('Form submitted.'));
  }

}
```

## After Applying Any Pattern

```bash
# 1. Register new classes/files and rebuild caches:
ssh web drush cr

# 2. If you added/changed a route, verify it exists:
ssh web drush route | grep mymodule

# 3. Run quality checks (see the quality-checks skill) before presenting the code.
```

Where to see the result: forms/controllers at their route path, blocks via Block Layout (`/admin/structure/block`) after placing them.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [trebormc](https://github.com/trebormc)
- **Source:** [trebormc/drupal-ai-agents](https://github.com/trebormc/drupal-ai-agents)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-trebormc-drupal-ai-agents-drupal-code-patterns
- Seller: https://agentstack.voostack.com/s/trebormc
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
