Skip to content

Latest commit

 

History

History
1066 lines (813 loc) · 34.3 KB

File metadata and controls

1066 lines (813 loc) · 34.3 KB

AGENTS.md

AI coding guide for [PROJECT_NAME] Drupal project.

AI Response Requirements

Communication style:

  • Code over explanations - provide implementations, not descriptions
  • Be direct, skip preambles
  • Assume Drupal expertise - no over-explaining basics
  • Suggest better approaches with code
  • Show only changed code sections with minimal context
  • Complete answers in one response when possible
  • Use Drupal APIs, not generic PHP
  • Ask if requirements are ambiguous

Response format:

  • Production-ready code with imports/dependencies
  • Inline comments explain WHY, not WHAT
  • Include full file paths
  • Proper markdown code blocks

Project Overview

  • Platform: Drupal [VERSION] [single site | multisite]
  • Context: [Brief description]
  • Architecture: [Architecture description]
  • Security: [Security level]
  • Languages: [single | multilingual: list languages]
  • Custom Entities: [List if any]
  • Role System: [Describe roles]
  • Use Cases: [Main use cases]

Date Verification Rule

CRITICAL: Before writing dates to .md files, run date command first. Never use example dates (e.g., "2024-01-01") - always use actual system date.

Git Workflow

Branches: main (prod), staging (test), feature/*, bugfix/*, hotfix/*, release/*

Flow: stagingfeature/name → PR → merge to staging → eventually main

Hotfix: mainhotfix/name → merge to main + staging, tag release

Commit format: [type]: description (max 50 chars) Types: feat, fix, docs, style, refactor, perf, test, chore, config

Before PR: Run phpcs, phpstan, tests, drush cex

Don'ts: No direct commits to main/staging, no --force on shared branches, no credentials in code

.gitignore essentials:

web/core/
web/modules/contrib/
web/themes/contrib/
vendor/
web/sites/*/files/
web/sites/*/settings.local.php
.ddev/
node_modules/
.env

Development Environment

Web root: web/ (or docroot/, html/ - check for dir with index.php and core/)

Setup

git clone [REPOSITORY_URL] && cd [PROJECT_DIR]
ddev start && ddev [BUILD_COMMAND]

DDEV: ddev ssh (container), ddev describe (info), ddev drush [cmd]

Custom DDEV Commands

Location: .ddev/commands/host/[name] WARNING: Don't use ## #ddev-generated comments - they break command recognition.

Drush Commands

# Core commands
ddev drush status                    # Status check
ddev drush cr                        # Cache rebuild
ddev drush cex                       # Config export
ddev drush cim                       # Config import
ddev drush updb                      # Database updates

# Database & PHP eval
ddev drush sql:query "SELECT * FROM node_field_data LIMIT 5;"
ddev drush php:eval "echo 'Hello World';"

# Test services and entities
ddev drush php:eval "var_dump(\Drupal::hasService('entity_type.manager'));"
ddev drush php:eval "\$node = \Drupal::entityTypeManager()->getStorage('node')->load(1); var_dump(\$node->getTitle());"
ddev drush php:eval "var_dump(\Drupal::config('system.site')->get('name'));"
ddev drush php:eval "var_dump(\Drupal::service('custom_module.service_name'));"

# Quick setup (pull from platform)
ddev pull platform -y && ddev drush cim -y && ddev drush cr && ddev drush uli

Composer

ddev composer outdated 'drupal/*'                    # Check updates
ddev composer update drupal/[module] --with-deps     # Update module
ddev composer require drupal/core:X.Y.Z drupal/core-recommended:X.Y.Z --update-with-all-dependencies  # Core update

Scripts in composer.json: build, deploy, test, phpcs, phpstan

Environment Variables

Store in .ddev/.env (gitignored). Access: $_ENV['VAR']. Restart DDEV after changes.

Patches

Structure: ./patches/{core,contrib/[module],custom}/

In composer.jsonextra.patches:

"drupal/module": {"#123 Fix": "patches/contrib/module/fix.patch"}

Sources: local files, Drupal.org issue queue, GitHub PRs Always include issue numbers in descriptions. Monitor upstream for merged patches.

Code Quality Tools

# PHPStan - static analysis
ddev exec vendor/bin/phpstan analyze web/modules/custom --level=1

# PHPCS - coding standards check
ddev exec vendor/bin/phpcs --standard=Drupal web/modules/custom/

# PHPCBF - auto-fix coding standards
ddev exec vendor/bin/phpcbf --standard=Drupal web/modules/custom/

# Rector - code modernization (run in container)
ddev ssh && vendor/bin/rector process web/modules/custom --dry-run

# Upgrade Status - Drupal compatibility check
ddev drush upgrade_status:analyze --all

Config files: phpstan.neon, phpcs.xml, rector.php Run before: commits, PRs, Drupal upgrades

Testing

# PHPUnit
ddev exec vendor/bin/phpunit web/modules/custom
ddev exec vendor/bin/phpunit web/modules/custom/[module]/tests/src/Unit/MyTest.php
ddev exec vendor/bin/phpunit --coverage-html coverage web/modules/custom

# Codeception
ddev exec vendor/bin/codecept run [acceptance|functional|unit]
ddev exec vendor/bin/codecept run --steps --debug --html

# Debug failed tests
ddev exec vendor/bin/phpunit --testdox --verbose [test-file]

Drupal test types (in tests/src/): Unit/ (isolated), Kernel/ (minimal bootstrap), Functional/ (full Drupal), FunctionalJavascript/

Codeception structure: tests/{acceptance,functional,unit,_support,_data,_output}/, config: codeception.yml

Debugging

# Xdebug
ddev xdebug on|off                              # Toggle (disable when not debugging for perf)

# Container & DB access
ddev ssh                                         # Web container
ddev mysql                                       # MySQL CLI
ddev mysql -e "SELECT..."                        # Direct query
ddev export-db --file=backup.sql.gz             # Export
ddev import-db --file=backup.sql.gz             # Import

# Logs
ddev logs -f                                     # Container logs (follow)
ddev drush watchdog:show --count=50 --severity=Error

# State
ddev drush state:get|set|delete [key] [value]

IDE: PhpStorm (port 9003), VS Code (PHP Debug extension) Tips: ddev describe (URLs/services), ddev debug (DDEV issues), Twig debug in development.services.yml

Performance

# Cache
ddev drush cr                                    # Rebuild all
ddev drush cache:clear [render|dynamic_page_cache|config]

# Redis (if enabled)
ddev redis-cli INFO stats|memory
ddev redis-cli FLUSHALL                          # Clear Redis

# DB performance
ddev mysql -e "SELECT table_name, round(((data_length+index_length)/1024/1024),2) 'MB' FROM information_schema.TABLES WHERE table_schema=DATABASE() ORDER BY (data_length+index_length) DESC;"
ddev mysql -e "SHOW VARIABLES LIKE 'slow_query%';"
ddev drush sql:query "OPTIMIZE TABLE cache_bootstrap, cache_config, cache_data, cache_default, cache_discovery, cache_dynamic_page_cache, cache_entity, cache_menu, cache_render;"

Optimization: Enable page cache + dynamic page cache, CSS/JS aggregation, Redis/Memcache, CDN for assets, image styles with lazy loading

Code Standards

Core Principles

  • SOLID/DRY: Follow SOLID principles, extract repeated logic
  • PHP 8.1+: Use strict typing: declare(strict_types=1);
  • Drupal Standards: PSR-12 based, English comments only

Module Structure

Location: /web/modules/custom/[prefix]_[module_name]/ Naming: [prefix]_[descriptive_name] (e.g., d_, custom_) - prevents conflicts with contrib

[prefix]_[module_name]/
├── [prefix]_[module_name].{info.yml,module,install,routing.yml,permissions.yml,services.yml,libraries.yml}
├── src/                          # PSR-4: \Drupal\[module_name]\[Subdir]\ClassName
│   ├── Entity/                   # Custom entities
│   ├── Form/                     # Forms (ConfigFormBase, FormBase)
│   ├── Controller/               # Route controllers
│   ├── Plugin/{Block,Field/FieldWidget,Field/FieldFormatter}/
│   ├── Service/                  # Custom services
│   └── EventSubscriber/          # Event subscribers
├── templates/                    # Twig templates
├── css/ & js/                    # Assets

PSR-4: src/Form/MyForm.php\Drupal\my_module\Form\MyForm

Entity Development Patterns

// 1. Enums instead of magic numbers
enum EntityStatus: string {
  case Draft = 0;
  case Published = 1;
}

// 2. Getter methods instead of direct field access
public function getStatus(): int {
  return (int) $this->get('status')->value;
}

// 3. Safe migrations with backward compatibility
function [module]_update_XXXX() {
  $manager = \Drupal::entityDefinitionUpdateManager();
  $field = $manager->getFieldStorageDefinition('field_name', 'entity_type');
  if ($field) {
    $new_def = BaseFieldDefinition::create('field_type')->setSettings([...]);
    $manager->updateFieldStorageDefinition($new_def);
    drupal_flush_all_caches();
    \Drupal::logger('module')->info('Migration completed.');
  }
}

Migration safety: Backup DB, test on staging, ensure backward compatibility, log changes, have rollback plan.

Drupal Best Practices

// Database API - always use placeholders, never raw SQL
$query = \Drupal::database()->select('node_field_data', 'n')
  ->fields('n', ['nid', 'title'])->condition('status', 1)->range(0, 10);
$results = $query->execute()->fetchAll();

// Dependency Injection - avoid \Drupal:: static calls in classes
class MyService {
  public function __construct(
    private readonly EntityTypeManagerInterface $entityTypeManager,
  ) {}
}

// Caching - use tags and contexts
$build = [
  '#markup' => $content,
  '#cache' => ['tags' => ['node:' . $nid], 'contexts' => ['user'], 'max-age' => 3600],
];
\Drupal\Core\Cache\Cache::invalidateTags(['node:' . $nid]);
\Drupal::cache()->set($cid, $data, time() + 3600, ['my_module']);
// Queue API - for heavy operations
$queue = \Drupal::queue('my_module_processor');
$queue->createItem(['data' => $data]);
// QueueWorker plugin: @QueueWorker(id="...", cron={"time"=60})

// Entity System - always use entity type manager
$storage = \Drupal::entityTypeManager()->getStorage('node');
$node = $storage->load($nid);
$query = $storage->getQuery()
  ->condition('type', 'article')->condition('status', 1)
  ->accessCheck(TRUE)->sort('created', 'DESC')->range(0, 10);
$nids = $query->execute();

// Form API - extend FormBase, implement getFormId(), buildForm(), validateForm(), submitForm()
$form['field'] = ['#type' => 'textfield', '#title' => $this->t('Name'), '#required' => TRUE];
$form_state->setErrorByName('field', $this->t('Error message.'));

// Translation - always use t() for user-facing strings
$this->t('Hello @name', ['@name' => $name]);

// Config API
$config = \Drupal::config('my_module.settings')->get('key');
\Drupal::configFactory()->getEditable('my_module.settings')->set('key', $value)->save();

// Permissions
user_role_grant_permissions($role_id, ['permission']);
user_role_revoke_permissions($role_id, ['permission']);

Code Style

  • Type declarations/hints required, PHPDoc for classes/methods
  • Align => in arrays, = in variable definitions
  • Controllers: final classes, DI, keep thin
  • Services: register in services.yml, single responsibility
  • Logging: \Drupal::logger('module')->notice('message')
  • Entity updates: always use update hooks in .install, maintain backward compatibility

Directory Structure

Key paths: /web/ (or docroot/), /web/modules/custom/, /config/sync/, /web/sites/default/settings.php, /patches/, /tests/

Development paths: routes → routing.yml, forms → src/Form/, entities → src/Entity/, permissions → permissions.yml, updates → .install

Multilingual Configuration

# Setup
ddev drush pm:enable language locale content_translation config_translation
ddev drush language:add pl && ddev drush language:add es
ddev drush locale:check && ddev drush locale:update

# Enable content translation
ddev drush config:set language.content_settings.node.article third_party_settings.content_translation.enabled true

Detection: Configure at /admin/config/regional/language/detection - use URL prefix (/en/, /pl/) for SEO

Custom entities: Add translatable = TRUE to @ContentEntityType, use ->setTranslatable(TRUE) on fields

In code: $this->t('Hello @name', ['@name' => $name]) | In Twig: {{ 'Hello'|trans }}

Common issues: Missing translations → locale:update, content not translatable → check Language settings tab

Configuration Management

ddev drush cex                    # Export config
ddev drush cim                    # Import config
ddev drush config:status          # Show differences

Security

Principles: HTTPS required, sanitize input, use DB abstraction (no raw SQL), env vars for secrets, proper access checks

# Security updates
ddev drush pm:security
ddev composer update drupal/core-recommended --with-dependencies
ddev composer update --security-only

# Audit
ddev drush role:perm:list
ddev drush watchdog:show --severity=Error --count=100

Hardening: chmod 444 settings.php, chmod 755 sites/default/files, disable PHP in files dir

Code: Use placeholders in queries, Html::escape() for output, $account->hasPermission() for access, Form API for validation

Headless/API-First Development

JSON:API (Core)

ddev drush pm:enable jsonapi
# Optional: ddev composer require drupal/jsonapi_extras

Endpoints:

GET  /jsonapi/node/article                                    # List all
GET  /jsonapi/node/article/{uuid}?include=field_image,uid    # With relations
GET  /jsonapi/node/article?filter[status]=1&sort=-created&page[limit]=10
POST /jsonapi/node/article  (Content-Type: application/vnd.api+json, Authorization: Bearer {token})

GraphQL

ddev composer require drupal/graphql drupal/graphql_compose
ddev drush pm:enable graphql graphql_compose
# Explorer at /admin/config/graphql

Authentication (Simple OAuth)

ddev composer require drupal/simple_oauth && ddev drush pm:enable simple_oauth
openssl genrsa -out keys/private.key 2048 && openssl rsa -in keys/private.key -pubout -out keys/public.key
# POST /oauth/token with grant_type, client_id, client_secret, username, password
# Use: Authorization: Bearer {access_token}

CORS (in services.yml)

cors.config:
  enabled: true
  allowedOrigins: ['http://localhost:3000']
  allowedMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS']
  allowedHeaders: ['*']
  supportsCredentials: true

Architecture Patterns

  • Fully Decoupled: Drupal API + React/Vue/Next.js frontend
  • Progressively Decoupled: Drupal pages + JS framework for interactive components
  • Hybrid: Mix of Drupal templates and API-driven sections

API Best Practices

OAuth tokens (not basic auth), rate limiting, HTTPS, validate input, API documentation (drupal/openapi)

SEO & Structured Data

Core Modules

ddev composer require drupal/metatag drupal/pathauto drupal/simple_sitemap drupal/redirect drupal/schema_metatag
ddev drush pm:enable metatag metatag_open_graph metatag_twitter_cards pathauto simple_sitemap redirect schema_metatag
ddev drush simple-sitemap:generate    # Generate sitemap at /sitemap.xml
ddev drush pathauto:generate          # Generate URL aliases

Schema.org & Open Graph

Configure at /admin/config/search/metatag/global:

  • Organization: @type: Organization, name, url, logo, sameAs
  • Article: @type: Article, headline [node:title], datePublished, author, image
  • Open Graph: og:title, og:description, og:image, og:url
  • Twitter Cards: twitter:card summary_large_image, twitter:title, twitter:image

Multilingual SEO

ddev composer require drupal/hreflang && ddev drush pm:enable hreflang

Twig: {% for lang in languages %}<link rel="alternate" hreflang="{{ lang.id }}" href="..."/>{% endfor %}

Performance

CSS/JS aggregation, BigPipe, WebP images, lazy loading, responsive image styles

Testing: Google Rich Results Test, Facebook Sharing Debugger, PageSpeed Insights

SEO Checklist

On-page: title tags (50-60 chars), meta descriptions (150-160), H1 unique, clean URLs, alt attributes Technical: sitemap submitted, robots.txt, canonical URLs, Schema.org, HTTPS, Core Web Vitals Multilingual: hreflang tags, language-specific sitemaps, canonical per language

Frontend Development

JS Aggregation Issues: Missing .libraries.yml deps, wrong load order, drupalSettings unavailable → Add deps (core/jquery, core/drupal, core/drupalSettings, core/once), use once() not .once(), test with aggregation enabled

CSS: BEM naming, SCSS/SASS, organize by component, use SDC when applicable

Theme location: /web/themes/custom/[theme_name]/

Build Commands

cd web/themes/custom/[theme_name]
npm install                    # Setup
npm run dev|build|watch        # Dev/prod/watch (or gulp/gulp dist/gulp watch)
gulp sass|js|images|fonts      # Individual tasks
gulp lint:scss|lint:js         # Linting

Libraries ([theme].libraries.yml)

global:
  css: { theme: { css/style.css: {} } }
  js: { js/global.js: {} }
  dependencies: [core/drupal, core/jquery, core/drupalSettings, core/once]

Twig Templates

Enable debugging (development.services.yml): twig.config: { debug: true, auto_reload: true, cache: false }

Naming: node--[type]--[view-mode].html.twig, paragraph--[type].html.twig, block--[type].html.twig, field--[name]--[entity].html.twig

Override: Enable debug → view source for suggestions → copy from core/themes → place in templates/ → ddev drush cr

Template directory structure:

templates/
├── block/           # Block templates
├── content/         # Node templates
├── field/           # Field templates
├── form/            # Form element templates
├── layout/          # Layout templates
├── misc/            # Miscellaneous templates
├── navigation/      # Menu and navigation
├── paragraph/       # Paragraph templates
└── views/           # Views templates

Preprocess Functions ([theme].theme)

function [theme]_preprocess_node(&$variables) {
  $variables['custom_var'] = $variables['node']->bundle();
}
function [theme]_preprocess_paragraph(&$variables) {
  $variables['type'] = $variables['paragraph']->bundle();
}
function [theme]_theme_suggestions_node_alter(array &$suggestions, array $variables) {
  $node = $variables['elements']['#node'];
  if ($node->hasField('field_layout') && !$node->get('field_layout')->isEmpty()) {
    $suggestions[] = 'node__' . $node->bundle() . '__' . $node->get('field_layout')->value;
  }
}

Single Directory Components (SDC)

Drupal 10.1+ core, or ddev composer require drupal/sdc for 10.0

Structure: components/[name]/ with [name].component.yml, [name].twig, optional .css/.js

component.yml:

name: Card
props: { type: object, properties: { title: { type: string }, link: { type: string } } }
slots: { content: { title: Content } }

Usage: {% include '[theme]:card' with { title: node.label, link: url } %} or {% embed %} for slots

Commands: ddev drush sdc:list, ddev drush pm:enable sdc

Troubleshooting

rm -rf node_modules package-lock.json && npm install   # Reset deps
rm -rf dist/ css/ js/compiled/                          # Clear build cache

Performance: Minify for prod, imagemin, critical CSS, font-display:swap, CSS/JS aggregation, AdvAgg module

Environment Indicators

  • Visual verification: Check indicators display correctly on all pages
  • Color scheme: GREEN (Local), BLUE (DEV), ORANGE (STG), RED (PROD)
  • Never commit "LOCAL" as value in environment_indicator.indicator.yml for production! Always use "PROD" and red color.

Documentation

MANDATORY: Document work in "Tasks and Problems" section. Use real date (date command). Document: modules, fixes, config changes, optimizations, problems/solutions.

Common Tasks

New Module

# Create /web/modules/custom/[prefix]_[name]/ with:
# - [prefix]_[name].info.yml (name, type:module, core_version_requirement:^10||^11, package:Custom)
# - [prefix]_[name].module (hooks), .routing.yml, .permissions.yml, .services.yml as needed
ddev drush pm:enable [prefix]_[name] && ddev drush cr

Update Core

ddev export-db --file=backup.sql.gz                                    # Backup
ddev composer update drupal/core-recommended drupal/core-composer-scaffold --with-dependencies
ddev drush updb && ddev drush cr                                       # Updates + cache

Database Migration

// In [module].install
function [module]_update_10001() {
  // Use EntityDefinitionUpdateManager for field changes
  // Check field exists, update displays, log completion
  drupal_flush_all_caches();
}

Tests

ddev exec vendor/bin/phpunit web/modules/custom/[module]/tests   # PHPUnit
ddev exec vendor/bin/codecept run                                 # Codeception
# Dirs: tests/src/Unit/, Kernel/, Functional/; tests/acceptance/

Permissions

ddev drush role:perm:list [role]                                  # List
# PHP: user_role_grant_permissions($role_id, ['perm1']); drupal_flush_all_caches();

Troubleshooting

Quick Fixes

ddev drush cr                                                     # Clear cache
ddev restart                                                      # Restart containers
ddev xdebug on|off                                               # Debug mode
ddev drush watchdog:show --count=50                              # Check logs

Cache Not Clearing

ddev drush cr                                                     # Standard
rm -rf web/sites/default/files/php/twig/* && ddev drush cr       # Twig
ddev drush sql:query "TRUNCATE cache_render;" && ddev drush cr   # Nuclear

Database Issues

ddev drush sql:cli                     # Check connection (SELECT 1;)
ddev drush updb && ddev drush entity:updates   # Pending updates
ddev mysql -e "REPAIR TABLE [name];"   # Repair table

DDEV Issues

ddev restart                           # Soft restart
ddev stop && ddev start                # Full restart
ddev delete -O && ddev start           # Recreate containers
ddev logs                              # View logs

Module Installation

ddev composer why-not drupal/[module]  # Check deps
ddev composer require drupal/[module] && ddev drush pm:enable [module]
ddev drush updb && ddev drush entity:updates   # Schema issues

Permissions

ddev exec chmod -R 775 web/sites/default/files
ddev exec chmod 444 web/sites/default/settings.php

WSOD (White Screen)

ddev drush config:set system.logging error_level verbose
ddev logs && ddev drush watchdog:show --count=50
ddev exec tail -f /var/log/php/php-fpm.log    # Check fatal errors

Config Import Fails

ddev drush config:status              # Check status
ddev drush config:set system.site uuid [correct-uuid]  # UUID mismatch

Memory Issues

echo "memory_limit = 512M" >> .ddev/php/php.ini && ddev restart
# Or: ddev exec php -d memory_limit=1G vendor/bin/drush [cmd]

Additional Resources


Drupal Entities Structure

Complete reference of content types, media types, taxonomies, and custom entities. See "HOW TO DISCOVER FULL ENTITY STRUCTURE" guide above for discovery commands.

Content Types (Node Bundles)

content_types[2]{machine_name,label,description,features,key_fields}:
  article,Article,News and blog posts,"revisions,menu_ui,content_translation","body,field_image,field_tags,field_category"
  page,Basic Page,Static pages,"revisions,menu_ui","body,field_sections"

Paragraph Types

paragraph_types:
  layout_paragraphs[3]: banner,two_column_layout,accordion
  content_paragraphs[3]: text_block,quote,call_to_action
  media_paragraphs[3]: image_gallery,video_embed,carousel

Media Types

media_types[3]{machine_name,label,source,source_field}:
  image,Image,image,field_media_image
  document,Document,file,field_media_document
  remote_video,Remote Video,oembed:video,field_media_oembed_video

Taxonomy Vocabularies

taxonomies[2]{machine_name,label,description,hierarchy}:
  tags,Tags,Content tagging,false
  categories,Categories,Content categorization,true

Custom Entities

custom_entities:
  custom_entity_name:
    type: content_entity
    base_table: custom_entity
    entity_keys: id:id,uuid:uuid,label:name
    fields[6]{name,type}:
      id,integer
      uuid,uuid
      name,string
      status,boolean
      created,timestamp
      changed,timestamp

Entity Relationships

Examples:

  • ArticleTags (many-to-many via field_tags)
  • ArticleAuthor (many-to-one via uid)
  • PageParagraphs (one-to-many via field_sections)
  • Custom EntityNode (reference via field_node_ref)

Field Patterns

Common field naming patterns in this project:

  • field_[name] - Standard field prefix
  • field_[prefix]_[name] - Module-specific fields (e.g., field_meta_tags)
  • Base fields: title, body, created, changed, uid, status

Key Field Types:

  • Reference fields: entity_reference, entity_reference_revisions
  • Text fields: string, text_long, text_with_summary
  • Date fields: datetime, daterange, timestamp
  • Media: image, file
  • Structured: link, address, telephone

View Modes

Node View Modes:

  • full - Full content display
  • teaser - Summary/card display
  • search_result - Search results display
  • Custom: [document_custom_view_modes]

Media View Modes:

  • full - Full media display
  • media_library - Media library thumbnail
  • Custom: [document_custom_view_modes]

Entity Constants

// Example: Content workflow states
define('ENTITY_STATUS_DRAFT', 0);
define('ENTITY_STATUS_PUBLISHED', 1);
define('ENTITY_STATUS_ARCHIVED', 2);

// Custom entity states
define('CUSTOM_ENTITY_PENDING', 0);
define('CUSTOM_ENTITY_APPROVED', 1);
define('CUSTOM_ENTITY_REJECTED', 2);

Entity Access Patterns

  • View: access content | Edit own: edit own [type] content | Delete own: delete own [type] content | Admin: administer [type] content

Migration Patterns

If project uses migrations, document source to destination mappings:

# Example migration mapping
source:
  entity_type: legacy_node
  bundle: legacy_article

destination:
  entity_type: node
  bundle: article

field_mapping:
  legacy_title → title
  legacy_body → body
  legacy_image → field_image
  legacy_category → field_category

Project-Specific Features

Development Workflow

  • Document all significant changes in "Tasks and Problems" section below
  • Follow the format and examples provided
  • Review existing entries before making architectural changes
  • Always run date command to get current date before adding entries

Tasks and Problems Log

Format: YYYY-MM-DD | [TYPE] Description — Types: TASK, PROBLEM/SOLUTION, CONFIG, PERF, SECURITY, NOTE

Run date first. Add new entries at top. Include file paths, module names, config keys.

[Add entries here - newest first]

Examples:
2024-01-15 | TASK: Created custom module d_custom_feature for special workflow
2024-01-15 | PROBLEM: Config import failing with UUID mismatch
          | SOLUTION: drush config:set system.site uuid [correct-uuid]
2024-01-14 | CONFIG: Enabled Redis cache backend in settings.php
2024-01-14 | PERF: Enabled CSS/JS aggregation and AdvAgg module
2024-01-13 | SECURITY: Applied security update for Drupal core 10.1.8
2024-01-13 | NOTE: Custom entity queries must include ->accessCheck(TRUE/FALSE)