Skip to content

Enabling Moderation on Existing Content

When to Use

When you are switching on content moderation for a content type that already has published or draft content. This is the step most moderation rollouts miss, and the failure is silent: existing content behaves as though it is outside the workflow until you explicitly backfill it.

Foundation

moderation_state is not a stored column on the moderated entity. It is a computed field (ModerationStateFieldItemList) backed by a separate content_moderation_state content entity, one per moderated revision. When you enable moderation on a bundle, content_moderation wires handler classes and route templates onto the entity type — but it does not walk existing rows and create content_moderation_state entities for them.

There is no automatic backfill: content_moderation.install ships no hook_update_N that seeds state for pre-existing content. So every revision created before moderation was enabled has no corresponding state entity, and reads as un-moderated / default until the entity is next saved. Only on save does Drupal create or update the content_moderation_state entity for that revision.

Decision

Situation Action
Enabling moderation on a new/empty bundle Nothing extra — all future saves get state automatically
Enabling moderation on a bundle with existing content Backfill required: resave (or explicitly seed state for) every existing entity before declaring the rollout done
Large content set where a synchronous resave would time out Backfill in a batch (Batch API / a queue), not a single request
You cannot backfill immediately Surface an operator-visible flag; do not report the workflow as fully live

Pattern

Backfill by loading each entity of the bundle and resaving it, which triggers creation of its content_moderation_state entity. For anything beyond a handful of nodes, do it in a batch so it does not time out:

// Backfill moderation state for existing nodes of a moderated bundle.
$storage = \Drupal::entityTypeManager()->getStorage('node');
$ids = $storage->getQuery()
  ->accessCheck(FALSE)
  ->condition('type', 'article')
  ->execute();

foreach (array_chunk($ids, 50) as $chunk) {
  foreach ($storage->loadMultiple($chunk) as $node) {
    // Set a sensible state for pre-existing content, then save to
    // materialise the content_moderation_state entity.
    $node->set('moderation_state', $node->isPublished() ? 'published' : 'draft');
    $node->save();
  }
}

Map the pre-existing publish status to a moderation state deliberately: already-published content should land in a published state, unpublished content in draft (or your workflow's equivalent) — otherwise a resave can flip visibility.

Common Mistakes

  • Wrong: Assuming enabling moderation retroactively moderates existing content → Right: Existing revisions have no state entity until resaved; there is no automatic backfill
  • Wrong: Resaving thousands of nodes in one request → Right: Use Batch API / a queue so the backfill does not time out or exhaust memory
  • Wrong: Blindly saving with the default state → Right: Map existing publish status to the correct moderation state, or you may unpublish live content
  • Wrong: Treating the workflow as live the moment the config imports → Right: The bundle is only fully moderated once existing content is backfilled; flag it until then
  • Wrong: Looking for a hook_update_N to do this → Right: None ships; the backfill is the site builder's explicit responsibility

See Also