diff --git a/core/modules/tour/lib/Drupal/tour/Plugin/Core/Entity/Tour.php b/core/modules/tour/lib/Drupal/tour/Plugin/Core/Entity/Tour.php
index aa0107d..4f639ce 100644
--- a/core/modules/tour/lib/Drupal/tour/Plugin/Core/Entity/Tour.php
+++ b/core/modules/tour/lib/Drupal/tour/Plugin/Core/Entity/Tour.php
@@ -50,9 +50,8 @@ class Tour extends ConfigEntityBase {
    * The paths in which this tip can be displayed.
    *
    * @var array
-   *   An array of paths.
    */
-  protected $paths;
+  protected $paths = array();
 
   /**
    * Holds the collection of tips that are attached to this tour.
@@ -66,7 +65,7 @@ class Tour extends ConfigEntityBase {
    *
    * @var array
    */
-  protected $tips;
+  protected $tips = array();
 
   /**
    * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct();
@@ -78,6 +77,16 @@ public function __construct(array $values, $entity_type) {
   }
 
   /**
+   * Returns the ID of tour.
+   *
+   * @return string
+   *   The ID of the tour.
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
    * Returns label of tour.
    *
    * @return string
@@ -114,7 +123,9 @@ public function getTip($id) {
    *   A list of tips.
    */
   public function getTipList() {
-    return array_keys($this->tips);
+    if (!empty($this->tips)) {
+      return array_keys($this->tips);
+    }
   }
 
   /**
diff --git a/core/modules/tour/lib/Drupal/tour/Plugin/tour/tip/TipPluginText.php b/core/modules/tour/lib/Drupal/tour/Plugin/tour/tip/TipPluginText.php
index c189d23..aebe888 100644
--- a/core/modules/tour/lib/Drupal/tour/Plugin/tour/tip/TipPluginText.php
+++ b/core/modules/tour/lib/Drupal/tour/Plugin/tour/tip/TipPluginText.php
@@ -15,6 +15,7 @@
  *
  * @Plugin(
  *   id = "text",
+ *   label = "Text",
  *   module = "tour"
  * )
  */
@@ -45,7 +46,7 @@ class TipPluginText extends TipPluginBase {
   public function getAriaId() {
     static $id;
     if (!isset($id)) {
-      $id = drupal_html_id($this->get('id'));
+      $id = drupal_html_id($this->getId());
     }
     return $id;
   }
@@ -71,7 +72,7 @@ public function getLocation() {
   }
 
   /**
-   * Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getAttributes();
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginBase::getAttributes();
    */
   public function getAttributes() {
     $attributes = parent::getAttributes();
@@ -84,11 +85,52 @@ public function getAttributes() {
   }
 
   /**
-   * Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getOutput();
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginBase::getOutput();
    */
   public function getOutput() {
     $output = '<h2 class="tour-tip-label" id="tour-tip-' . $this->getAriaId() . '-label">' . check_plain($this->getLabel()) . '</h2>';
     $output .= '<p class="tour-tip-body" id="tour-tip-' . $this->getAriaId() . '-contents">' . filter_xss_admin($this->getBody()) . '</p>';
     return array('#markup' => $output);
   }
+
+  /**
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginBase::getExportProperties().
+   */
+  public function getExportProperties() {
+    $properties = parent::getExportProperties();
+    $names = array(
+      'body',
+      'location',
+    );
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+  /**
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginBase::optionsForm().
+   */
+  public function optionsForm() {
+    $form = parent::optionsForm();
+    $form['location'] = array(
+      '#type' => 'select',
+      '#title' => t('Location'),
+      '#options' => array(
+        'top' => t('Top'),
+        'bottom' => t('Button'),
+        'left' => t('Left'),
+        'right' => t('Right'),
+      ),
+      '#default_value' => $this->getLocation(),
+    );
+    $form['body'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Body'),
+      '#required' => TRUE,
+      '#default_value' => $this->getBody(),
+    );
+    return $form;
+  }
+
 }
diff --git a/core/modules/tour/lib/Drupal/tour/TipPluginBase.php b/core/modules/tour/lib/Drupal/tour/TipPluginBase.php
index a9d42e0..e597400 100644
--- a/core/modules/tour/lib/Drupal/tour/TipPluginBase.php
+++ b/core/modules/tour/lib/Drupal/tour/TipPluginBase.php
@@ -51,28 +51,42 @@ public function __construct(array $configuration, $plugin_id, CacheDecorator $di
   }
 
   /**
-   * Implements \Drupal\tour\Plugin\tour\tour\TourInterface::getLabel().
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::getId().
+   */
+  public function getId() {
+    return $this->get('id');
+  }
+
+  /**
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::getPlugin().
+   */
+  public function getPlugin() {
+    return $this->get('plugin');
+  }
+
+  /**
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::getLabel().
    */
   public function getLabel() {
     return $this->get('label');
   }
 
   /**
-   * Implements \Drupal\tour\Plugin\tour\tour\TourInterface::getWeight().
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::getWeight().
    */
   public function getWeight() {
     return $this->get('weight');
   }
 
   /**
-   * Implements \Drupal\tour\Plugin\tour\tour\TourInterface::getAttributes().
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::getAttributes().
    */
   public function getAttributes() {
     return $this->get('attributes');
   }
 
   /**
-   * Implements \Drupal\tour\Plugin\tour\tour\TourInterface::get().
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::get().
    */
   public function get($key) {
     if (!empty($this->configuration[$key])) {
@@ -81,9 +95,86 @@ public function get($key) {
   }
 
   /**
-   * Implements \Drupal\tour\Plugin\tour\tour\TourInterface::set().
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::set().
    */
   public function set($key, $value) {
     $this->configuration[$key] = $value;
   }
+
+  /**
+   * Implements \Drupal\tour\Plugin\tour\tour\TourPluginInterface::getExportProperties().
+   */
+  public function getExportProperties() {
+    $names = array(
+      'id',
+      'plugin',
+      'label',
+      'weight',
+      'attributes',
+    );
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+  /**
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginInterface::optionsForm().
+   */
+  public function optionsForm() {
+    $form = array();
+    $id = $this->getId();
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Label'),
+      '#required' => TRUE,
+      '#default_value' => $this->getLabel(),
+    );
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#machine_name' => array(
+        'exists' => '_tour_load',
+        'replace_pattern' => '[^a-z0-9-]+',
+        'replace' => '-',
+      ),
+      '#default_value' => $id,
+      '#disabled' => !empty($id),
+    );
+    $form['plugin'] = array(
+      '#type' => 'value',
+      '#value' => $this->getPlugin(),
+    );
+    $form['weight'] = array(
+      '#type' => 'weight',
+      '#title' => t('Weight'),
+      '#default_value' => $this->getWeight(),
+      '#attributes' => array(
+        'class' => array('tip-order-weight'),
+      ),
+    );
+    $attributes = $this->getAttributes();
+    $form['attributes'] = array(
+      '#type' => 'fieldset', 
+      '#title' => t('Attributes'), 
+      '#collapsible' => TRUE, 
+      '#collapsed' => FALSE,
+      '#tree' => TRUE,
+    );
+    $form['attributes']['data-id'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Data id'),
+      '#description' => t('Provide a id selector for this tip to be attached to on the specified tour page. If this and <em>Data class</em> field is not specified the tip will be a modal.'),
+      '#field_prefix' => '#',
+      '#default_value' => !empty($attributes['data-id']) ? $attributes['data-id'] : '',
+    );
+    $form['attributes']['data-class'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Data class'),
+      '#description' => t('Provide a class selector for this tip to be attached to on the specified tour page. If this and <em>Data id</em> field is not specified the tip will be a modal.'),
+      '#field_prefix' => '.',
+      '#default_value' => !empty($attributes['data-class']) ? $attributes['data-class'] : '',
+    );
+    return $form;
+  }
+
 }
diff --git a/core/modules/tour/lib/Drupal/tour/TipPluginInterface.php b/core/modules/tour/lib/Drupal/tour/TipPluginInterface.php
index 3f376ef..bfbe872 100644
--- a/core/modules/tour/lib/Drupal/tour/TipPluginInterface.php
+++ b/core/modules/tour/lib/Drupal/tour/TipPluginInterface.php
@@ -13,6 +13,22 @@
 interface TipPluginInterface {
 
   /**
+   * Returns id of the tip.
+   *
+   * @return string
+   *   The id of the tip.
+   */
+  public function getId();
+
+  /**
+   * Returns plugin of the tip.
+   *
+   * @return string
+   *   The plugin of the tip.
+   */
+  public function getPlugin();
+
+  /**
    * Returns label of the tip.
    *
    * @return string
@@ -65,4 +81,21 @@ public function set($key, $value);
    *   A renderable array.
    */
   public function getOutput();
+
+  /**
+   * Retrieves the exportable properties of the plugin.
+   *
+   * @return array
+   *   An array of exportable properties and their values.
+   */
+  public function getExportProperties();
+
+  /**
+   * Provides a form for creation of this type of tip.
+   *
+   * @return array
+   *   An renderable form.
+   */
+  public function optionsForm();
+
 }
diff --git a/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Plugin/tour/tip/TipPluginImage.php b/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Plugin/tour/tip/TipPluginImage.php
index bde0902..e158b5c 100644
--- a/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Plugin/tour/tip/TipPluginImage.php
+++ b/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Plugin/tour/tip/TipPluginImage.php
@@ -15,6 +15,7 @@
  *
  * @Plugin(
  *   id = "image",
+ *   label = "Image",
  *   module = "tour_test"
  * )
  */
@@ -37,12 +38,67 @@ class TipPluginImage extends TipPluginBase {
   protected $alt;
 
   /**
+   * Returns url of the image tip.
+   *
+   * @return string
+   *   The url of the image tip.
+   */
+  public function getUrl() {
+    return $this->get('url');
+  }
+
+  /**
+   * Returns alt text of the image tip.
+   *
+   * @return string
+   *   The alt text of the image tip.
+   */
+  public function getAlt() {
+    return $this->get('alt');
+  }
+
+  /**
    * Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getOutput().
    */
   public function getOutput() {
-    $output = '<h2 class="tour-tip-label" id="tour-tip-' . $this->get('ariaId') . '-label">' . check_plain($this->get('label')) . '</h2>';
-    $output .= '<p class="tour-tip-image" id="tour-tip-' . $this->get('ariaId') . '-contents">' . theme('image', array('uri' => $this->get('url'), 'alt' => $this->get('alt'))) . '</p>';
+    $output = '<h2 class="tour-tip-label" id="tour-tip-' . $this->get('ariaId') . '-label">' . check_plain($this->getLabel()) . '</h2>';
+    $output .= '<p class="tour-tip-image" id="tour-tip-' . $this->get('ariaId') . '-contents">' . theme('image', array('uri' => $this->getUrl(), 'alt' => $this->getAlt())) . '</p>';
     return array('#markup' => $output);
   }
 
+  /**
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginBase::getExportProperties().
+   */
+  public function getExportProperties() {
+    $properties = parent::getExportProperties();
+    $names = array(
+      'url',
+      'alt',
+    );
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+  /**
+   * Overrides \Drupal\tour\Plugin\tour\tour\TourPluginBase::optionsForm().
+   */
+  public function optionsForm() {
+    $form = parent::optionsForm();
+    $form['url'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Url'),
+      '#required' => TRUE,
+      '#default_value' => $this->getUrl(),
+    );
+    $form['alt'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Alt'),
+      '#required' => TRUE,
+      '#default_value' => $this->getAlt(),
+    );
+    return $form;
+  }
+
 }
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/Routing/TourUIController.php b/core/modules/tour_ui/lib/Drupal/tour_ui/Routing/TourUIController.php
new file mode 100644
index 0000000..b50c6ae
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/Routing/TourUIController.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\tour_ui\Routing\TourUIController.
+ */
+
+namespace Drupal\tour_ui\Routing;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\tour\Plugin\Core\Entity\Tour;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * @todo.
+ */
+class TourUIController {
+
+  /**
+   * The manager of this tour UI.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $manager;
+
+  /**
+   * Construction function for the tour UI controller.
+   */
+  public function __construct(EntityManager $manager) {
+    $this->manager = $manager;
+  }
+
+  /**
+   * Provies a listing form for a tour entity.
+   *
+   * @return array
+   *   A renderable form array.
+   */
+  public function listing() {
+    return $this->manager->getListController('tour')->render();
+  }
+
+  /**
+   * Provies a creation form for tour.
+   *
+   * @return array
+   *   A renderable form array.
+   */
+  public function add() {
+    $tour = $this->manager->getStorageController('tour')->create(array());
+    return entity_get_form($tour);
+  }
+
+  /**
+   * Provies an edit form for a tour entity.
+   *
+   * @return array
+   *   A renderable form array.
+   */
+  public function edit(Tour $tour) {
+    return entity_get_form($tour);
+  }
+
+  /**
+   * Provies a creation form for a new tip to be added to a tour entity.
+   *
+   * @return array
+   *   A renderable form array.
+   */
+  public function addTip(Tour $tour, $type) {
+    // We need a type to build this form.
+    if (empty($type)) {
+      throw new NotFoundHttpException();
+    }
+
+    // Default values.
+    $request = drupal_container()->get('request');
+    $defaults = array(
+      'plugin' => $type,
+      'weight' => $request->query->get('weight'),
+    );
+
+    // Build a new stub tip.
+    $manager = drupal_container()->get('plugin.manager.tour.tip');
+    $stub = $manager->createInstance($type, $defaults);
+
+    // Attach the tour, tip and if it's new to the form.
+    $form_state['#tour'] = $tour;
+    $form_state['#tip'] = $stub;
+    $form_state['#new'] = TRUE;
+
+    return entity_get_form($tour, 'tips', $form_state);
+  }
+
+  /**
+   * Provies an edit form for tip to be updated against a tour entity.
+   *
+   * @return array
+   *   A renderable form array.
+   */
+  public function editTip(Tour $tour, $tip = '') {
+    // We need a tip to build this form.
+    if (empty($tip) && empty($tour)) {
+      throw new NotFoundHttpException();
+    }
+
+    // If the tip doesn't exist return.
+    $tips = $tour->getTipList();
+    if (!in_array($tip, $tips)) {
+      throw new NotFoundHttpException();
+    }
+
+    // Attach the tour, tip and if it's new to the form.
+    $form_state['#tour'] = $tour;
+    $form_state['#tip'] = $tour->getTip($tip);
+
+    return entity_get_form($tour, 'tips', $form_state);
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/Tests/TourUITest.php b/core/modules/tour_ui/lib/Drupal/tour_ui/Tests/TourUITest.php
new file mode 100644
index 0000000..8f4aba2
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/Tests/TourUITest.php
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\tour_ui\Tests\TourUITest.
+ */
+
+namespace Drupal\tour_ui\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the Tour UI..
+ */
+class TourUITest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('tour_ui', 'tour_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Tour UI',
+      'description' => 'Tests the Tour UI.',
+      'group' => 'Tour',
+    );
+  }
+
+  /**
+   * Tests the listing and editing of a tour.
+   */
+  public function testUI() {
+    $this->drupalLogin($this->root_user);
+    $this->listTest();
+    $this->editTest();
+    $this->tipTest();
+  }
+
+  /**
+   * Tests the listing of a tour.
+   */
+  protected function listTest() {
+    // Assert that all three test tours are shown.
+    $this->drupalGet('admin/config/user-interface/tour');
+    $elements = $this->xpath('//table/tbody/tr');
+    $this->assertEqual(count($elements), 3);
+
+    // The first column contains the title.
+    $elements = $this->xpath('//table/tbody/tr[contains(@class, :class)]/td[1]', array(':class' => 'tour-test-en'));
+    $this->assertIdentical((string) $elements[0], t('Tour test english'));
+    // The second column contains the paths.
+    $elements = $this->xpath('//table/tbody/tr[contains(@class, :class)]/td/a[contains(@href, :href)]', array(':class' => 'tour-test-en', ':href' => 'tour-test-1'));
+    $this->assertIdentical((string) $elements[0], '/tour-test-1');
+    // The third column contains the number of tips.
+    $elements = $this->xpath('//table/tbody/tr[contains(@class, :class)]/td[3]', array(':class' => 'tour-test-en'));
+    $this->assertIdentical((string) $elements[0], '2');
+  }
+
+  /**
+   * Tests the editing of a tour.
+   */
+  protected function editTest() {
+    // Create a new tour. Ensure that it comes before the test tours.
+    $edit = array(
+      'label' => 'a' . $this->randomString(),
+      'id' => strtolower($this->randomName()),
+    );
+    $this->drupalPost('admin/config/user-interface/tour/add', $edit, t('Save'));
+    $this->assertRaw(t('The %tour tour has been created.', array('%tour' => $edit['label'])));
+    $elements = $this->xpath('//table/tbody/tr');
+    $this->assertEqual(count($elements), 1);
+
+    // Edit and re-save an existing tour.
+    $this->assertTitle(t('Edit tour | @site-name', array('@site-name' => config('system.site')->get('name'))));
+    $this->drupalPost(NULL, array(), t('Save'));
+    $this->assertRaw(t('Updated the %tour tour', array('%tour' => $edit['label'])));
+
+    // Reorder the tour tips.
+    $this->drupalGet('admin/config/user-interface/tour/manage/tour-test-en/edit');
+    $weights = array(
+      'tips[tour-test-1][weight]' => '2',
+      'tips[tour-test-3][weight]' => '1',
+    );
+    $this->drupalPost(NULL, $weights, t('Save'));
+    $this->drupalGet('admin/config/user-interface/tour/manage/tour-test-en/edit');
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable odd',
+      ':text' => 'The awesome image',
+    ));
+    $this->assertEqual(count($elements), 1, 'Found odd tip "The awesome image".');
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable even',
+      ':text' => 'The first tip',
+    ));
+    $this->assertEqual(count($elements), 1, 'Found even tip "The first tip".');
+    $weights = array(
+      'tips[tour-test-1][weight]' => '1',
+      'tips[tour-test-3][weight]' => '2',
+    );
+    $this->drupalPost(NULL, $weights, t('Save'));
+    $this->drupalGet('admin/config/user-interface/tour/manage/tour-test-en/edit');
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable odd',
+      ':text' => 'The first tip',
+    ));
+    $this->assertEqual(count($elements), 1, 'Found odd tip "The first tip".');
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable even',
+      ':text' => 'The awesome image',
+    ));
+    $this->assertEqual(count($elements), 1, 'Found even tip "The awesome image".');
+
+    // Attempt to create a duplicate tour.
+    $this->drupalPost('admin/config/user-interface/tour/add', $edit, t('Save'));
+    $this->assertRaw(t('The machine-readable name is already in use. It must be unique.'));
+
+    // Delete a tour.
+    $this->drupalGet('admin/config/user-interface/tour/manage/' . $edit['id'] . '/edit');
+    $this->drupalPost(NULL, NULL, t('Delete'));
+    $this->assertRaw(t('Are you sure you want to delete the %tour tour?', array('%tour' => $edit['label'])));
+    $this->clickLink(t('Cancel'));
+    $this->clickLink(t('Delete'));
+    $this->drupalPost(NULL, NULL, t('Delete'));
+    $elements = $this->xpath('//table/tbody/tr');
+    $this->assertEqual(count($elements), 3);
+    $this->assertRaw(t('Deleted the %tour tour.', array('%tour' => $edit['label'])));
+  }
+
+  /**
+   * Tests the add/edit/delete of a tour tip.
+   */
+  protected function tipTest() {
+    // Create a new tour for tips to be added to.
+    $edit = array(
+      'label' => 'a' . $this->randomString(),
+      'id' => strtolower($this->randomName()),
+      'paths' => '',
+    );
+    $this->drupalPost('admin/config/user-interface/tour/add', $edit, t('Save'));
+    $this->assertRaw(t('The %tour tour has been created.', array('%tour' => $edit['label'])));
+
+    // Add a new tip.
+    $tip = array(
+      'new' => 'image',
+    );
+    $this->drupalPost('admin/config/user-interface/tour/manage/' . $edit['id'] . '/edit', $tip, t('Add'));
+    $tip = array(
+      'label' => 'a' . $this->randomString(),
+      'id' => 'tour-ui-test-image-tip',
+      'url' => 'http://testimage.png',
+      'alt' => 'Testing a new image tip through Tour UI.',
+    );
+    $this->drupalPost(NULL, $tip, t('Save'));
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable odd',
+      ':text' => $tip['label'],
+    ));
+    $this->assertEqual(count($elements), 1, 'Found tip "' . $tip['label']. '".');
+
+    // Edit the tip.
+    $tip_id = $tip['id'];
+    unset($tip['id']);
+    $tip['label'] = 'a' . $this->randomString();
+    $this->drupalPost('admin/config/user-interface/tour/manage/' . $edit['id'] . '/tip/edit/' . $tip_id, $tip, t('Save'));
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable odd',
+      ':text' => $tip['label'],
+    ));
+    $this->assertEqual(count($elements), 1, 'Found tip "' . $tip['label']. '".');
+
+    // Delete the tip.
+    $this->drupalPost('admin/config/user-interface/tour/manage/' . $edit['id'] .'/tip/delete/' . $tip_id, array(), t('Delete'));
+    $elements = $this->xpath('//tr[@class=:class and ./td[contains(., :text)]]', array(
+      ':class' => 'draggable odd',
+      ':text' => $tip['label'],
+    ));
+    $this->assertNotEqual(count($elements), 1, 'Did not find tip "' . $tip['label']. '".');
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/TourDeleteForm.php b/core/modules/tour_ui/lib/Drupal/tour_ui/TourDeleteForm.php
new file mode 100644
index 0000000..a1778e1
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/TourDeleteForm.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\tour_ui\TourDeleteForm.
+ */
+
+namespace Drupal\tour_ui;
+
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Builds the form to delete a tour.
+ */
+class TourDeleteForm implements FormInterface {
+
+  /**
+   * Stores the tour entity being deleted.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * Creates a new instance of this form.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $tour
+   *   The tour being acted upon.
+   *
+   * @return array
+   *   The built form array.
+   */
+  public function getForm(EntityInterface $tour) {
+    $this->entity = $tour;
+    return drupal_get_form($this);
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::getFormID().
+   */
+  public function getFormID() {
+    return 'tour_ui_confirm_delete';
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::buildForm().
+   */
+  public function buildForm(array $form, array &$form_state) {
+    return confirm_form($form,
+      t('Are you sure you want to delete the %tour tour?', array('%tour' => $this->entity->label())),
+      'admin/config/user-interface/tour',
+      t('This action cannot be undone.'),
+      t('Delete'),
+      t('Cancel')
+    );
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::validateForm().
+   */
+  public function validateForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::submitForm().
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $this->entity->delete();
+    $form_state['redirect'] = 'admin/config/user-interface/tour';
+    drupal_set_message(t('Deleted the %tour tour.', array('%tour' => $this->entity->label())));
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/TourFormController.php b/core/modules/tour_ui/lib/Drupal/tour_ui/TourFormController.php
new file mode 100644
index 0000000..4bf08d1
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/TourFormController.php
@@ -0,0 +1,251 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\tour_ui\TourFormController.
+ */
+
+namespace Drupal\tour_ui;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+
+/**
+ * Form controller for the tour entity edit forms.
+ */
+class TourFormController extends EntityFormController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $entity) {
+    $form = parent::form($form, $form_state, $entity);
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Tour name'),
+      '#required' => TRUE,
+      '#default_value' => $entity->label(),
+    );
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#machine_name' => array(
+        'exists' => '_tour_load',
+        'replace_pattern' => '[^a-z0-9-]+',
+        'replace' => '-',
+      ),
+      '#default_value' => $entity->id(),
+      '#disabled' => !$entity->isNew(),
+    );
+    $form['langcode'] = array(
+      '#type' => 'language_select',
+      '#title' => t('Language'),
+      '#languages' => LANGUAGE_ALL,
+      // We default to the content language opposed to und (language not specified).
+      '#default_value' => $entity->isNew() ? language(LANGUAGE_TYPE_CONTENT)->langcode : $entity->langcode,
+    );
+    $form['paths'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Paths'),
+      '#default_value' => implode("\n", $entity->getPaths()),
+      '#rows' => 5,
+      '#description' => t('Provide a list of paths that this tour will be displayed on.'),
+    );
+
+    // Don't show the tips on the inital add.
+    if ($entity->isNew()) {
+      return $form;
+    }
+
+    $form['tips'] = array(
+      '#type' => 'table',
+      '#header' => array(
+        t('Label'),
+        t('Weight'),
+        t('Operations'),
+      ),
+      '#caption' => t('Tips provided by this tour.'),
+      '#tabledrag' => array(
+        array('order', 'sibling', 'tip-order-weight'),
+      ),
+      '#weight' => 40,
+    );
+
+    $tip_ids = $entity->getTipList();
+    if (!empty($tip_ids)) {
+
+      $tips = array();
+      foreach ($tip_ids as $id) {
+        $tips[$id] = $entity->getTip($id);
+      }
+
+      // Sort by weight.
+      uasort($tips, function ($a, $b) {
+      if ($a->getWeight() == $b->getWeight()) {
+        return 0;
+      }
+        return ($a->getWeight() < $b->getWeight()) ? -1 : 1;
+      });
+
+      foreach ($tips as $key => $tip) {
+        $form['#data'][$key] = $tip->getExportProperties();
+        $form['tips'][$key]['#attributes']['class'][] = 'draggable';
+        $form['tips'][$key]['label'] = array(
+          '#markup' => check_plain($tip->getLabel()),
+        );
+
+        $form['tips'][$key]['weight'] = array(
+          '#type' => 'weight',
+          '#title' => t('Weight for @title', array('@title' => $tip->getLabel())),
+          '#title_display' => 'invisible',
+          '#default_value' => $tip->getWeight(),
+          '#attributes' => array(
+            'class' => array('tip-order-weight'),
+          ),
+        );
+
+        $links = array();
+        // @todo, Setup forms on the tip types.
+        $tip_form = $tip->optionsForm();
+        if (!empty($tip_form)) {
+          $links['edit'] = array(
+            'title' => t('edit'),
+            'href' => 'admin/config/user-interface/tour/manage/' . $entity->id() . '/tip/edit/' . $key,
+          );
+        }
+        $links['delete'] = array(
+          'title' => t('delete'),
+          'href' => 'admin/config/user-interface/tour/manage/' . $entity->id() . '/tip/delete/' . $key,
+        );
+        $form['tips'][$key]['operations'] = array(
+          '#type' => 'operations',
+          '#links' => $links,
+        );
+      }
+    }
+
+    // Build the new tour tip addition form and add it to the tips list.
+    $tip_definitions = drupal_container()->get('plugin.manager.tour.tip')->getDefinitions();
+    foreach ($tip_definitions as $tip => $definition) {
+      $tip_definition_options[$tip] = $definition['label'];
+    }
+    $form['tips']['new'] = array(
+      '#tree' => FALSE,
+      '#weight' => isset($form_state['input']['weight']) ? $form_state['input']['weight'] : NULL,
+      '#attributes' => array(
+        'class' => array('draggable')
+      ),
+    );
+    $form['tips']['new']['new'] = array(
+      '#type' => 'select',
+      '#title' => t('Tip'),
+      '#title_display' => 'invisible',
+      '#options' => $tip_definition_options,
+      '#empty_option' => t('Select a new tip'),
+    );
+    $form['tips']['new']['weight'] = array(
+      '#type' => 'weight',
+      '#title' => t('Weight for new tip'),
+      '#title_display' => 'invisible',
+      '#default_value' => count($form['tips']) - 1,
+      '#attributes' => array(
+        'class' => array('tip-order-weight'),
+      ),
+    );
+    $form['tips']['new']['add'] = array(
+      '#type' => 'submit',
+      '#value' => t('Add'),
+      '#validate' => array(array($this, 'tipValidate')),
+      '#submit' => array(array($this, 'tipAdd')),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Validate handler.
+   */
+  public function tipValidate($form, &$form_state) {
+    if (!$form_state['values']['new']) {
+      form_error($form['tips']['new']['new'], t('Select a new tip.'));
+    }
+  }
+
+  /**
+   * Submit handler.
+   */
+  public function tipAdd($form, &$form_state) {
+    $tour = $this->getEntity($form_state);
+
+    // Merge the form values in with the current configuration.
+    $tips = array();
+    if (!empty($form_state['values']['tips'])) {
+      foreach ($form_state['values']['tips'] as $key => $values) {
+        $data = $form['#data'][$key];
+        $tips[$key] = array_merge($data, $values);
+      }
+    }
+    else {
+      $tips = array();
+    }
+    $tour->set('tips', $tips);
+    $tour->save();
+
+    $manager = drupal_container()->get('plugin.manager.tour.tip');
+    $stub = $manager->createInstance($form_state['values']['new'], array());
+
+    // If a form is available for this tip then redirect to a add page.
+    $stub_form = $stub->optionsForm();
+    if (isset($stub_form)) {
+      // Redirect to the appropriate page to add this new tip.
+      $path = 'admin/config/user-interface/tour/manage/' . $tour->id() . '/tip/add/' . $form_state['values']['new'];
+      $form_state['redirect'] = array($path, array('query' => array('weight' => $form_state['values']['weight'])));
+    }
+
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::submit().
+   */
+  public function submit(array $form, array &$form_state) {
+    // Filter out invalid characters and convert to an array.
+    preg_replace('/(\r\n?|\n)/', '\n', $form_state['values']['paths']);
+    $form_state['values']['paths'] = explode("\n", $form_state['values']['paths']);
+    $form_state['values']['paths'] = array_map('trim', $form_state['values']['paths']);
+    $form_state['values']['paths'] = array_filter($form_state['values']['paths']);
+
+    // Merge the form values in with the current configuration.
+    if (!empty($form_state['values']['tips'])) {
+      foreach ($form_state['values']['tips'] as $key => $values) {
+        $data = $form['#data'][$key];
+        $form_state['values']['tips'][$key] = array_merge($data, $values);
+      }
+    }
+    else {
+      $form_state['values']['tips'] = array();
+    }
+
+    $entity = parent::submit($form, $form_state);
+    $is_new = $entity->isNew();
+    $entity->save();
+
+    if ($is_new) {
+      drupal_set_message(t('The %tour tour has been created.', array('%tour' => $entity->label())));
+      $form_state['redirect'] = 'admin/config/user-interface/tour/manage/' . $entity->id() . '/edit';
+    }
+    else {
+      drupal_set_message(t('Updated the %tour tour.', array('%tour' => $entity->label())));
+      $form_state['redirect'] = 'admin/config/user-interface/tour';
+    }
+
+    return $entity;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::delete().
+   */
+  public function delete(array $form, array &$form_state) {
+    $entity = $this->getEntity($form_state);
+    $form_state['redirect'] = 'admin/config/user-interface/tour/manage/' . $entity->id() . '/delete';
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/TourListController.php b/core/modules/tour_ui/lib/Drupal/tour_ui/TourListController.php
new file mode 100644
index 0000000..b0a0842
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/TourListController.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * Contains \Drupal\tour_ui\TourListController.
+ */
+
+namespace Drupal\tour_ui;
+
+use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a listing of tours.
+ */
+class TourListController extends ConfigEntityListController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::buildHeader().
+   */
+  public function buildHeader() {
+    $row['label'] = t('Label');
+    $row['paths'] = t('Paths');
+    $row['tips'] = t('Number of tips');
+    $row['operations'] = t('Operations');
+    return $row;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::buildRow().
+   */
+  public function buildRow(EntityInterface $entity) {
+    $data['label'] = check_plain($entity->label());
+    // Include the paths this tour is used on.
+    $data['paths'] = implode('<br>', array_map(function($path) {
+      // If the path contains no wildcards, output it as a link.
+      if (strpos($path, '*') === FALSE) {
+        return l('/' . $path, $path);
+      }
+      return check_plain('/' . $path);
+    }, $entity->getPaths()));
+    // Count the number of tips.
+    $data['tips'] = count($entity->getTipList());
+    $data['operations']['data'] = $this->buildOperations($entity);
+    // Wrap the whole row so that the entity ID is used as a class.
+    return array(
+      'data' => $data,
+      'class' => array(
+        $entity->id(),
+      ),
+    );
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::getOperations().
+   */
+  public function getOperations(EntityInterface $entity) {
+    $operations = parent::getOperations($entity);
+    // Tours do not support being enabled or disabled.
+    unset($operations['enable'], $operations['disable']);
+    return $operations;
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/TourTipDeleteForm.php b/core/modules/tour_ui/lib/Drupal/tour_ui/TourTipDeleteForm.php
new file mode 100644
index 0000000..25bfbc8
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/TourTipDeleteForm.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\tour_ui\TourTipDeleteForm.
+ */
+
+namespace Drupal\tour_ui;
+
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Builds the form to delete a tour tip.
+ */
+class TourTipDeleteForm implements FormInterface {
+
+  /**
+   * Stores the tour entity being deleted.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $entity;
+
+  /**
+   * Stores the tour tip candidate for deletion.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface
+   */
+  protected $tip;
+
+  /**
+   * Creates a new instance of this form.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $tour
+   *   The tour being acted upon.
+   *
+   * @param $tip
+   *   The ID of the tip candidate for deletion.
+   *
+   * @return array
+   *   The built form array.
+   */
+  public function getForm(EntityInterface $tour, $tip) {
+    $this->entity = $tour;
+    $this->tip = $tour->getTip($tip);
+    return drupal_get_form($this);
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::getFormID().
+   */
+  public function getFormID() {
+    return 'tour_ui_tip_confirm_delete';
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::buildForm().
+   */
+  public function buildForm(array $form, array &$form_state) {
+    return confirm_form($form,
+      t('Are you sure you want to delete the %tour tour %tip tip?', array('%tour' => $this->entity->label(), '%tip' => $this->tip->getLabel())),
+      'admin/config/user-interface/tour/manage/' . $this->entity->getId() . '/edit',
+      t('This action cannot be undone.'),
+      t('Delete'),
+      t('Cancel')
+    );
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::validateForm().
+   */
+  public function validateForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * Implements \Drupal\Core\Form\FormInterface::submitForm().
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    // Rebuild the tips and remove the irrelevant one.
+    $candidate = $this->tip->getId();
+    $tips = array();
+    foreach ($this->entity->getTipList() as $tip_id) {
+      if ($tip_id == $candidate) {
+        continue;
+      }
+      $tip = $this->entity->getTip($tip_id);
+      $tips[$tip_id] = $tip->getExportProperties();
+    }
+    $this->entity->set('tips', $tips);
+    $this->entity->save();
+
+    $form_state['redirect'] = 'admin/config/user-interface/tour/manage/' . $this->entity->getId() . '/edit';
+    drupal_set_message(t('Deleted the %tour tour %tip tip.', array('%tour' => $this->entity->label(), '%tip' => $this->tip->getLabel())));
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/TourTipFormController.php b/core/modules/tour_ui/lib/Drupal/tour_ui/TourTipFormController.php
new file mode 100644
index 0000000..702daa3
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/TourTipFormController.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\tour_ui\TourTipFormController.
+ */
+
+namespace Drupal\tour_ui;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+
+/**
+ * Form controller for the tour tip plugin edit forms.
+ */
+class TourTipFormController extends EntityFormController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $entity) {
+    $form = parent::form($form, $form_state, $entity);
+    $form += $form_state['#tip']->optionsForm();
+    return $form;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::validate().
+   */
+  public function validate(array $form, array &$form_state) {
+    // Determine if one of our tips already exist.
+    $values = $form_state['values'];
+    $tips = $form_state['#tour']->getTipList();
+
+    // Also if there are no initial tips then we don't need to check.
+    if (empty($tips)) {
+      return;
+    }
+
+    if (in_array($values['id'], $tips) && !empty($form_state['#new'])) {
+      form_error($form['label'], t('A tip with the same identifier exists.'));
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::submit().
+   */
+  public function submit(array $form, array &$form_state) {
+    $tour = $form_state['#tour'];
+    $tip = $form_state['#tip'];
+    $values = $form_state['values'];
+    $exports = $tip->getExportProperties();
+
+    // Build a new tip.
+    $new_tip = $tip->getExportProperties();
+    foreach ($exports as $export_id => $export) {
+      if (!empty($values[$export_id])) {
+        $value = $values[$export_id];
+        $new_tip[$export_id] = is_array($value) ? array_filter($value) : $value;
+      }
+    }
+
+    // Rebuild the tips.
+    $new_tip_list = $tour->getTipList();
+    $new_tips = array();
+    if (!empty($new_tip_list)) {
+      foreach ($new_tip_list as $new_tip_id) {
+        $tip = $tour->getTip($new_tip_id);
+        $new_tips[$new_tip_id] = $tip->getExportProperties();
+      }
+    }
+
+    // Add our tip and save.
+    $new_tips[$new_tip['id']] = $new_tip;
+    $tour->set('tips', $new_tips);
+    $tour->save();
+
+    if (!empty($form_state['#new'])) {
+      drupal_set_message(t('The %tip tip has been created.', array('%tip' => $new_tip['label'])));
+    }
+    else {
+      drupal_set_message(t('Updated the %tip tip.', array('%tip' => $new_tip['label'])));
+    }
+
+    $form_state['redirect'] = 'admin/config/user-interface/tour/manage/' . $tour->getId() . '/edit';
+    return $tour;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::delete().
+   */
+  public function delete(array $form, array &$form_state) {
+    $entity = $this->getEntity($form_state);
+    $form_state['redirect'] = 'admin/config/user-interface/tour/manage/' . $entity->id() . '/tip/delete/' . $form_state['#tip']->getId();
+  }
+
+}
diff --git a/core/modules/tour_ui/lib/Drupal/tour_ui/TourUiBundle.php b/core/modules/tour_ui/lib/Drupal/tour_ui/TourUiBundle.php
new file mode 100644
index 0000000..719757b
--- /dev/null
+++ b/core/modules/tour_ui/lib/Drupal/tour_ui/TourUiBundle.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\tour_ui\TourUiBundle.
+ */
+
+namespace Drupal\tour_ui;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Tour UI dependency injection container.
+ */
+class TourUiBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('tour_ui.controller', 'Drupal\tour_ui\Routing\TourUIController')
+      ->addArgument(new Reference('plugin.manager.entity'));
+  }
+
+}
diff --git a/core/modules/tour_ui/tour_ui.info b/core/modules/tour_ui/tour_ui.info
new file mode 100644
index 0000000..e52f5ed
--- /dev/null
+++ b/core/modules/tour_ui/tour_ui.info
@@ -0,0 +1,6 @@
+name = Tour UI
+description = Provides a UI to manage guided tours.
+package = Core
+version = VERSION
+core = 8.x
+dependencies[] = tour
diff --git a/core/modules/tour_ui/tour_ui.module b/core/modules/tour_ui/tour_ui.module
new file mode 100644
index 0000000..6939410
--- /dev/null
+++ b/core/modules/tour_ui/tour_ui.module
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * @todo.
+ */
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Implements hook_entity_info().
+ */
+function tour_ui_entity_info(&$entity_info) {
+  $entity_info['tour']['list_controller_class'] = 'Drupal\tour_ui\TourListController';
+  $entity_info['tour']['form_controller_class']['default'] = 'Drupal\tour_ui\TourFormController';
+  $entity_info['tour']['form_controller_class']['tips'] = 'Drupal\tour_ui\TourTipFormController';
+  $entity_info['tour']['uri_callback'] = 'tour_ui_uri';
+}
+
+/**
+ * Implements hook_menu().
+ */
+function tour_ui_menu() {
+  $items['admin/config/user-interface/tour'] = array(
+    'title' => 'Tour',
+    'description' => 'Add and modify guided tours.',
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  $items['admin/config/user-interface/tour/add'] = array(
+    'title' => 'Add tour',
+    'type' => MENU_LOCAL_ACTION,
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  $items['admin/config/user-interface/tour/manage/%/edit'] = array(
+    'title' => 'Edit tour',
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  $items['admin/config/user-interface/tour/manage/%/delete'] = array(
+    'title' => 'Delete tour',
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  $items['admin/config/user-interface/tour/manage/%/tip/add/%'] = array(
+    'title' => 'Add tour tip',
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  $items['admin/config/user-interface/tour/manage/%/tip/edit/%'] = array(
+    'title' => 'Edit tour tip',
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  $items['admin/config/user-interface/tour/manage/%/tip/delete/%'] = array(
+    'title' => 'Edit tour tip',
+    'page callback' => 'NOT_USED',
+    'access callback' => TRUE,
+  );
+  return $items;
+}
+
+/**
+ * Callback for the entity URI.
+ */
+function tour_ui_uri(EntityInterface $entity) {
+  return array(
+    'path' => 'admin/config/user-interface/tour/manage/' . $entity->id,
+    'options' => array(
+      'entity_type' => $entity->entityType(),
+      'entity' => $entity,
+    ),
+  );
+}
+
+/**
+ * Callback for machine_name exists.
+ */
+function _tour_load($tour) {
+  return entity_load('tour', $tour);
+}
diff --git a/core/modules/tour_ui/tour_ui.routing.yml b/core/modules/tour_ui/tour_ui.routing.yml
new file mode 100644
index 0000000..fb9ccce
--- /dev/null
+++ b/core/modules/tour_ui/tour_ui.routing.yml
@@ -0,0 +1,42 @@
+tour_ui_listing:
+  pattern: '/admin/config/user-interface/tour'
+  defaults:
+    _controller: 'tour_ui.controller:listing'
+  requirements:
+    _access: 'TRUE'
+tour_ui_add:
+  pattern: '/admin/config/user-interface/tour/add'
+  defaults:
+    _controller: 'tour_ui.controller:add'
+  requirements:
+    _access: 'TRUE'
+tour_ui_edit:
+  pattern: '/admin/config/user-interface/tour/manage/{tour}/edit'
+  defaults:
+    _controller: 'tour_ui.controller:edit'
+  requirements:
+    _access: 'TRUE'
+tour_ui_delete:
+  pattern: '/admin/config/user-interface/tour/manage/{tour}/delete'
+  defaults:
+    _controller: '\Drupal\tour_ui\TourDeleteForm::getForm'
+  requirements:
+    _access: 'TRUE'
+tour_ui_tip_add:
+  pattern: '/admin/config/user-interface/tour/manage/{tour}/tip/add/{type}'
+  defaults:
+    _controller: 'tour_ui.controller:addTip'
+  requirements:
+    _access: 'TRUE'
+tour_ui_tip_edit:
+  pattern: '/admin/config/user-interface/tour/manage/{tour}/tip/edit/{tip}'
+  defaults:
+    _controller: 'tour_ui.controller:editTip'
+  requirements:
+    _access: 'TRUE'
+tour_ui_tip_delete:
+  pattern: '/admin/config/user-interface/tour/manage/{tour}/tip/delete/{tip}'
+  defaults:
+    _controller: '\Drupal\tour_ui\TourTipDeleteForm::getForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/views/views_ui/config/tour.tour.views-ui-en.yml b/core/modules/views/views_ui/config/tour.tour.views-ui-en.yml
index 77f66f3..0ab2fe0 100644
--- a/core/modules/views/views_ui/config/tour.tour.views-ui-en.yml
+++ b/core/modules/views/views_ui/config/tour.tour.views-ui-en.yml
@@ -10,6 +10,7 @@ tips:
     label: Active display
     body: This is the active display in the view. When there are multiple displays, one link for each display is shown and you can switch displays simply by clicking on the display link.
     weight: "2"
+    location: bottom
     attributes:
       data-class: views-display-top li.active
   views-ui-displays-en:
@@ -18,6 +19,7 @@ tips:
     label: Displays in this view
     body: A view can consist of multiple displays. A display is a way of outputting the results E.g. as a page or in a block. The available displays in your view are show here.
     weight: "1"
+    location: bottom
     attributes:
       data-id: views-display-top
   views-ui-fields-en:
@@ -26,6 +28,7 @@ tips:
     label: Fields
     body: This section shows the fields output for each result. Depending on the format selected for the view, you may not see anything listed here. If the format of your view uses fields, you can click on each field displayed to configure it.
     weight: "5"
+    location: bottom
     attributes:
       data-class: views-ui-display-tab-bucket.fields
   views-ui-filter-en:
@@ -34,6 +37,7 @@ tips:
     label: Filter your view
     body: This section displays the filters you have active in your view. A filter is used to limit the results available in the output. E.g. to only show content that was <em>published</em>, you would add a filter for <em>Published</em> and select <em>Yes</em>.
     weight: "6"
+    location: bottom
     attributes:
       data-class: views-ui-display-tab-bucket.filter-criteria
   views-ui-filter-operations-en:
@@ -42,6 +46,7 @@ tips:
     label: Filter actions
     body: Use this drop-button to add and re-arrange filters
     weight: "7"
+    location: bottom
     attributes:
       data-class: views-ui-display-tab-bucket.filter-criteria .dropbutton-widget
   views-ui-format-en:
@@ -50,6 +55,7 @@ tips:
     label: Output format
     body: Use this section to manage the format of the output results. You can choose different ways in which the matching results are output. E.g. Choose <em>Content</em> to output each item completely, using your configured display settings. Other options include <em>Fields</em> which allows you to output only specific fields on each matching result. Additional formats can be added by installing additional modules to <em>extend</em> Drupal's base functionality.
     weight: "4"
+    location: bottom
     attributes:
       data-class: views-ui-display-tab-bucket.format
   views-ui-preview-en:
@@ -58,6 +64,7 @@ tips:
     label: Preview
     body: Use this button to show a preview of the view output
     weight: "10"
+    location: bottom
     attributes:
       data-id: preview-submit
   views-ui-sorts-en:
@@ -66,6 +73,7 @@ tips:
     label: Sort Criteria
     body: This section shows the enabled sorting criteria for the view. Sorting criteria are used to control the order in which the results are output. Clicking on any of the active sorting criteria shown in this section enables you to configure it.
     weight: "8"
+    location: bottom
     attributes:
       data-class: views-ui-display-tab-bucket.sort-criteria
   views-ui-sorts-operations-en:
@@ -74,6 +82,7 @@ tips:
     label: Sort actions
     body: Use this drop-button to add and re-arrange the sorting criteria.
     weight: "9"
+    location: bottom
     attributes:
       data-class: views-ui-display-tab-bucket.sort-criteria .dropbutton-widget
   views-ui-view-admin-en:
