Custom table orphans

Last updated on
1 October 2026

Many modules keep their own tables with entity IDs in them: logs, metadata, hierarchy links, statistics. When the entity is deleted, core has no idea those rows exist, so they stay forever. Drupal Cleanup lets any module declare such tables in a small YAML file, and then removes rows whose entities are gone, both in the cron sweep and right after content is deleted.

No PHP is needed, and your module needs no dependency on Drupal Cleanup: without it, the file is ignored.

Declaring a table

Add MODULE.drupal_cleanup.yml to your module's root folder. Each top-level entry becomes its own task, with the ID custom_table_orphans:MODULE__KEY:

mymodule_log:
  label: 'My module log rows for deleted content'
  table: mymodule_log
  key: id
  references:
    - { column: nid, entity_type: node }
    - { column: uid, entity_type: user }
  match: any
Key Meaning
label Optional. Shown in the admin UI and Drush.
table The table name, without the database prefix.
key The table's single-column primary key, used to back up and delete in chunks.
references One or more columns holding entity IDs, and the entity type each one points at.
match any (default): a row is an orphan when any reference is dead. all: only when every reference is dead.

A reference is dead when its column is not NULL and no entity with that ID exists. NULL never counts as dead, so with match: all, a row with any NULL reference is kept.

queues and files are reserved top-level keys (see the queue and working-files pages). Rebuild caches after adding or editing the file.

Choosing references carefully

Only declare a column if the row is worthless once that entity is gone. For example, a hierarchy table might hold both the item's own node and its parent document's node. If a live item should keep its row even after its parent document is deleted, declare only the item's own column.

Reference columns should have the same type as the entity ID (usually an integer), so the check can join them directly.

When a declaration is wrong

If the table, the key column, a reference column or the entity type doesn't exist, the task reports why and does nothing, for example table mymodule_log does not exist or misconfigured: column mymodule_log.nid does not exist. Check with:

drush cleanup:list

A missing table is common on sites where your module creates it lazily; the task simply starts working once the table exists.

Cleanup right after content is deleted

When content is deleted through the entity API, rows in declared tables that pointed at it are removed at the end of the same request: after the response is sent, or when the Drush command finishes. All deletes in one request become one backed-up run with batched queries, so a bulk delete of thousands of nodes makes a single backup, not thousands.

A problem at that point is logged and never undoes or blocks the delete; the cron sweep picks up whatever is left. Turn this off with Clean up immediately when content is deleted at Configuration » System » Cleanup if you prefer to leave everything to cron.

Running it

drush cleanup:run --task=custom_table_orphans:mymodule__mymodule_log --dry-run
drush cleanup:run --task=custom_table_orphans:mymodule__mymodule_log

Each run backs up the exact rows it removes first; drush cleanup:restore {backup-id} puts them back.

Help improve this page

Page status: No known problems

You can: