Problem/Motivation
In TrashStorageTrait::delete() and TrashStorageTrait::restoreFromTrash(), the $revisionable flag is initialised once, before the entity loop, using $this->getEntityType()->isRevisionable():
$field_name = 'deleted';
$revisionable = $this->getEntityType()->isRevisionable();
foreach ($to_trash as $entity) {
// Always create a new revision if the entity type is revisionable.
if ($revisionable) {
$entity->setNewRevision(TRUE);
...
}
}isRevisionable() is a type-level capability flag — it answers "does this entity type support revisions?" not "should this specific entity create a new revision right now?" As a result, the trait unconditionally calls $entity->setNewRevision(TRUE) for every entity of a revisionable type, ignoring the per-entity revision state entirely.
This is particularly disruptive for entity types such as Paragraph, where revisions are created and owned by the host entity (e.g. a Node). When a paragraph is soft-deleted or restored through Trash, the module creates an independent paragraph revision that is not anchored to any node revision. This produces orphaned revision rows in the database and can break the integrity of the host entity's revision history.
The correct check is $entity->isNewRevision(), which reflects the actual per-entity revision intent at the time of the operation — whether that intent was set explicitly by the caller or by a bundle-level default.
An example: The media entity type is revisionable (it has revision_table: media_revision and revision: vid in its entity keys). Individual media types, however, can set new_revision: false in their bundle config — the core Image media type ships with this as its default. Trashing or restoring an Image media entity triggers the bug: Trash forces a new revision entry into media_revision for a bundle that was never configured to use them.
The correct check is $entity->isNewRevision(), which reflects the actual per-entity revision intent at the time of the operation. For entities loaded from storage whose bundle has new_revision: false, isNewRevision() returns FALSE and setNewRevision(TRUE) is never called.
Steps to reproduce
- Enable the Trash module on a Drupal 11 site with Paragraphs (or any other revisionable entity type whose revisions are managed externally, e.g. by a host entity).
- Enable Trash for that entity type (e.g. paragraph).
- Soft-delete a Paragraph entity — either programmatically via $entity->delete() or through the UI.
- Inspect the paragraph_revision table (or equivalent revision table). An extra revision row has been created by Trash independently of any host Node revision.
- Restore the entity from Trash ($storage->restoreFromTrash([$entity])).
- Observe another orphaned revision row created on restore, again unrelated to any Node operation.
Proposed resolution
Override $revisionable per entity inside the foreach loop using $entity->isNewRevision(). This delegates the revision decision to the entity itself — consistent with how Drupal core handles revision creation throughout the entity save pipeline.
Issue fork trash-3606545
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3606545-trashstoragetrait-uses-entity-type
changes, plain diff MR !134
Comments
Comment #3
majid.ali commentedComment #4
amateescu commentedThe current behavior of always creating a new revision when trashing an entity is by design. Paragraphs were recently flagged as problematic and fixed in #3592729: Mark paragraphs entities as unsupported. If there are any other entity types with a similar behavior, a separate issue should be opened for them individually.
Note that I don't consider Media entities in that bucket.