Custom entity types versus content types in Drupal

Node bundles, custom content entities and configuration entities store different kinds of data and behave differently for revisions, translation, access and Views. This page explains how to choose and what the choice costs you later.

On this page

When custom entity types beat content types in Drupal

A custom entity type beats a content type when the business data is not really a page or article, has its own lifecycle, and must be queried, permissioned and revised independently of site content. For orders, applications, locations or certificates, the usual question is whether a node bundle is enough or whether the team needs a custom content entity with its own storage, fields and access logic. Node bundles are the default Drupal content model: each bundle is a set of fields on the node entity, which gives editors the familiar content editing, revisions, translation and Views support out of the box. A custom content entity is a separate entity type defined in code, with its own table structure, handlers and routes, so it fits data that should not appear in the node listing, the menu or the search index unless someone deliberately makes it appear there. A configuration entity is different again: it stores site settings such as a workflow or a list of options, and it is exported with the site configuration rather than edited as content.

The licence does not drive this decision. Drupal and the contributed files that are derivative works of Drupal are GPL-2.0-or-later, and there is no paid edition of Drupal itself, so the cost of choosing a custom entity over a node bundle is engineering time. That cost recurs. Custom entities need code for their storage, access control and forms, and that code must be maintained across Drupal major versions. Confirm with your team which parts of the requirement really need a separate entity, such as independent revisions, a separate access model or high write volume, and which can be met with fields on a bundle. Before the build starts, check whether the features you need for translation, revisions and Views are supplied by core or by a contributed module on Drupal.org, and confirm the behavior against the Drupal version you plan to run, using the Drupal core release schedule to plan the upgrade window.

Node bundles, custom content entities and configuration entities compared

This table compares node bundles, custom content entities and configuration entities across the factors that shape a business data model. Read each row as a question to answer for your data before you pick a model, since the choice is hard to reverse once content exists. The main takeaway is that node bundles suit editorial pages, while custom content entities suit structured business records that need their own access and lifecycle rules.

FactorNode bundlesCustom content entitiesConfiguration entities
Data shapeFits pages and articles with a title, body and a few fields. Fields are added per bundle through the UI or config.Fits structured records such as orders, applications or assets with their own fields and relations.Fits site settings and lists such as taxonomies of options, not editorial or transactional records.
RevisionsBuilt in with content moderation and revision history.Revisions must be enabled in the entity annotation and handler design. Confirm the effort with your team.Not applicable. Changes are tracked through configuration export and version control.
TranslationSupported through the content translation module with field-level control.Supported only if the entity type declares translatability. Plan it at design time.Limited. Translatable config uses the configuration translation layer, not content translation.
Access controlUses node grants and standard node permissions. Fits per-content and per-bundle rules.Requires custom access handlers and permissions. Fits records with rules unlike node access.Controlled by the administrative permission for configuration. Fits only admin-managed data.
ViewsWorks out of the box with the node base table and standard relationships.Works after the entity declares views data and a views integration. Plan the table mappings early.Limited. Views support depends on the module, so confirm before relying on it.
Editorial workflowFits content moderation, scheduled publishing and editor-facing forms.Needs custom workflow or content moderation support configured for the entity type.Not a fit. There is no editorial lifecycle for configuration.
Upgrade exposureLow when using core node features and standard contributed modules. Custom fields are the main risk.Higher. Custom entity classes, handlers and schema depend on core APIs that change across major versions.Low to moderate. Config schema changes need update hooks, and the format should be checked on each upgrade.
Choose whenContent is public-facing, editorially managed and fits the page model.Records have their own lifecycle, relations and access rules that do not fit a page.The data defines how the site behaves, and admins rather than content editors maintain it.

How revisions, translation and access differ by model

The core difference is how much Drupal already does for you. A node bundle inherits the node entity's revision handling, translation support and node access system, so editorial revisions, content translation and grants from modules such as Content Access or Node Access tie into existing forms, routes and permissions without additional code. A custom content entity gets none of this automatically. Your team must declare revisionability and translatability in the entity annotation or attributes, implement a revision and translation handler where the entity needs one, supply an access controller that answers view, create, update and delete checks, and define routes, forms and link templates for every page users reach. Each of these is code your team owns and must maintain through upgrades.

Before committing to a custom entity, confirm how each of these pieces behaves on the Drupal version you run and with the contributed modules you intend to use. Revisioning, translation and access APIs have changed across major versions, and some contributed modules only hook into nodes. Ask the implementation team to show, for your chosen version, which content moderation, translation and access modules work with the custom entity, and which need custom integration. Write these checks into the acceptance criteria so they are verified in a test site, not assumed from documentation.

The practical risk is an entity that looks complete in development but breaks in an editorial workflow. Test a draft revision, a published revision with a pending draft, a translated revision and a user with a restricted role, each against a realistic set of entities. Record which checks pass without custom code and which depend on your own handlers. That record shows the true maintenance cost of the custom route, and it gives you a clear basis for comparing the quote you receive for each model.

How to build a custom entity without losing upgrade headroom

This sequence settles the data model and access rules before any code is written, then builds the entity in layers so each one can be checked against the requirements. The result is a custom entity that behaves predictably for revisions, translations, permissions and Views, and that survives configuration export and import.

  1. Confirm the data model and access rules

    The product owner, a content architect and a Drupal developer list the business records, their fields, relationships, expected volume and who may create, edit, publish and delete each one. They also record whether records need revision history, translation and their own URL. The step is done when this is a signed-off document, and each open question about ownership or access has a named decision maker. Without it, the later choice between a node bundle and an entity is guesswork.

  2. Prototype with a node bundle where it is enough

    Before writing a custom entity, a developer builds the model as a content type with fields in a sandbox, using core features such as the content moderation workflow, revisions and Views. The team tests the editor experience and a sample of real-looking records, including translation if it is required. If the bundle meets every requirement from the first step, the project stops here and the entity is not built. If it does not, the prototype shows exactly which requirement forced the move, and that gap is recorded for the design review.

  3. Define the entity class, annotation and handlers

    A developer creates the entity type in a custom module, declaring its annotation or attribute, base table and revision and translation keys, the access control handler, the list builder and the form handlers. Routes and link templates follow from these declarations. The check at the end is that the entity type appears in the Drupal entity type list, can be created and saved programmatically, and that its routes resolve for an administrator. Confirm that revision and translation keys match the requirements from the first step before going further.

  4. Build routes, forms and access handling

    The developer wires the add, edit, delete and canonical routes, then implements the access handler so that permissions are checked per operation and per bundle, if bundles are used. An administrator, an editor and an anonymous user each try the operations the access model allows and forbids. The step closes when every denied operation returns a denial and no record is reachable through a guessed URL. This check is easy to skip and expensive to fix after content exists.

  5. Add Views integration and permissions

    The entity's Views data is declared so that a site builder can create lists, filters and relationships without custom queries. Permissions are defined in the module and mapped to the handler. A content lead builds one listing and one filtered view as a user with limited rights, and confirms that the results match the access rules and that unpublished or unauthorized records stay hidden. Record the view configuration as part of the module's shipped configuration so it can be reviewed and rebuilt.

  6. Verify revisions and translations against the specification

    A tester creates a record, edits it several times, reverts to an earlier revision and checks that the revision log and author data are correct. For translated records, the tester adds a second language, edits each translation separately and confirms that fields marked translatable or non-translatable behave as the specification states. Mismatches between the specification and the observed behavior are fixed before the next step, because they usually come from a missing key or handler declaration.

  7. Write automated tests for the entity

    The developer adds kernel tests for entity creation, validation, access and revisions, and functional or browser tests for the main editor and listing journeys. Tests run in the project's continuous integration pipeline on each change. The step is complete when the suite passes on a clean install and fails when a deliberately broken access rule is introduced, which proves the tests cover the rules that matter. The Drupal testing and code review guidance is the natural next read for the test layout.

  8. Confirm the entity survives configuration export and import

    A developer exports the site configuration from a working environment, imports it into a fresh environment with the same module version, and checks that the entity type, field storage, view configuration and permissions are all present and unchanged. Sample content is then restored from a database copy, not from configuration, because records are content rather than configuration. The step is done when the export diff is clean after import and the entity behaves the same on the new environment. Confirm the export process with the team's own deployment pipeline before relying on it.

Access control and security risks in custom entity design

Custom entities change where access rules live, how Views reads data and who is responsible for patching custom code. The cards below cover the failure points that most often leak data or leave a site exposed, and what to verify before launch and after each release.

Access control per entity type

A custom entity type defines one access handler for every bundle, so a permission grant on one bundle can reach records you meant to keep separate. Content types, by contrast, let you scope permissions per bundle from the start. Before you build, decide which bundles need different editors, then test each role against every bundle with the access handler in place. Confirm that the access handler and entity access checks cover view, create, update and delete operations separately.

Unpublished records leaking through Views

Views applies its own query, and custom entities do not always carry the published status and access checks that node queries inherit by default. An unpublished or restricted record can appear in a listing, search result or exposed filter if the query lacks the right access tag. Build each view as an admin and as an anonymous and authenticated user, then compare the rows returned. Verify that the query uses access tags for the entity type and that any custom query alters preserve them.

Custom routes missing access checks

A custom controller or route that loads an entity by ID can return data to anyone who guesses the URL, even when the entity itself has access rules. Each route needs an explicit access requirement, and the controller should check entity access before rendering or returning JSON. Review every route defined in the module's routing file and every REST or JSON:API resource exposed for the entity type. Include negative tests that request records the current user should not see.

Coding standards that keep access code reviewable

Access logic is easy to break in a small edit, which makes consistent structure more important than clever code. Follow Drupal coding standards for naming, dependency injection and docblocks, and avoid bypassing the entity access API with direct database queries. Run static analysis and the Drupal coding standards checks in CI so reviewers see violations before merge. Ask any custom-code vendor to show the review checklist they use for access-related changes.

Security advisories for core and contributed code

Drupal core and contributed modules publish security advisories with CVE identifiers and risk scores when vulnerabilities are resolved. A custom entity module that extends a contributed dependency inherits that project's advisories as well as your own code risks. Subscribe the owning team to the advisory feed, record which contributed projects each entity module depends on, and assign an owner for triage. Confirm who is responsible for reading advisories and deciding on the response window.

Applying security releases on schedule

Drupal publishes security releases on the third Wednesday of the month, and bugfix releases on the first Wednesday. Custom entity code must be retested after each core or contributed update because access handlers and query alters are sensitive to API changes. Set a standing window that includes access regression tests, and agree in writing who applies the release and who signs off. Ask any partner to state how quickly they will apply a security release to your environment.

How Views and translation change by model

A node bundle is a content type built on the core node entity. Because nodes are the most common entity in Drupal, Views ships with a mature integration for them. Editors can list, filter and sort node bundles through the standard Views interface, and the node entity already supports revisions, publishing and content moderation patterns that teams rely on. Translation is also built in: when the content language settings are enabled for a bundle, each translation is handled as part of the node. The practical question is whether your business fields fit the node model without workarounds.

  • Default listing: Views can list node bundles, filter them on fields and base properties, and relate them to users, taxonomy terms and other nodes through entity reference fields.
  • Translation: Translation is enabled per bundle through the core language and content translation settings, with each language stored as a translation of the same node.
  • Revisions: Revision support is available on nodes and is the default expectation for editorial content.
  • Access control: Node access follows the standard grants system, which is well understood but can slow listings when many grants apply.
  • Limits: Fields that do not describe content, such as orders, invoices or asset records, can look forced onto nodes and create awkward reporting.

Confirm which Views relationships your reports need, and test them with realistic translation and access settings before you commit the bundle design.

Questions teams ask before choosing an entity model

Is a content type enough for our business data in Drupal?

A content type is often enough when the data is page-like, editors create and publish it through the normal node workflow, and each record needs its own URL. It becomes a weaker fit when records are structured reference data, have no public page, or need their own access rules and revision history separate from content. Start by listing the records, who creates them, whether they need URLs, and whether they must be translated or reviewed. If most answers point to editorial pages, a bundle of nodes is usually the simpler choice. If records behave like rows in a business system, plan a custom content entity and test it against your real use cases before committing.

What does a custom content entity build actually involve?

A custom content entity is more than a new table. The work usually covers the entity class and its fields, storage and schema, access control handlers, route and form definitions, a list builder, permissions, and the revision and translation behavior you choose. Teams then add Views integration, tests for access and storage, and configuration for display modes. The main effort drivers are the number of entity types, how many fields carry logic, whether revisions and translations are required, how access depends on other records, and how much editors need to manage the data through the admin interface. Ask a partner to break the work into these parts in the written scope.

Can our in-house team build a custom entity type ourselves?

Yes, if the team has experience with Drupal's Entity API, service container, plugin system and automated tests. The harder part is not the first entity type but the decisions that follow: revision storage, translation fields, entity reference patterns and access logic that must stay correct as the site grows. Teams without that background often build something that works in the admin screen but fails under Views filters, multilingual content or permission checks. Before deciding, review your team's skills against the code review and testing practices for custom modules, and consider a short external review of the design even if the build stays in-house.

How does a custom entity affect future Drupal upgrades?

Core APIs for entities are stable enough that well-built entity types usually survive major upgrades, but the custom code around them needs ongoing care. Upgrade cost rises when the module depends on deprecated APIs, overrides internal classes, or mixes business logic into templates and hooks that change between versions. Keep the entity definition, access handlers and services in a focused module with tests, and run Upgrade Status checks before each major release. Content types built only from configuration generally carry less upgrade work, because they rely on core features rather than custom PHP.

Does migrating from Drupal 7 change which entity model we should choose?

Yes, the migration path shapes the decision. Drupal 6 and 7 sites cannot be updated in place to Drupal 10 or later. They are migrated into a new site with the core Migrate, Migrate Drupal and Migrate Drupal UI modules or with Drush, and themes must be rebuilt. Entity fields and content mapped to node bundles usually migrate more directly than data that lived in custom D7 tables or contributed entity modules. Confirm early which D7 structures would need a custom destination entity, because that adds mapping and test work. Drupal 7 security support ended on January 5, 2025, so the migration timeline itself is a planning constraint.

What should we ask a partner for in a fixed-scope quote?

Ask for a written scope that names each entity type, its fields, relationships, revision and translation requirements, access rules, Views listings, editor screens, integrations, migrated data sets and tests. Request a list of deliverables, the acceptance criteria for each, the assumptions about your environment and content volumes, and what is excluded. Also ask how the partner handles upgrade readiness, documentation and handover, and who owns the code after delivery. DeployOpen does not publish a price list, and a quote follows from the written scope, so a clear scope makes comparison with your internal estimate possible.

What ongoing costs come with a custom entity model?

The main recurring costs are maintenance of the custom module, testing as core and contributed modules update, and support for editors who work with the data every day. One-time costs include design, build, data migration and initial testing. Ongoing cost grows with the number of custom entity types, the volume of records, and the number of integrations that read them. Ask a partner which parts of the entity code are covered by a support or retainer arrangement and which need your team's ownership after launch.

Limits to confirm before committing to a custom entity

A custom entity type is only as portable as the platform underneath it, so the choice depends on the Drupal version, PHP runtime, database and web server you will run for years. The cards below list the checks to complete on a test site before the data model is built. Confirm each item with the vendor or on your own environment, not from memory.

Drupal version and support window

Confirm which Drupal major version you will build on and when its support ends. Drupal 11 is the current major version, Drupal 12 is scheduled for the week of December 7, 2026, and Drupal 10 reaches end of life on December 9, 2026. A custom entity built on a version that leaves support mid-project adds an upgrade before the business data is even stable, so plan the upgrade window into the build.

PHP runtime requirement

Each Drupal major version sets its own minimum PHP version. Drupal 10 requires PHP 8.1 or later, Drupal 11 requires PHP 8.3 or later and Drupal 12 requires PHP 8.5 or later. Check that your hosting image, container base and CI runners can run the target PHP version, because an entity schema built on a runtime you cannot upgrade will block the next Drupal major release.

Contributed module support

If the design depends on contributed modules for revisions, translation, Views integration or entity reference handling, confirm that each module has a release for your target Drupal version. Check its project page for the supported core range and the date of its last release. A custom entity that relies on an unmaintained module is harder to move forward than one built on core APIs alone.

Database engine compatibility

Drupal runs on MariaDB, MySQL, PostgreSQL or SQLite. Custom entities with large tables, many revisions or heavy Views queries behave differently across these engines, particularly around indexing, JSON fields and transaction handling. Confirm the exact engine and version your organization supports, then run the entity's revision and query load against that engine, not a local SQLite file.

Web server and runtime fit

Drupal runs on Apache, Nginx and other web servers. Confirm the server you operate and its configuration for clean URLs, caching headers and the access paths you will use for custom entity routes and any private file handling. Access control on entities depends on routing being correct, so test restricted routes directly on the server you will run in production.

Upgrade path and test coverage

Ask whether your team can run an upgrade rehearsal on a copy of production data before committing to the entity design. Upgrade Status and deprecation checks show which custom code needs changes, but they do not prove that stored entity data, revisions and access rules survive the move. Agree the rehearsal, its owner and its acceptance criteria in writing before the schema is frozen.

How DeployOpen scopes entity model work

DeployOpen plans, builds, documents and supports self-hosted open-source platforms inside your own infrastructure, including Drupal projects. For custom entity types versus content types in Drupal, we start with a written entity model review and carry the chosen design through custom code, tests and handover.

How we scope

  • Inventory each business data object and record how it is created, edited, reviewed and retired.
  • Record which objects need revisions, translation, moderation or per-record access rules.
  • Set out the written scope, deliverables and timeline before any build work starts.

How we build

  • Build the agreed entity types or bundles as custom code, keeping configuration in exportable form.
  • Write automated tests for access rules, revision behavior and Views output on each entity type.
  • Review the code against Drupal coding standards and deprecation checks before each release.

How we hand over

  • Document the entity model, field definitions, access logic and the reasons behind each choice.
  • Walk your engineers through the code, tests and configuration export so they can maintain it.
  • Record the upgrade-sensitive parts so your team knows what to review at each Drupal major version.

How we support

  • Ongoing support, upgrades and continuous testing are available as a retainer after handover.
  • Confirm which Drupal version and PHP requirement your entity code targets before planning upgrades.
  • Ask for a quote based on the written scope, which sets out what is one-time and what recurs.

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.