1. Frontend path handling — replace dynamic routes with a path processor
The original ContentSingletonRouteSubscriber generated a separate per-bundle route (e.g. content_singleton.news_stories.view) for each bundle's clean URL. The canonical /content-singleton/{id} route then 302-redirected to it.
This approach has two problems:
- Routes are generated at compile time from config, so adding a bundle type requires a router rebuild before the path works.
- The redirect means local task tabs (View/Edit/Revisions) defined against the canonical route never appear on the clean frontend URL. The tabs render on
/content-singleton/1, which immediately 302s away — the user never sees them.
Fix: Replace the dynamic route generation and redirect with a ContentSingletonPathProcessor (InboundPathProcessorInterface + OutboundPathProcessorInterface). On inbound requests the processor rewrites /news-stories → /content-singleton/1 before routing occurs, so the standard canonical route renders directly. Outbound links from toUrl('canonical') are rewritten back to the clean path. This is the same pattern Drupal's path alias system uses. As a result:
- The canonical route renders the entity directly (no redirect).
- Standard entity route infrastructure — local task tabs,
$route_match->getParameter('content_singleton'), and_entity_accessrequirements — all work on the clean URL. - No router rebuild is needed when a singleton entity is first created.
The redirectToFrontend() controller method and its canonical route override in ContentSingletonRouteProvider are removed.
2. ERR field crash on draft save — "existing default revision cannot be changed to non-default"
When a content_singleton entity has entity_reference_revisions fields (paragraphs) and content moderation is enabled, saving a draft (non-default revision) throws:
An existing default revision can not be changed to a non-default revision.
Root cause: content_moderation's entityTypeAlter() registers the base ModerationHandler for content_singleton. That handler's enforceRevisionsEntityFormAlter() is a no-op, so ContentEntityForm::buildEntity() never calls setNewRevision(TRUE) on the host entity before save(). ERR field preSave() then sees isNewRevision() === FALSE on the host and throws.
Fix: ContentSingletonHooks::entityTypeAlter() overrides the moderation handler to NodeModerationHandler, which forces setNewRevision(TRUE) via enforceRevisionsEntityFormAlter(). Guarded by class_exists() so it only applies when content_moderation is installed.
3. ContentSingletonForm::save() TypeError on non-default revisions
ContentEntityStorageBase::doSave() returns FALSE (not an int) when saving a non-default/pending revision (content moderation draft). The declared int return type of save() causes a TypeError.
Fix: Coerce the return value: parent::save($form, $form_state) ?: SAVED_UPDATED.
4. revision_log textarea causes crash via ERR WidgetBase
The manually added revision_log textarea in ContentSingletonForm::form() collides with ERR WidgetBase::extractFormValues(), causing a "Cannot unset string offsets" fatal error when a paragraph field is present.
Fix: Remove the manually rendered revision checkbox and revision_log textarea from form(). Revision logging is handled by the entity system and content moderation's own UI; the custom fields are not needed and actively harmful.
5. Mercury Editor support
content_singleton bundles do not appear in Mercury Editor's settings form because ME checks for a mercury_editor form class on the entity type.
Fix: ContentSingletonHooks::entityTypeBuild() registers MercuryEditorContentSingletonForm (a thin subclass of ContentSingletonForm) as the mercury_editor form operation. Guarded by class_exists() on MercuryEditorEntityFormAlterer so the hook is a no-op on sites without ME.
6. Theme system support
The module had no hook_theme() implementation, no template suggestions, and no base template, making it impossible to theme singleton entities via standard Twig template overrides.
Fix: ContentSingletonThemeHooks adds:
hook_theme()registering thecontent_singletonrender elementhook_theme_suggestions_content_singleton()producingcontent_singleton__[view_mode],content_singleton__[bundle],content_singleton__[bundle]__[view_mode]suggestionspreprocessContentSingleton()populating$variables['content']and$variables['view_mode']- A base
content-singleton.html.twigtemplate
7. "Add singleton" action button shown when all bundles exist
The action button to add a new singleton was visible even when every defined bundle type already had an entity, leading to a confusing empty or non-functional add page.
Fix: ContentSingletonAddAccessCheck (registered as _content_singleton_add_access) denies access — and therefore hides the action button — when ContentSingletonHelper::hasAvailableBundles() returns false. Cache tags content_singleton_list and content_singleton_type_list ensure the result is invalidated when entities or bundle types change.
The getExistingBundles() logic previously inlined in ContentSingletonAddController is extracted into a new ContentSingletonHelper service shared by both the controller and the access check.
8. Admin path alignment
The admin listing path was /admin/content-singleton. Changed to /admin/content/singletons to follow Drupal's standard admin content path conventions and appear under the Content admin menu.
Issue fork content_singleton-3593282
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:
Comments
Comment #3
jnettikMerged MR fixing outlined issues.