Drupal is built around entities. Content, users, taxonomy terms, files, and custom data are all stored and managed as entities.
In the last few topics, we built a custom field type, widget, and formatter. Now we move to the next essential backend skill: creating, reading, updating, and deleting entities programmatically.
This is commonly called CRUD:
- Create
- Read
- Update
- Delete
Entity API CRUD is used everywhere in real Drupal projects:
- Importing data from external systems (Salesforce, Elasticsearch, Oracle)
- Syncing records in cron or queues
- Building custom workflows and automation
- Fixing data or backfilling fields
This article continues the learning-by-building approach using the same weeklydrupal_demo module.
By the end of this article, you will understand:
- What an entity is and why it matters
- How to load entities safely
- How to create and save entities
- How to update fields programmatically
- How to delete entities correctly
- Best practices for production-safe CRUD
1. What Is an Entity
An entity is a structured object that represents stored data in Drupal.
Examples of core entities:
- Node (content)
- User
- Taxonomy term
- File
Entities provide:
- Storage
- Field system integration
- Access control
- Validation
- Revisions (for some entity types)
Entity API is the recommended way to work with stored data.
2. CRUD Overview in Drupal Terms
Drupal CRUD usually looks like:
- Load entity using storage
- Modify fields
- Save entity
- Delete entity (if needed)
Most operations go through the entity type manager.
3. Loading Entities (Read)
Load a single entity by ID
$node = \Drupal::entityTypeManager()
->getStorage('node')
->load($nid);
If the entity does not exist, $node will be NULL.
Load multiple entities
$nodes = \Drupal::entityTypeManager()
->getStorage('node')
->loadMultiple([$nid1, $nid2]);
Load by properties
$results = \Drupal::entityTypeManager()
->getStorage('node')
->loadByProperties([
'type' => 'article',
'status' => 1,
]);
loadByProperties() returns an array of entities.
4. Creating Entities (Create)
To create a node:
$node = \Drupal::entityTypeManager()
->getStorage('node')
->create([
'type' => 'article',
'title' => 'WeeklyDrupal Entity API Demo',
'status' => 1,
]);
$node->save();
What is happening
create()builds an entity object in memorysave()persists it to the database- Field values are stored using the field storage tables
5. Setting Field Values
Fields are set using either set() or direct property assignment.
Using set()
$node->set('title', 'Updated title');
$node->set('status', 1);
Setting a custom field
Assume a node has a field named field_rating (using our custom rating field type).
$node->set('field_rating', 5);
For multi-value fields:
$node->set('field_tags', [1, 2, 3]);
6. Updating Entities (Update)
Updating is the same flow:
- Load entity
- Change values
- Save
Example:
$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
if ($node) {
$node->set('title', 'New title from backend');
$node->save();
}
7. Deleting Entities (Delete)
Delete a single entity
$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
if ($node) {
$node->delete();
}
Delete multiple entities
$storage = \Drupal::entityTypeManager()->getStorage('node');
$entities = $storage->loadMultiple($nids);
$storage->delete($entities);
8. Production-Safe CRUD (Important Practices)
Always check access when required
Entity API does not automatically enforce view/update access in backend scripts.
if ($node && $node->access('update')) {
$node->set('title', 'Safe update');
$node->save();
}
Avoid \Drupal:: in reusable code
In real modules, prefer dependency injection.
Example concept:
- Inject
entity_type.manager - Use
$this->entityTypeManager->getStorage('node')
This makes code testable and maintainable.
9. CRUD Inside a Service (Recommended Structure)
Example service pattern:
namespace Drupal\weeklydrupal_demo\Service;
use Drupal\Core\Entity\EntityTypeManagerInterface;
class NodeWriter {
public function __construct(protected EntityTypeManagerInterface $entityTypeManager) {}
public function createArticle(string $title): int {
$node = $this->entityTypeManager->getStorage('node')->create([
'type' => 'article',
'title' => $title,
'status' => 1,
]);
$node->save();
return (int) $node->id();
}
}
What is happening
- The service receives
EntityTypeManagerInterface - CRUD logic is centralized
- Controllers and forms stay thin
10. Common Mistakes to Avoid
- Writing direct SQL instead of using Entity API
- Updating entities without checking access where needed
- Loading large sets without batching
- Doing heavy CRUD in a page request instead of queue/batch
11. Learning by Building: weeklydrupal_demo
To apply this topic in the demo module, a good next feature is:
- Add a route
/weeklydrupal/demo/create-article - Controller calls a service
- Service creates an Article node and sets
field_rating - Controller redirects to the node
This connects:
- Routes
- Controllers
- Services
- Field system
- Entity CRUD
12. Why Entity API CRUD Matters
Entity API CRUD is foundational because it is how Drupal expects backend code to interact with data.
If you understand this topic, you can:
- Build importers
- Build integrations
- Build automation
- Debug content issues
- Write production-safe backend logic