diff --git a/config/schema/field_default_token.schema.yml b/config/schema/field_default_token.schema.yml
new file mode 100644
index 0000000..bdf675e
--- /dev/null
+++ b/config/schema/field_default_token.schema.yml
@@ -0,0 +1,7 @@
+# Schema for the Field Default Token third party settings
+field.field.third_party.field_default_token:
+  type: mapping
+  mapping:
+    label_token:
+      label: 'Whether or not the specified token contains the value label instead of the key for list fields.'
+      type: boolean
diff --git a/field_default_token.info b/field_default_token.info
deleted file mode 100644
index 91b4a36..0000000
--- a/field_default_token.info
+++ /dev/null
@@ -1,4 +0,0 @@
-name = Field default token
-description = Enables to use tokens as field default values.
-package = Fields
-core = 7.x
diff --git a/field_default_token.info.yml b/field_default_token.info.yml
new file mode 100644
index 0000000..0d3d965
--- /dev/null
+++ b/field_default_token.info.yml
@@ -0,0 +1,5 @@
+name: 'Field default token'
+description: 'Enables to use tokens as field default values.'
+type: module
+package: Fields
+core: 8.x
diff --git a/field_default_token.install b/field_default_token.install
deleted file mode 100644
index 6d4fa95..0000000
--- a/field_default_token.install
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the field_default_token module.
- */
-
-/**
- * Update field instances which use tokens as default value.
- */
-function field_default_token_update_7000() {
-  $updated_fields = array();
-  foreach (field_info_instances() as $entity_instances) {
-    foreach ($entity_instances as $bundle_instances) {
-      foreach ($bundle_instances as $field_name => $instance) {
-        $has_tokens = FALSE;
-        if (!empty($instance['default_value'])) {
-          foreach ($instance['default_value'] as $values) {
-            if (!is_array($values)) {
-              if (strpos($values, '[') !== FALSE) {
-                $has_tokens = TRUE;
-              }
-            }
-            else {
-              foreach ($values as $value) {
-                if (!is_array($value)) {
-                  if (strpos($value, '[') !== FALSE) {
-                    $has_tokens = TRUE;
-                  }
-                }
-                else {
-                  foreach ($value as $column_value) {
-                    if (strpos($column_value, '[') !== FALSE) {
-                      $has_tokens = TRUE;
-                    }
-                  }
-                }
-              }
-            }
-          }
-        }
-        if ($has_tokens) {
-          field_update_instance($instance);
-          $updated_fields[$field_name] = $field_name;
-        }
-      }
-    }
-  }
-  if (!empty($updated_fields)) {
-    return t('Updated fields: @fields.', array('@fields' => implode(', ', $updated_fields)));
-  }
-}
diff --git a/field_default_token.module b/field_default_token.module
index 812e408..2004889 100644
--- a/field_default_token.module
+++ b/field_default_token.module
@@ -3,105 +3,82 @@
 /**
  * @file
  * Enables to use tokens as field default values.
+ *
+ * @todo Entity reference integration.
  */
 
+use Drupal\Core\Entity\FieldableEntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Element\Number;
+use Drupal\Core\Render\Element\Select;
+use Drupal\Core\TypedData\OptionsProviderInterface;
+use Drupal\field\FieldConfigInterface;
+
 /**
- * Implements hook_field_update_instance().
+ * Implements hook_ENTITY_TYPE_presave() for field configuration.
  */
-function field_default_token_field_update_instance($instance, $prior_instance) {
+function field_default_token_field_config_presave(FieldConfigInterface $field_config) {
+  // See https://www.drupal.org/node/2818877.
+  /** @var \Drupal\field\FieldConfigInterface|\Drupal\Core\Field\FieldConfigInterface $field_config */
   $has_tokens = FALSE;
-  if (!empty($instance['default_value'])) {
-    foreach ($instance['default_value'] as $values) {
-      if (!is_array($values)) {
-        if (strpos($values, '[') !== FALSE) {
-          $has_tokens = TRUE;
-        }
+  foreach ($field_config->getDefaultValueLiteral() as $item) {
+    foreach ($item as $property_value) {
+      if (is_array($property_value)) {
+        continue;
       }
-      else {
-        foreach ($values as $value) {
-          if (!is_array($value)) {
-            if (strpos($value, '[') !== FALSE) {
-              $has_tokens = TRUE;
-            }
-          }
-          else {
-            foreach ($value as $column_value) {
-              if (is_array($column_value)) {
-                // Complex fields such as Table Field contain a structure inside the value, skip them.
-                continue;
-              }
-              if (strpos($column_value, '[') !== FALSE) {
-                $has_tokens = TRUE;
-              }
-            }
-          }
-        }
+      elseif (strpos($property_value, '[') !== FALSE) {
+        $has_tokens = TRUE;
       }
     }
   }
 
-  $update = FALSE;
-
-  if (empty($instance['default_value_function'])) {
-    if ($has_tokens) {
-      $instance['default_value_function'] = 'field_default_token_default_value_function';
-      $update = TRUE;
-    }
+  $callback = $field_config->getDefaultValueCallback();
+  if (!$callback && $has_tokens) {
+    $field_config->setDefaultValueCallback('field_default_token_default_value_callback');
   }
-  elseif ($instance['default_value_function'] == 'field_default_token_default_value_function') {
-    if (!$has_tokens) {
-      unset($instance['default_value_function']);
-      $update = TRUE;
-    }
-  }
-
-  if ($update) {
-    // Save the instance and clear caches again without hook invoking.
-    // @see field_update_instance()
-    _field_write_instance($instance, TRUE);
-    field_cache_clear();
+  elseif ($callback === 'field_default_token_default_value_callback' && !$has_tokens) {
+    $field_config->setDefaultValueCallback(NULL);
   }
 }
 
 /**
- * Function for token replacement of default values.
+ * Default value callback for fields with default values containing tokens.
+ *
+ * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
+ *   The entity being created.
+ * @param \Drupal\Core\Field\FieldDefinitionInterface $definition
+ *   The field definition.
+ *
+ * @return array[]
+ *   A numerically indexed array of items, each item being an associative array
+ *   where the keys are the property names and the values the respective
+ *   property values.
  */
-function field_default_token_default_value_function($entity_type, $entity, $field, $instance, $langcode) {
-  $token_type = $entity_type;
-  $entity_info = entity_get_info($entity_type);
-  if (isset($entity_info['token type'])) {
-    $token_type = $entity_info['token type'];
-  }
-
-  // Do not try to generate tokens from stub entities.
-  list($entity_id) = entity_extract_ids($entity_type, $entity);
-  $data = !is_null($entity_id) ? array($token_type => $entity) : array();
+function field_default_token_default_value_callback(FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
+  $entity_type = $entity->getEntityType();
+  $token_type = $entity_type->get('token_type') ?: $entity_type->id();
 
-  $token_is_label = (isset($field['settings']['allowed_values']) && (is_array($field['settings']['allowed_values']))
-    && (!empty($instance['default_value_label_token'])));
+  $data = !$entity->isNew() ? [$token_type => $entity] : [];
 
-  if (empty($instance['default_value'])) {
-    return array();
-  }
+  $allowed_values = $definition->getSetting('allowed_values');
+  $token_is_label = $allowed_values
+    && ($definition instanceof FieldConfigInterface)
+    && $definition->getThirdPartySetting('field_default_token', 'label_token', FALSE);
 
-  $items = $instance['default_value'];
+  /** @var \Drupal\Core\Utility\Token $token */
+  $token = \Drupal::service('token');
+  $items = $definition->getDefaultValueLiteral();
   foreach ($items as &$item) {
-    if (!is_array($item)) {
-      $item = token_replace($item, $data, array('clear' => TRUE, 'sanitize' => FALSE));
-      if ($token_is_label) {
-        $item = array_search($item, $field['settings']['allowed_values']);
+    foreach ($item as $property_name => $property_value) {
+      if (is_array($property_value)) {
+        continue;
       }
-    }
-    else {
-      foreach ($item as $column_name => $column_value) {
-        if (is_array($column_value)) {
-          // Complex fields such as Table Field contain a structure inside the value, so we can't replace it with a string.
-          continue;
-        }
-        $item[$column_name] = token_replace($column_value, $data, array('clear' => TRUE, 'sanitize' => FALSE));
-        if ($token_is_label) {
-          $item[$column_name] = array_search($item[$column_name], $field['settings']['allowed_values']);
-        }
+
+      $item[$property_name] = $token->replace($property_value, $data, ['clear' => TRUE]);
+      if ($token_is_label) {
+        $item[$property_name] = array_search($item[$property_name], $allowed_values);
       }
     }
   }
@@ -109,81 +86,94 @@ function field_default_token_default_value_function($entity_type, $entity, $fiel
 }
 
 /**
- * Implements hook_form_FORM_ID_alter().
+ * Implements hook_form_FORM_ID_alter() for the field configuration edit form.
  */
-function field_default_token_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
-  $field = $form['#field'];
-  $instance = $form['#instance'];
-
-  if (!isset($form['instance']['default_value_widget'])) {
-    if ((field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT)
-        && (!empty($instance['default_value_function']))
-        && ($instance['default_value_function'] === 'field_default_token_default_value_function')) {
-      // Read default value widget hidden by core.
-      $form['instance']['default_value_widget'] = field_ui_default_value_widget($field, $instance, $form, $form_state);
+function field_default_token_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {
+  /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
+  $form_object = $form_state->getFormObject();
+  // See https://www.drupal.org/node/2818877.
+  /** @var \Drupal\field\FieldConfigInterface|\Drupal\Core\Field\FieldConfigInterface $field_config */
+  $field_config = $form_object->getEntity();
+
+  // \Drupal\Core\Field\FieldItemList::defaultValuesForm() does not display a
+  // default value form if there is a default value callback. In case the
+  // default value callback is 'field_default_token_default_value_callback'
+  // we display the default value form as if there were no callback.
+  if (!isset($form['default_value']) && $field_config->getDefaultValueCallback() === 'field_default_token_default_value_callback') {
+    // See \Drupal\field_ui\Form\FieldConfigEditForm::form()
+    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
+    $entity = $form['#entity'];
+    $items = $entity->get($field_config->getName());
+
+    $items->getFieldDefinition()->setDefaultValueCallback(NULL);
+    if ($element = $items->defaultValuesForm($form, $form_state)) {
+      $element = array_merge($element, [
+        '#type' => 'details',
+        '#title' => t('Default value'),
+        '#open' => TRUE,
+        '#tree' => TRUE,
+        '#description' => t('The default value for this field, used when creating new content.'),
+      ]);
+
+      $form['default_value'] = $element;
     }
+    $items->getFieldDefinition()->setDefaultValueCallback('field_default_token_default_value_function');
   }
 
-  $options_widgets = array('options_select', 'options_buttons', 'options_onoff');
-
-  if ((isset($instance['widget']['type'])) && (in_array($instance['widget']['type'], $options_widgets))) {
+  /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */
+  $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type');
+  $field_type = $field_type_plugin_manager->getDefinition($field_config->getType());
+  if (is_subclass_of($field_type['class'], OptionsProviderInterface::class)) {
     $default_value = '';
+    foreach ($field_config->getDefaultValueLiteral() as $item) {
+      foreach ($item as $value) {
+        if (is_array($value)) {
+          continue;
+        }
 
-    if (!empty($instance['default_value'])) {
-      foreach ($instance['default_value'] as $values) {
-        foreach ($values as $value) {
-          if (strpos($value, '[') !== FALSE) {
-            $default_value = $value;
-          }
+        if (strpos($value, '[') !== FALSE) {
+          $default_value = $value;
         }
       }
     }
 
-    $form['instance']['default_value_widget']['default_value_token'] = array(
+    $form['default_value']['default_value_token'] = [
       '#type' => 'textfield',
       '#title' => t('Token for default value'),
       '#description' => t('If set, this token will be used as the field default value instead.'),
       '#maxlength' => 1024,
       '#default_value' => $default_value,
-      '#weight' => 100,
-    );
+    ];
   }
 
-  if (isset($form['instance']['default_value_widget'])) {
-    field_default_token_enlarge_max_length($form['instance']['default_value_widget']);
-    field_default_token_fix_number_validation($form['instance']['default_value_widget']);
+  if (isset($form['default_value'])) {
+    field_default_token_enlarge_max_length($form['default_value']);
+    field_default_token_fix_number_validation($form['default_value']);
 
     // Allow tokens to be field value labels, not just field values.
-    if ((isset($field['settings']['allowed_values'])) && (is_array($field['settings']['allowed_values']))) {
-      $form['instance']['default_value_label_token'] = array(
+    if ($field_config->getSetting('allowed_values')) {
+      $form['third_party_settings']['field_default_token']['label_token'] = [
         '#type' => 'checkbox',
         '#title' => t('Token for default value contains field value label, not stored key'),
         '#description' => t('If checked, token value must be field value label from allowed values list of key|label pairs.'),
-        '#default_value' => isset($instance['default_value_label_token']) ? $instance['default_value_label_token'] : FALSE,
-        '#weight' => 150,
-      );
+        '#default_value' => $field_config->getThirdPartySetting('field_default_token', 'label_token', FALSE),
+      ];
     }
 
-    $entity_type = $form['#instance']['entity_type'];
-    $token_type = $entity_type;
-    $entity_info = entity_get_info($entity_type);
-    if (isset($entity_info['token type'])) {
-      $token_type = $entity_info['token type'];
-    }
-
-    $form['instance']['default_value_widget']['token_tree'] = array(
-      '#theme' => 'token_tree',
-      '#token_types' => array($token_type),
-      '#dialog' => TRUE,
+    $target_entity_type = \Drupal::entityTypeManager()->getDefinition($field_config->getTargetEntityTypeId());
+    $token_type = $target_entity_type->get('token_type') ?: $target_entity_type->id();
+    $form['default_value']['token_tree'] = [
+      '#theme' => 'token_tree_link',
+      '#token_types' => [$token_type],
       '#weight' => 200,
-    );
+    ];
   }
 
   // Replace validator to disable validation of strings with tokens
   // in Field UI forms.
   foreach ($form['#validate'] as &$validator) {
-    if ($validator == 'field_ui_field_edit_form_validate') {
-      $validator = 'field_default_token_field_ui_field_edit_form_validate';
+    if ($validator == '::validateForm') {
+      $validator = 'field_default_token_field_config_edit_form_validate';
     }
   }
 }
@@ -201,7 +191,7 @@ function field_default_token_enlarge_max_length(&$element) {
       $element['#maxlength'] = 1024;
     }
   }
-  foreach (element_children($element) as $key) {
+  foreach (Element::children($element) as $key) {
     if (isset($element[$key]) && $element[$key]) {
       field_default_token_enlarge_max_length($element[$key]);
     }
@@ -217,12 +207,12 @@ function field_default_token_enlarge_max_length(&$element) {
 function field_default_token_fix_number_validation(&$element) {
   if (!empty($element['#element_validate'])) {
     foreach ($element['#element_validate'] as &$callback) {
-      if ($callback === 'number_field_widget_validate') {
-        $callback = 'field_default_token_number_field_widget_validate';
+      if ($callback === [Number::class, 'validateNumber']) {
+        $callback = 'field_default_token_number_validate';
       }
     }
   }
-  foreach (element_children($element) as $key) {
+  foreach (Element::children($element) as $key) {
     if (isset($element[$key]) && $element[$key]) {
       field_default_token_fix_number_validation($element[$key]);
     }
@@ -230,22 +220,11 @@ function field_default_token_fix_number_validation(&$element) {
 }
 
 /**
- * Implements hook_ctools_plugin_directory().
- */
-function field_default_token_ctools_plugin_directory($module, $plugin) {
-  if ($module == 'entityreference') {
-    return 'plugins/' . $plugin;
-  }
-}
-
-/**
  * Implements hook_field_widget_form_alter().
  */
-function field_default_token_field_widget_form_alter(&$element, &$form_state, $context) {
-  if (!empty($element['#entity_type'])) {
-    if (empty($element['#entity'])) {
-      field_default_token_modify_field_ui_form($element, $form_state, $context);
-    }
+function field_default_token_field_widget_form_alter(&$element, FormStateInterface $form_state, $context) {
+  if ($context['default']) {
+    field_default_token_modify_field_ui_form($element, $form_state, $context);
   }
 }
 
@@ -265,23 +244,24 @@ function field_default_token_modify_field_ui_form(&$element, &$form_state, $cont
  * Replacement for form_type_select_value() functions
  * for selectors, checkboxes, radio buttons (Field UI forms only).
  */
-function field_default_token_selection_element_value(&$element, $input, $form_state) {
-  if (!empty($form_state['input']['default_value_token'])) {
+function field_default_token_selection_element_value(&$element, $input, FormStateInterface $form_state) {
+  $user_input = $form_state->getUserInput();
+  if (!empty($user_input['default_value_input']['default_value_token'])) {
+    $token = $user_input['default_value_input']['default_value_token'];
+
     // Disable validation on Field UI forms.
     $element['#after_build'][] = 'field_default_token_remove_validation';
-    $token = $form_state['input']['default_value_token'];
 
     if ($input !== FALSE) {
       if (isset($element['#multiple']) && $element['#multiple']) {
-        $input = array($token);
+        $input = [$token];
       }
       else {
         $input = $token;
       }
     }
   }
-
-  return form_type_select_value($element, $input);
+  return Select::valueCallback($element, $input, $form_state);
 }
 
 /**
@@ -290,58 +270,44 @@ function field_default_token_selection_element_value(&$element, $input, $form_st
  * Disables field validation on Field UI forms
  * for selectors, checkboxes and radio buttons.
  */
-function field_default_token_remove_validation(&$element, &$form_state) {
+function field_default_token_remove_validation($element, FormStateInterface $form_state) {
   unset($element['#needs_validation']);
   return $element;
 }
 
 /**
- * Form validation handler for field_ui_field_edit_form.
+ * Form validation handler for the field configuration edit form.
  *
- * Replacement for field_ui_field_edit_form_validate().
+ * Replacement for \Drupal\field_ui\Form\FieldConfigEditForm::validateForm().
  */
-function field_default_token_field_ui_field_edit_form_validate($form, &$form_state) {
-  // Take the incoming values as the $instance definition, so that the 'default
-  // value' gets validated using the instance settings being submitted.
-  $instance = $form_state['values']['instance'];
-  $field_name = $instance['field_name'];
-
-  if (isset($form['instance']['default_value_widget'])) {
-    $element = $form['instance']['default_value_widget'];
-
-    $field_state = field_form_get_state($element['#parents'], $field_name, LANGUAGE_NONE, $form_state);
-    $field = $field_state['field'];
-
-    // Extract the 'default value'.
-    $items = array();
-    field_default_extract_form_values(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state);
-
+function field_default_token_field_config_edit_form_validate($form, FormStateInterface $form_state) {
+  // See \Drupal\field_ui\Form\FieldConfigEditForm::validateForm().
+  if (isset($form['default_value']) && ($widget = $form_state->get('default_value_widget'))) {
+    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
+    $entity = $form['#entity'];
+    /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
+    $form_object = $form_state->getFormObject();
+    /** @var \Drupal\field\FieldConfigInterface $field_config */
+    $field_config = $form_object->getEntity();
+    $items = $entity->get($field_config->getName());
+
+    $widget->extractFormValues($items, $form['default_value'], $form_state);
     foreach ($items as $item) {
-      if (is_array($item)) {
-        foreach ($item as $value) {
-          if (is_scalar($value)) {
-            if (strpos($value, '[') !== FALSE) {
-              // Token in default value, do not validate.
-              return;
-            }
-          }
+      /** @var \Drupal\Core\TypedData\TypedDataInterface $property */
+      foreach ($item as $property) {
+        $value = $property->getValue();
+        if (is_array($value)) {
+          continue;
+        }
+
+        if (strpos($value, '[') !== FALSE) {
+          // Token in default value, do not validate.
+          return;
         }
       }
     }
 
-    // Validate the value and report errors.
-    $errors = array();
-    $function = $field['module'] . '_field_validate';
-    if (function_exists($function)) {
-      $function(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $errors);
-    }
-    if (isset($errors[$field_name][LANGUAGE_NONE])) {
-      // Store reported errors in $form_state.
-      $field_state['errors'] = $errors[$field_name][LANGUAGE_NONE];
-      field_form_set_state($element['#parents'], $field_name, LANGUAGE_NONE, $form_state, $field_state);
-      // Assign reported errors to the correct form element.
-      field_default_form_errors(NULL, NULL, $field, $instance, LANGUAGE_NONE, $items, $element, $form_state);
-    }
+    $items->defaultValuesFormValidate($form['default_value'], $form, $form_state);
   }
 }
 
@@ -350,11 +316,11 @@ function field_default_token_field_ui_field_edit_form_validate($form, &$form_sta
  *
  * Replacement for number_field_widget_validate().
  */
-function field_default_token_number_field_widget_validate($element, &$form_state) {
+function field_default_token_number_validate(&$element, FormStateInterface $form_state, &$complete_form) {
   $value = $element['#value'];
   if (strpos($value, '[') !== FALSE) {
     // Token in default value, do not validate.
     return;
   }
-  number_field_widget_validate($element, $form_state);
+  Number::validateNumber($element, $form_state, $complete_form);
 }
diff --git a/plugins/behavior/FieldDefaultTokenEmptyAlter.class.php b/plugins/behavior/FieldDefaultTokenEmptyAlter.class.php
deleted file mode 100644
index 7668fd0..0000000
--- a/plugins/behavior/FieldDefaultTokenEmptyAlter.class.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-/**
- * @file
- * Additional behavior for Entity reference fields.
- */
-
-/**
- * Prevents field value containing tokens from being treated as empty.
- */
-class FieldDefaultTokenEmptyAlter extends EntityReference_BehaviorHandler_Abstract {
-
-  /**
-   * Alter the empty status of a field item.
-   */
-  public function is_empty_alter(&$empty, $item, $field) {
-    // If field value contains tokens, entityreference.module treats it as empty.
-    if (($empty) && (isset($item['target_id'])) && (strpos($item['target_id'], '[') !== FALSE)) {
-      $empty = FALSE;
-    }
-  }
-
-}
diff --git a/plugins/behavior/field_default_token.inc b/plugins/behavior/field_default_token.inc
deleted file mode 100644
index 7a060d2..0000000
--- a/plugins/behavior/field_default_token.inc
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-/**
- * @file
- * CTools plugin declaration for FieldDefaultTokenEmptyAlter behavior.
- */
-
-$plugin = array(
-  'title' => t('Allows tokens as field default value'),
-  'description' => t('Allows using tokens while selecting the default value of the field.'),
-  'class' => 'FieldDefaultTokenEmptyAlter',
-  'behavior type' => 'field',
-  'force enabled' => TRUE,
-  'weight' => 100,
-);
diff --git a/tests/src/Kernel/FieldDefaultTokenBasicTest.php b/tests/src/Kernel/FieldDefaultTokenBasicTest.php
new file mode 100644
index 0000000..37462fb
--- /dev/null
+++ b/tests/src/Kernel/FieldDefaultTokenBasicTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Tests\field_default_token\Kernel;
+
+use Drupal\entity_test\Entity\EntityTest;
+
+/**
+ * Tests that tokens in field default values get replaced correctly.
+ *
+ * @group field_default_token
+ */
+class FieldDefaultTokenBasicTest extends FieldDefaultTokenKernelTestBase  {
+
+  /**
+   * The site name of the test site.
+   *
+   * @var string
+   */
+  protected $siteName;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $entityTypeId = 'entity_test';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['entity_test', 'system', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
+    $config_factory = $this->container->get('config.factory');
+    $this->siteName = $config_factory->get('system.site')->get('name');
+  }
+
+  /**
+   * Tests that the default value callback is registered for a new field.
+   */
+  public function testCallbackNewField() {
+    $field = $this->createField();
+    $field->setDefaultValue('This is the site name: [site:name]')->save();
+    $this->assertEquals('field_default_token_default_value_callback', $field->getDefaultValueCallback());
+  }
+
+  /**
+   * Tests that the default value callback is registered for an existing field.
+   */
+  public function testCallbackExistingField() {
+    $field = $this->createField();
+    $field->save();
+    $this->assertEquals('', $field->getDefaultValueCallback());
+
+    $field->setDefaultValue('This is the site name: [site:name]')->save();
+    $this->assertEquals('field_default_token_default_value_callback', $field->getDefaultValueCallback());
+  }
+
+  /**
+   * Tests the the default value callback is removed properly.
+   */
+  public function testCallbackRemoval() {
+    $field = $this->createField();
+    $field->setDefaultValue('This is the site name: [site:name]')->save();
+    $this->assertEquals('field_default_token_default_value_callback', $field->getDefaultValueCallback());
+
+    $field->setDefaultValue('There are no tokens to see here, move along')->save();
+    $this->assertNull($field->getDefaultValueCallback());
+  }
+
+  /**
+   * Test that tokens in a field default value get replaced properly.
+   */
+  public function testReplacement() {
+    $field = $this->createField();
+    $field->setDefaultValue('This is the site name: [site:name]')->save();
+
+    $entity = EntityTest::create();
+    $entity->save();
+
+    $expected = [['value' => 'This is the site name: ' . $this->siteName]];
+    $this->assertEquals($expected, $field->getDefaultValue($entity));
+  }
+
+}
diff --git a/tests/src/Kernel/FieldDefaultTokenKernelTestBase.php b/tests/src/Kernel/FieldDefaultTokenKernelTestBase.php
new file mode 100644
index 0000000..c8e0abb
--- /dev/null
+++ b/tests/src/Kernel/FieldDefaultTokenKernelTestBase.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\field_default_token\Kernel;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Provides a common base class for kernel tests of Field Default Token module.
+ */
+abstract class FieldDefaultTokenKernelTestBase extends KernelTestBase  {
+
+  /**
+   * The ID of the entity type used in the test.
+   *
+   * @var string
+   */
+  protected $entityTypeId;
+
+  /**
+   * The bundle of the entity type used in the test.
+   *
+   * If this is left empty, the entity type ID will be used as a bundle.
+   *
+   * @var string
+   */
+  protected $bundle;
+
+  /**
+   * The name of the field used in the test.
+   *
+   * @var string
+   */
+  protected $fieldName = 'field_default_token_test';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['field', 'field_default_token'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema($this->entityTypeId);
+    FieldStorageConfig::create([
+      'field_name' => $this->fieldName,
+      'entity_type' => $this->entityTypeId,
+      'type' => 'string',
+    ])->save();
+  }
+
+  /**
+   * Creates a test field configuration.
+   *
+   * @return \Drupal\field\Entity\FieldConfig
+   *   The field configuration.
+   */
+  protected function createField() {
+    return FieldConfig::create([
+      'field_name' => $this->fieldName,
+      'entity_type' => $this->entityTypeId,
+      'bundle' => $this->bundle ?: $this->entityTypeId,
+    ]);
+  }
+
+}
diff --git a/tests/src/Kernel/FieldDefaultTokenNodeTest.php b/tests/src/Kernel/FieldDefaultTokenNodeTest.php
new file mode 100644
index 0000000..b72568a
--- /dev/null
+++ b/tests/src/Kernel/FieldDefaultTokenNodeTest.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\Tests\field_default_token\Kernel;
+
+use Drupal\node\Entity\Node;
+use Drupal\node\Entity\NodeType;
+
+/**
+ * Tests that tokens in default values of node fields get replaced correctly.
+ *
+ * @group field_default_token
+ */
+class FieldDefaultTokenNodeTest extends FieldDefaultTokenKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $entityTypeId = 'node';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $bundle = 'article';
+
+  /**
+   * {@inheritdoc}
+   *
+   * DRUPAL_OPTIONAL is defined in system.module.
+   */
+  public static $modules = ['node', 'system', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('user');
+
+    NodeType::create(['type' => $this->bundle])->save();
+  }
+
+  /**
+   * Test that tokens in a field default value get replaced properly.
+   */
+  public function testReplacement() {
+    $field = $this->createField();
+    $field->setDefaultValue('This is the node title: [node:title]')->save();
+
+    $node = Node::create([
+      'type' => $this->bundle,
+      'title' => 'Test node title',
+    ]);
+
+    // Make sure that the token is properly stripped before it can be replaced.
+    $expected = [['value' => 'This is the node title: ']];
+    $this->assertEquals($expected, $field->getDefaultValue($node));
+
+    $node->save();
+
+    $expected = [['value' => 'This is the node title: Test node title']];
+    $this->assertEquals($expected, $field->getDefaultValue($node));
+  }
+
+}
