Work with Drupal's database abstraction layer for custom tables and direct SQL queries. Use when asked to create custom database tables via hook_schema(), write dynamic or static select/insert/update/delete queries, define database schema, or build hook_update_N() for schema changes. Covers tagged queries and query alter hooks. Do NOT use for entity data -- use drupal-entities-fields and Entity Query instead. Do NOT use for Views query handlers (use drupal-views-dev instead).
The Database API is stable across Drupal 10 and 11. No syntax differences between versions.
Before writing any database query, determine the correct approach:
Working with entities (nodes, users, taxonomy terms, custom entities)?
-> Use Entity Query or entity storage loadMultiple(). NOT direct database queries.
Need complex SQL that Entity Query cannot handle? -> Use Database API to find entity IDs, then load entities via storage handler.
Custom tables (non-entity data like logs, statistics, integration data)?
-> Define with hook_schema(), query with Database API.
Simple one-off query against a custom table? -> Static query with placeholders.
Complex, alterable query against custom tables? -> Dynamic query builder.
WRONG: Using direct SQL for entity data.
$database->query("SELECT * FROM {node_field_data} WHERE title = :t", [':t' => $title])bypasses access control, field storage abstraction, and cache invalidation. Use or instead.
\Drupal::entityQuery('node')\Drupal::entityTypeManager()->getStorage('node')->loadByProperties()Preferred -- inject via dependency injection:
// In a service class or controller:
use Drupal\Core\Database\Connection;
class MyService {
public function __construct(
protected readonly Connection $database,
) {}
public function getRecords(): array {
return $this->database->select('my_table', 'm')
->fields('m')
->execute()
->fetchAll();
}
}
# my_module.services.yml