Drupal Entity Api

Published by gkastanis in drupal-workflow

No known issues34 installs

What this skill does

Field type selection, entity CRUD operations, view modes, and content modeling patterns for Drupal 10/11. Use when designing content types, selecting field types, or working with the Entity API.

Add Drupal Entity Api to your agent

Review the source and files first. When you are ready, copy the prompt instruction or use the CLI command supported by your environment.

Install with a prompt

Paste this into a compatible coding agent:

add this skill "drupal-entity-api" from https://github.com/gkastanis/drupal-workflow

Install with the CLI

Run this command in a controlled environment after reviewing the repository:

npx skills add https://github.com/gkastanis/drupal-workflow --skill drupal-entity-api

Skill instructions

Drupal Entity API

Field Type Selection Matrix

Content NeedField TypeWidgetNotes
Event date/timedatetimedatetime_defaultSingle datetime
Date rangedaterangedaterange_defaultStart/end dates
Location (simple)stringstring_textfieldText-based
Location (structured)address (contrib)address_defaultFull address
Short textstringstring_textfieldMax 255 chars
Long texttext_longtext_textareaUnlimited plain text
Rich contenttext_with_summarytext_textarea_with_summaryFormatted with summary
Emailemailemail_defaultValidated email
Phonetelephonetelephone_defaultPhone number
Websitelinklink_defaultURL with title
Documentfilefile_genericAny file type
Imageimageimage_imageWith alt text
Content referenceentity_reference (node)entity_reference_autocompleteLinks to nodes
Term referenceentity_reference (taxonomy_term)entity_reference_autocompleteCategories
Yes/No flagbooleanboolean_checkboxTrue/false
Whole numberintegernumberInteger values
Decimal numberdecimalnumberPrecise decimals (financial)
Float numberfloatnumberApproximate decimals
Flexible componentsentity_reference_revisions (paragraphs)paragraphsContrib: requires entity_reference_revisions storage

Entity Type Selection

Always choose the right entity type for your data. Use ContentEntityBase for fieldable content, ConfigEntityBase for exportable config. Never create a custom entity when a node type would suffice.

  • Nodes (ContentEntityType): Published content (articles, pages, events).
  • Custom entities (ContentEntityBase): Non-published data (rarely needed — prefer nodes).
  • Taxonomy terms: Categorization and metadata.
  • Paragraphs: Flexible content components.
  • Media: Files, images, videos.
  • Users: User profiles (extend, don't replace).

Custom Entity Definition

Always implement baseFieldDefinitions() to declare your entity's base fields. Always add an AccessControlHandler class for custom entities.

// In your entity class:
public static function baseFieldDefinitions(EntityTypeInterface $entity_type): array {
  $fields = parent::baseFieldDefinitions($entity_type);
  // Add custom base fields here.
  return $fields;
}

Entity CRUD Operations

Use dependency injection — inject EntityTypeManagerInterface via constructor, never use \Drupal:: static calls in classes.

// In a service or controller with injected $entityTypeManager:

// Load single entity.
$node = $this->entityTypeManager->getStorage('node')->load($nid);

// Load multiple entities (avoids N+1).
$nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($nids);

// Create entity.
$node = $this->entityTypeManager->getStorage('node')->create([
  'type' => 'article',
  'title' => 'My Article',
  'field_tags' => [['target_id' => $tid]],
]);
$node->save();

// Query entities (inject EntityTypeManagerInterface, not entityQuery directly).
$nids = $this->entityTypeManager->getStorage('node')->getQuery()
  ->condition('type', 'article')
  ->condition('status', 1)
  ->accessCheck(TRUE)
  ->range(0, 10)
  ->execute();

Entity Access Control Handler

Custom entities need an AccessControlHandler. Without one, all access defaults to denied.

declare(strict_types=1);

namespace Drupal\my_module\Access;

use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;

final class MyEntityAccessControlHandler extends EntityAccessControlHandler {

  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResult {
    return match ($operation) {
      'view' => AccessResult::allowedIfHasPermission($account, 'view my_entity'),
      'update' => AccessResult::allowedIfHasPermission($account, 'edit my_entity'),
      'delete' => AccessResult::allowedIfHasPermission($account, 'delete my_entity'),
      default => AccessResult::neutral(),
    };
  }

  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL): AccessResult {
    return AccessResult::allowedIfHasPermission($account, 'create my_entity');
  }

}

Register in the entity annotation: handlers = {"access" = "Drupal\my_module\Access\MyEntityAccessControlHandler"}

Field Storage Reusability

Share field storage across content types when the same concept applies:

# Good: shared storage
field_location (on event, venue, office)
field_contact_email (on person, organization, department)

# Bad: duplicated storage
field_event_location, field_venue_location, field_office_location

View Modes

  • full: Full content display.
  • teaser: Summary/listing display.
  • search_result: Search listing.
  • Custom view modes for specific contexts (e.g., card, sidebar).

Content Modeling Checklist

  1. Identify fields that can share storage across bundles.
  2. Select appropriate field types (not just "string" for everything).
  3. Plan widget types for optimal editor experience.
  4. Consider cardinality (single vs multi-value).
  5. Plan required fields vs optional.
  6. Document field purposes and usage.
  7. Consider field display in different view modes.

Files included

  • SKILL.md

More skills

Drupal Entity Api: Install, Source and Security | FunnelSlayer