Drupal Custom Entities with Bundle Classes

One thing that always felt missing from Drupal 8 was the ability to define bundles in custom entities via classes. It seemed like a natural extension of custom entities and would help with better code organization, especially when specific operations needed to be performed when saving a custom entity of certain bundles. Instead, we were left with configuration of bundles, which was fine but always felt hacky to me. This changed with very little fanfare with the release of Drupal 9.3.

While researching this, I found that there was very little documentation helping developers make use of this new functionality, apart from a brief page with broken or incomplete code examples and the release notes announcing the feature. There were a few blogs on the subject too, but all of them just seemed to provide a tour of the feature, and none of them provided a solid functional example. The feature seems to be so obscure that even ChatGPT could not create nor be significantly helpful in collaborating towards a functional example, even when given the same source material I had. I struggled for several days just trying to get a functional example working. Now successful, I am posting it here for the benefit of my future self and anyone else.

I think part of the problem with this feature is that it seems to be a bit underdone in terms of implementation. Though amazing, it seems to be missing key development patterns that align with how most things are done in newer versions of Drupal, or at least the current direction Drupal development patterns are heading.

Adding a .install File

On its own, Drupal is capable of automatically creating all fields in a custom entity type just from BaseFieldDefinitions. Built-in methods examine custom entity classes and do all of the work for setting up the database schema and getting it ready. With the current implementation of bundle classes, developers need to use HOOK_install() to loop over the bundles and install the field definitions. For custom entities generated via Drush 11, .install files are not needed because Drupal core will do all of the work to install the module and the custom entity type when the module is enabled.

Overreliance on Hooks in the .module File

In order for Drupal to even recognize the bundle classes, developers must make use of the HOOK_entity_bundle_info(), HOOK_entity_field_storage_info(), just to register the entity bundles and provide field storage definitions. While custom entities need a couple of hooks to wire up the theme, the bundles themselves should make use of a similar mechanism to that of the custom entities themselves, class decorators to establish parent custom entity type, bundle type ID, and bundle label. There is a contrib module that accomplishes this, though this functionality should be part of core as part of the change that introduced this feature from the beginning.

Modifying Annotations of the Custom Entity Type

Out of the box, without further modification, users will experience a 404 error when trying to use the default admin user interface to add a new entity for the bundle. To fix this, developers have to adjust the annotations of the custom entity type for the add-form path. This is done by changing the placeholder from the configuration entity type name to just bundle.

Incomplete Drush Generate Implementation

The ability to implement custom entity bundle classes via drush gen is incomplete. Unlike drush gen for custom content entity types, developers are required to add a lot more code which could be either considered boilerplate and be added by drush, or automatically handled by Drupal with fewer code adjustments required. None of the code changes mentioned above are handled by Drush.

Hopefully, this fully functional example will help someone someday. That said, it is possible that as these wrinkles in Drupal are ironed out over time this example will probably break in the future.