# 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**: `staging` → `feature/name` → PR → merge to `staging` → eventually `main` **Hotfix**: `main` → `hotfix/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**: ```gitignore 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 ```bash 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 ```bash # 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 ```bash 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.json` → `extra.patches`: ```json "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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```php // 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 ```php // 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']); ``` ```php // 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 ```bash # 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 ```bash 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 ```bash # 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) ```bash 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 ```bash ddev composer require drupal/graphql drupal/graphql_compose ddev drush pm:enable graphql graphql_compose # Explorer at /admin/config/graphql ``` ### Authentication (Simple OAuth) ```bash 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) ```yaml 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 ```bash 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 ```bash ddev composer require drupal/hreflang && ddev drush pm:enable hreflang ``` Twig: `{% for lang in languages %}{% 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 ```bash 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`) ```yaml 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`) ```php 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**: ```yaml 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 ```bash 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 ```bash # 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 ```bash 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 ```php // In [module].install function [module]_update_10001() { // Use EntityDefinitionUpdateManager for field changes // Check field exists, update displays, log completion drupal_flush_all_caches(); } ``` ### Tests ```bash 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 ```bash ddev drush role:perm:list [role] # List # PHP: user_role_grant_permissions($role_id, ['perm1']); drupal_flush_all_caches(); ``` ## Troubleshooting ### Quick Fixes ```bash 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 ```bash 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 ```bash 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 ```bash ddev restart # Soft restart ddev stop && ddev start # Full restart ddev delete -O && ddev start # Recreate containers ddev logs # View logs ``` ### Module Installation ```bash 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 ```bash ddev exec chmod -R 775 web/sites/default/files ddev exec chmod 444 web/sites/default/settings.php ``` ### WSOD (White Screen) ```bash 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 ```bash ddev drush config:status # Check status ddev drush config:set system.site uuid [correct-uuid] # UUID mismatch ``` ### Memory Issues ```bash echo "memory_limit = 512M" >> .ddev/php/php.ini && ddev restart # Or: ddev exec php -d memory_limit=1G vendor/bin/drush [cmd] ``` ## Additional Resources - **Project Documentation**: `.cursor/TASKS_AND_PROBLEMS.md` - **Drupal Documentation**: https://www.drupal.org/docs - **DDEV Documentation**: https://ddev.readthedocs.io/ --- ## 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) ```toon 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 ```toon 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 ```toon 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 ```toon taxonomies[2]{machine_name,label,description,hierarchy}: tags,Tags,Content tagging,false categories,Categories,Content categorization,true ``` ### Custom Entities ```toon 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**: - **Article** → **Tags** (many-to-many via `field_tags`) - **Article** → **Author** (many-to-one via `uid`) - **Page** → **Paragraphs** (one-to-many via `field_sections`) - **Custom Entity** → **Node** (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 ```php // 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: ```yaml # 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) ```