diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
index 55173dd270..20eeaea9cb 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -32,6 +32,7 @@ class ContentModerationStateTest extends KernelTestBase {
     'media_test_source',
     'image',
     'file',
+    'taxonomy',
     'field',
     'content_moderation',
     'user',
@@ -63,6 +64,7 @@ protected function setUp() {
     $this->installEntitySchema('block_content');
     $this->installEntitySchema('media');
     $this->installEntitySchema('file');
+    $this->installEntitySchema('taxonomy_term');
     $this->installEntitySchema('content_moderation_state');
     $this->installConfig('content_moderation');
     $this->installSchema('file', 'file_usage');
@@ -111,7 +113,7 @@ public function testBasicModeration($entity_type_id) {
     $workflow->save();
 
     $entity = $entity_storage->create([
-      'title' => 'Test title',
+      $this->entityTypeManager->getDefinition($entity_type_id)->getKey('label') => 'Test title',
       $this->entityTypeManager->getDefinition($entity_type_id)->getKey('bundle') => $bundle_id,
     ]);
     if ($entity instanceof EntityPublishedInterface) {
@@ -196,6 +198,9 @@ public function basicModerationTestCases() {
       'Media' => [
         'media',
       ],
+      'Taxonomy term' => [
+        'taxonomy_term',
+      ],
       'Test Entity with Bundle' => [
         'entity_test_with_bundle',
       ],
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
index 44a4e83033..d93b9db1e9 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
@@ -86,6 +86,9 @@ protected function getExpectedNormalizedEntity() {
       'tid' => [
         ['value' => 1],
       ],
+      'revision_id' => [
+        ['value' => 1],
+      ],
       'uuid' => [
         ['value' => $this->entity->uuid()],
       ],
@@ -129,6 +132,16 @@ protected function getExpectedNormalizedEntity() {
           'langcode' => 'en',
         ],
       ],
+      'status' => [
+        [
+          'value' => TRUE,
+        ]
+      ],
+      'revision_created' => [
+        $this->formatExpectedTimestampItemValues((int) $this->entity->getRevisionCreationTime()),
+      ],
+      'revision_user' => [],
+      'revision_log_message' => [],
     ];
   }
 
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 03491ab962..6c05e86d9a 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -2,8 +2,7 @@
 
 namespace Drupal\taxonomy\Entity;
 
-use Drupal\Core\Entity\ContentEntityBase;
-use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EditorialContentEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -30,14 +29,23 @@
  *   },
  *   base_table = "taxonomy_term_data",
  *   data_table = "taxonomy_term_field_data",
+ *   revision_table = "taxonomy_term_revision",
+ *   revision_data_table = "taxonomy_term_field_revision",
  *   uri_callback = "taxonomy_term_uri",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "tid",
+ *     "revision" = "revision_id",
  *     "bundle" = "vid",
  *     "label" = "name",
  *     "langcode" = "langcode",
- *     "uuid" = "uuid"
+ *     "uuid" = "uuid",
+ *     "published" = "status",
+ *   },
+ *   revision_metadata_keys = {
+ *     "revision_user" = "revision_user",
+ *     "revision_created" = "revision_created",
+ *     "revision_log_message" = "revision_log_message",
  *   },
  *   bundle_entity_type = "taxonomy_vocabulary",
  *   field_ui_base_route = "entity.taxonomy_vocabulary.overview_form",
@@ -48,12 +56,13 @@
  *     "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
  *     "create" = "/taxonomy/term",
  *   },
- *   permission_granularity = "bundle"
+ *   permission_granularity = "bundle",
+ *   constraints = {
+ *     "TaxonomyHierarchy" = {}
+ *   }
  * )
  */
-class Term extends ContentEntityBase implements TermInterface {
-
-  use EntityChangedTrait;
+class Term extends EditorialContentEntityBase implements TermInterface {
 
   /**
    * {@inheritdoc}
@@ -118,6 +127,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['name'] = BaseFieldDefinition::create('string')
       ->setLabel(t('Name'))
       ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
       ->setRequired(TRUE)
       ->setSetting('max_length', 255)
       ->setDisplayOptions('view', [
@@ -134,6 +144,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['description'] = BaseFieldDefinition::create('text_long')
       ->setLabel(t('Description'))
       ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
       ->setDisplayOptions('view', [
         'label' => 'hidden',
         'type' => 'text_default',
@@ -161,7 +172,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['changed'] = BaseFieldDefinition::create('changed')
       ->setLabel(t('Changed'))
       ->setDescription(t('The time that the term was last edited.'))
-      ->setTranslatable(TRUE);
+      ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE);
 
     return $fields;
   }
diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyHierarchyConstraint.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyHierarchyConstraint.php
new file mode 100644
index 0000000000..e3d8e3c84c
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyHierarchyConstraint.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\taxonomy\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Validation constraint for changing taxonomy hierarchies in forward revisions.
+ *
+ * @Constraint(
+ *   id = "TaxonomyHierarchy",
+ *   label = @Translation("Taxonomy hierarchy.", context = "Validation"),
+ * )
+ */
+class TaxonomyHierarchyConstraint extends Constraint {
+
+  public $message = 'You can only change the taxonomy hierarchy for the <em>published</em> version of this content.';
+
+}
diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyHierarchyConstraintValidator.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyHierarchyConstraintValidator.php
new file mode 100644
index 0000000000..256ab0ff00
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyHierarchyConstraintValidator.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\taxonomy\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Constraint validator for changing taxonomy hierarchies in forward revisions.
+ */
+class TaxonomyHierarchyConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Creates a new BookOutlineConstraintValidator instance.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($entity, Constraint $constraint) {
+    if (isset($entity) && !$entity->isNew() && !$entity->isDefaultRevision()) {
+      /** @var \Drupal\taxonomy\TermStorage $storage */
+      $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+      $new_parent_id = $entity->get('parent')->target_id;
+      /** @var \Drupal\taxonomy\TermInterface[] $parents */
+      $parents = $storage->loadParents($entity->id());
+      foreach ($parents as $parent) {
+        if ($parent->id() != $new_parent_id) {
+          $this->context->buildViolation($constraint->message)
+            ->atPath('term.parent')
+            ->setInvalidValue($entity)
+            ->addViolation();
+        }
+      }
+    }
+  }
+
+}
diff --git a/core/modules/taxonomy/src/TermStorageSchema.php b/core/modules/taxonomy/src/TermStorageSchema.php
index 5bcb088db4..822d1f4e02 100644
--- a/core/modules/taxonomy/src/TermStorageSchema.php
+++ b/core/modules/taxonomy/src/TermStorageSchema.php
@@ -17,7 +17,7 @@ class TermStorageSchema extends SqlContentEntityStorageSchema {
   protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
     $schema = parent::getEntitySchema($entity_type, $reset = FALSE);
 
-    $schema['taxonomy_term_field_data']['indexes'] += [
+    $schema[$entity_type->getDataTable()]['indexes'] += [
       'taxonomy_term__tree' => ['vid', 'weight', 'name'],
       'taxonomy_term__vid_name' => ['vid', 'name'],
     ];
diff --git a/core/modules/taxonomy/src/Tests/Update/TaxonomyUpdateTest.php b/core/modules/taxonomy/src/Tests/Update/TaxonomyUpdateTest.php
new file mode 100644
index 0000000000..53680bef8f
--- /dev/null
+++ b/core/modules/taxonomy/src/Tests/Update/TaxonomyUpdateTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\taxonomy\Tests\Update;
+
+use Drupal\system\Tests\Update\UpdatePathTestBase;
+use Drupal\user\Entity\User;
+
+/**
+ * Tests the upgrade path for taxonomy terms.
+ *
+ * @group Update
+ */
+class TaxonomyUpdateTest extends UpdatePathTestBase  {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
+    ];
+  }
+
+  /**
+   * Tests the conversion of taxonomy terms to be revisionable and publishable.
+   *
+   * @see taxonomy_update_8400()
+   * @see taxonomy_post_update_make_taxonomy_term_revisionable()
+   */
+  public function testConversionToRevisionableAndPublishable() {
+    $this->runUpdates();
+
+    // Log in as user 1.
+    $account = User::load(1);
+    $account->pass_raw = 'drupal';
+    $this->drupalLogin($account);
+
+    // Make sure our vocabulary exists.
+    $this->drupalGet('admin/structure/taxonomy/manage/test_vocabulary/overview');
+
+    // Make sure our terms exist.
+    $this->assertText('Test root term');
+    $this->assertText('Test child term');
+    $this->drupalGet('taxonomy/term/3');
+    $this->assertResponse('200');
+
+    // Make sure the terms are still translated.
+    $this->drupalGet('taxonomy/term/2/translations');
+    $this->assertLink('Test root term - Spanish');
+
+    // Check that taxonomy terms can be created, saved and then loaded.
+    $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
+    /** @var \Drupal\taxonomy\Entity\Term $term */
+    $term = $storage->create([
+      'name' => 'Test term',
+      'vid' => 'article',
+    ]);
+    $term->save();
+
+    $storage->resetCache();
+    $term = $storage->loadRevision($term->getRevisionId());
+
+    $this->assertEqual('Test term', $term->label());
+    $this->assertEqual('article', $term->bundle());
+    $this->assertTrue($term->isPublished());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function replaceUser1() {
+    // Do not replace the user from our dump.
+  }
+
+}
diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install
new file mode 100644
index 0000000000..991f40b098
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.install
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Taxonomy module.
+ */
+
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function taxonomy_update_dependencies() {
+  // The update function that adds the status field must run after
+  // content_translation_update_8400() which fixes NULL values for the
+  // 'content_translation_status' field.
+  if (\Drupal::moduleHandler()->moduleExists('content_translation')) {
+    $dependencies['taxonomy'][8400] = [
+      'content_translation' => 8400,
+    ];
+
+    return $dependencies;
+  }
+}
+
+/**
+ * Add the 'published' and revisionable metadata fields to taxonomy terms.
+ */
+function taxonomy_update_8400() {
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+
+  // Add the published entity key and revisionable metadata fields to the
+  // taxonomy_term entity type.
+  $entity_type = $definition_update_manager->getEntityType('taxonomy_term');
+
+  $entity_keys = $entity_type->getKeys();
+  $entity_keys['published'] = 'status';
+  $entity_type->set('entity_keys', $entity_keys);
+
+  $revision_metadata_keys = [
+    'revision_user' => 'revision_user',
+    'revision_created' => 'revision_created',
+    'revision_log_message' => 'revision_log_message'
+  ];
+  $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+
+  $definition_update_manager->updateEntityType($entity_type);
+
+  // Add the status field.
+  $status = BaseFieldDefinition::create('boolean')
+    ->setLabel(t('Publishing status'))
+    ->setDescription(t('A boolean indicating the published state.'))
+    ->setRevisionable(TRUE)
+    ->setTranslatable(TRUE)
+    ->setDefaultValue(TRUE);
+
+  $has_content_translation_status_field = \Drupal::moduleHandler()->moduleExists('content_translation') && $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term');
+  if ($has_content_translation_status_field) {
+    $status->setInitialValueFromField('content_translation_status');
+  }
+  else {
+    $status->setInitialValue(TRUE);
+  }
+  $definition_update_manager->installFieldStorageDefinition('status', 'taxonomy_term', 'taxonomy_term', $status);
+
+  // Add the revision metadata fields.
+  $revision_created = BaseFieldDefinition::create('created')
+    ->setLabel(t('Revision create time'))
+    ->setDescription(t('The time that the current revision was created.'))
+    ->setRevisionable(TRUE);
+  $definition_update_manager->installFieldStorageDefinition('revision_created', 'taxonomy_term', 'taxonomy_term', $revision_created);
+
+  $revision_user = BaseFieldDefinition::create('entity_reference')
+    ->setLabel(t('Revision user'))
+    ->setDescription(t('The user ID of the author of the current revision.'))
+    ->setSetting('target_type', 'user')
+    ->setRevisionable(TRUE);
+  $definition_update_manager->installFieldStorageDefinition('revision_user', 'taxonomy_term', 'taxonomy_term', $revision_user);
+
+  $revision_log_message = BaseFieldDefinition::create('string_long')
+    ->setLabel(t('Revision log message'))
+    ->setDescription(t('Briefly describe the changes you have made.'))
+    ->setRevisionable(TRUE)
+    ->setDefaultValue('')
+    ->setDisplayOptions('form', [
+      'type' => 'string_textarea',
+      'weight' => 25,
+      'settings' => [
+        'rows' => 4,
+      ],
+    ]);
+  $definition_update_manager->installFieldStorageDefinition('revision_log_message', 'taxonomy_term', 'taxonomy_term', $revision_log_message);
+
+  // Uninstall the 'content_translation_status' field if needed.
+  $database = \Drupal::database();
+  if ($has_content_translation_status_field) {
+    // First we have to remove the field data.
+    $database->update($entity_type->getDataTable())
+      ->fields(['content_translation_status' => NULL])
+      ->execute();
+
+    // A site may have disabled revisionability for this entity type.
+    if ($entity_type->isRevisionable()) {
+      $database->update($entity_type->getRevisionDataTable())
+        ->fields(['content_translation_status' => NULL])
+        ->execute();
+    }
+
+    $content_translation_status = $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term');
+    $definition_update_manager->uninstallFieldStorageDefinition($content_translation_status);
+  }
+
+  return t('Taxonomy terms have been converted to revisionable and publishable.');
+}
diff --git a/core/modules/taxonomy/taxonomy.post_update.php b/core/modules/taxonomy/taxonomy.post_update.php
new file mode 100644
index 0000000000..993d260aa6
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.post_update.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for the Taxonomy module.
+ */
+
+use \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchemaConverter;
+
+/**
+ * Update taxonomy terms to be revisionable.
+ */
+function taxonomy_post_update_make_taxonomy_term_revisionable(&$sandbox) {
+  $schema_converter = new SqlContentEntityStorageSchemaConverter(
+    'taxonomy_term',
+    \Drupal::entityTypeManager(),
+    \Drupal::entityDefinitionUpdateManager(),
+    \Drupal::service('entity.last_installed_schema.repository'),
+    \Drupal::keyValue('entity.storage_schema.sql'),
+    \Drupal::database()
+  );
+
+  $schema_converter->convertToRevisionable(
+    $sandbox,
+    [
+      'name',
+      'description',
+      'changed',
+    ]
+  );
+}
