Skip to content

Anti-Patterns & Common Mistakes

When to Use

Use this guide when reviewing taxonomy implementations to identify and fix problematic patterns.

Decision

Anti-Pattern Why It's Bad Correct Approach
Using taxonomy as content type Terms lack field flexibility, revision control, view modes Use content type with entity reference when you need >3 fields or rich content
Creating terms programmatically on every request Database writes on every page load; orphaned terms Create terms during content save; use auto_create in field config
Loading entire term tree on every page Memory bloat, slow page loads Cache term tree; load on-demand
Deep hierarchies (>5 levels) Unusable UI, poor performance Flatten to 2-3 levels max
Vocabulary per content type Prevents cross-content categorization Use shared vocabularies
Not restricting target_bundles in fields Widget shows terms from ALL vocabularies Always set target_bundles to specific vocabulary
Calling loadTree() with load_entities = TRUE on large vocabs Out-of-memory errors, timeouts Use load_entities = FALSE, load specific entities afterward
Deleting terms without checking references Broken entity references Query for term usage first; unpublish instead of delete if used
Auto-creating terms without validation Spam, typos, duplicates Implement hook_taxonomy_term_presave() to validate
Using 'administer taxonomy' for content editors Allows vocabulary deletion, structure changes Use per-vocabulary permissions

Pattern

Anti-pattern: Loading all terms in widget

// BAD: Loads all terms, slow with large vocabularies
$terms = $term_storage->loadByProperties(['vid' => 'tags']);
foreach ($terms as $term) {
  $options[$term->id()] = $term->getName();
}

Better: Use autocomplete widget

# Field form display config
type: entity_reference_autocomplete
# Loads terms via AJAX, no page load penalty

Anti-pattern: Term as pseudo-content

// BAD: Trying to add multiple fields to term
$term = Term::create([
  'vid' => 'products',
  'name' => 'Widget Pro',
  'field_price' => 99.99,
  'field_sku' => 'WID-001',
  'field_description' => 'Long description...',
  'field_images' => [...],
]);

Better: Use content type

// GOOD: Content type for rich content
$node = Node::create([
  'type' => 'product',
  'title' => 'Widget Pro',
  'field_price' => 99.99,
  'field_category' => ['target_id' => $category_term_id], // Reference taxonomy term
]);

Common Mistakes

  • Wrong: Not caching loadTree() results → Right: Cache in static variable or cache bin; invalidate on term save
  • Wrong: Using taxonomy_index table for non-node entities → Right: taxonomy_index only tracks nodes; query entity reference fields directly for custom entities
  • Wrong: Assuming term order is stable → Right: Always sort explicitly if order matters
  • Wrong: Creating vocabulary in update hook without checking existence → Right: Check if vocabulary exists first
  • Wrong: Not handling term deletion in field references → Right: Implement hook_taxonomy_term_delete() to clean up
  • Wrong: Using fixed term IDs in code → Right: Reference by name or use config entities to map names to IDs

See Also