Programmatic Term Operations
When to Use
Use this guide when creating, updating, or deleting terms via code (migrations, imports, install hooks, drush commands).
Programmatic approach complements config-first vocabulary management.
Steps
Create single term:
-
Use Term::create() — Instantiate term entity
use Drupal\taxonomy\Entity\Term; $term = Term::create([ 'vid' => 'tags', 'name' => 'Drupal 11', 'description' => [ 'value' => 'Posts about Drupal 11', 'format' => 'basic_html', ], 'weight' => 0, 'parent' => [0], // Root level ]); -
Call save() — Persist to database
$term->save(); $tid = $term->id();
Update existing term:
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
$term = $term_storage->load($tid);
$term->setName('Updated Name');
$term->setDescription('New description');
$term->setWeight(10);
$term->save();
Delete term:
$term = $term_storage->load($tid);
$term->delete();
// Or bulk delete
$terms = $term_storage->loadMultiple($tids);
$term_storage->delete($terms);
Batch creation (>100 terms):
function mymodule_import_terms_batch($data) {
$batch = [
'title' => t('Importing terms'),
'operations' => [],
'finished' => 'mymodule_import_terms_finished',
];
foreach ($data as $item) {
$batch['operations'][] = [
'mymodule_import_term_operation',
[$item],
];
}
batch_set($batch);
}
function mymodule_import_term_operation($item, &$context) {
Term::create([
'vid' => 'categories',
'name' => $item['name'],
'parent' => [$item['parent_tid']],
])->save();
$context['message'] = t('Imported @name', ['@name' => $item['name']]);
}
Hierarchy manipulation:
// Add child term
$parent = $term_storage->load($parent_tid);
$child = Term::create([
'vid' => 'categories',
'name' => 'Child Category',
'parent' => [$parent->id()],
]);
$child->save();
// Add multiple parents
$term = $term_storage->load($tid);
$term->set('parent', [$parent1_tid, $parent2_tid]);
$term->save();
// Move term to different parent
$term = $term_storage->load($tid);
$term->set('parent', [$new_parent_tid]);
$term->save();
Common Mistakes
- Not setting
parentfield → Defaults to root (0), but explicit is better. Always set'parent' => [0]for top-level or[$parent_tid]for children - Using
create()in loops without batching → Causes timeouts and memory issues with >100 terms. Use Batch API or queue workers for bulk operations - Forgetting description format → Description is formatted text:
['value' => '...', 'format' => 'basic_html']. Omitting format causes default (plain_text) - Saving terms without checking duplicates → Use
loadByProperties(['vid' => $vid, 'name' => $name])to check for existing terms before creating - Deleting parent term without handling children → Children become orphans or deleted (depending on config). Load and re-parent or delete children explicitly
- Not validating vocabulary exists → Loading non-existent vid fails silently. Check vocabulary entity exists before creating terms
- Calling
save()unnecessarily in read-only operations → Every save triggers hooks, cache clear, search indexing. Only save when changing data
See Also
- ← Previous: Term Storage & Querying | Next: Taxonomy with Entity Reference →
- Reference:
/core/modules/taxonomy/src/Entity/Term.php(lines 137-143)