diff --git a/config/schema/gatsby_endpoints_test.entity_type.schema.yml b/config/schema/gatsby_endpoints_test.entity_type.schema.yml
new file mode 100644
index 0000000000000000000000000000000000000000..29a8f69459edf3ffe1a9a8303b87ba7d6841aea4
--- /dev/null
+++ b/config/schema/gatsby_endpoints_test.entity_type.schema.yml
@@ -0,0 +1,12 @@
+gatsby_endpoints.gatsby_endpoint_type.*:
+  type: config_entity
+  label: 'Gatsby Endpoint type config'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    label:
+      type: label
+      label: 'Label'
+    uuid:
+      type: string
diff --git a/gatsby_endpoints.info.yml b/gatsby_endpoints.info.yml
index 4ebe9c85bce93b0a6014891495e841c3ecad84bf..7816f8dc9e08c0c02b4bf2264c19fee48c315bfd 100644
--- a/gatsby_endpoints.info.yml
+++ b/gatsby_endpoints.info.yml
@@ -1,10 +1,9 @@
-name: 'Gatsby JSON:API Endpoints (experimental)'
+name: Gatsby Endpoints
 type: module
-description: 'Create multiple endpoints that can be used to source content into Gatsby sites (under active development and should be considered experimental).'
+description: 'Create and manage Gatsby Endpoints.'
+package: Gatsby
 core: 8.x
 core_version_requirement: ^8 || ^9
-package: 'Gatsby'
 dependencies:
-  - gatsby:gatsby_instantpreview
-  - drupal:jsonapi
-  - jsonapi_extras:jsonapi_extras
+  - drupal:text
+configure: entity.gatsby_endpoint_type.collection
diff --git a/gatsby_endpoints.links.action.yml b/gatsby_endpoints.links.action.yml
index 63e66fb87d3999115da15882852b125334e6597f..30c1b44b472b25a124fc69daa08f15826eb8416c 100644
--- a/gatsby_endpoints.links.action.yml
+++ b/gatsby_endpoints.links.action.yml
@@ -1,5 +1,11 @@
-entity.gatsby_endpoint.add_form:
-  route_name: gatsby_endpoints.gatsby_endpoint_plugin_types
+gatsby_endpoint.type_add:
+  route_name: entity.gatsby_endpoint_type.add_form
+  title: 'Add Gatsby Endpoint type'
+  appears_on:
+    - entity.gatsby_endpoint_type.collection
+
+gatsby_endpoint.add_page:
+  route_name: entity.gatsby_endpoint.add_page
   title: 'Add Gatsby Endpoint'
   appears_on:
-    - gatsby_endpoints.gatsby_endpoints_collection
+    - entity.gatsby_endpoint.collection
diff --git a/gatsby_endpoints.links.menu.yml b/gatsby_endpoints.links.menu.yml
new file mode 100644
index 0000000000000000000000000000000000000000..26df7e55531ac8870cbfa6c61f0411866a7b528e
--- /dev/null
+++ b/gatsby_endpoints.links.menu.yml
@@ -0,0 +1,5 @@
+entity.gatsby_endpoint_type.collection:
+  title: 'Gatsby Endpoint types'
+  parent: system.admin_structure
+  description: 'Create and manage your Gatsby Endpoint types.'
+  route_name: entity.gatsby_endpoint_type.collection
diff --git a/gatsby_endpoints.links.task.yml b/gatsby_endpoints.links.task.yml
index 7073672387e57cde690e41218eac6be441a07140..54bc262650300763644b5d03e25f834ced1467c3 100644
--- a/gatsby_endpoints.links.task.yml
+++ b/gatsby_endpoints.links.task.yml
@@ -1,12 +1,26 @@
-gatsby_endpoints.gatsby_endpoint_plugin_types:
-  title: 'Endpoint Types'
-  route_name: gatsby_endpoints.gatsby_endpoint_plugin_types
-  description: 'Manage list of Gatsby endpoint types.'
-  base_route: gatsby.gatsby_admin_form
-  weight: 30
-gatsby_endpoints.gatsby_endpoints_collection:
-  title: 'Endpoints'
-  route_name: gatsby_endpoints.gatsby_endpoints_collection
-  description: 'Manage list of Gatsby endpoints.'
-  base_route: gatsby.gatsby_admin_form
-  weight: 40
+entity.gatsby_endpoint.view:
+  title: Site Dashboard
+  route_name: entity.gatsby_endpoint.canonical
+  base_route: entity.gatsby_endpoint.canonical
+entity.gatsby_endpoint.edit_form:
+  title: Site Settings
+  route_name: entity.gatsby_endpoint.edit_form
+  base_route: entity.gatsby_endpoint.canonical
+entity.gatsby_endpoint.delete_form:
+  title: Delete
+  route_name: entity.gatsby_endpoint.delete_form
+  base_route: entity.gatsby_endpoint.canonical
+  weight: 10
+entity.gatsby_endpoint.collection:
+  title: Gatsby Endpoints
+  route_name: entity.gatsby_endpoint.collection
+  base_route: system.admin_content
+  weight: 10
+entity.gatsby_endpoint_type.edit_form:
+  title: Edit
+  route_name: entity.gatsby_endpoint_type.edit_form
+  base_route: entity.gatsby_endpoint_type.edit_form
+entity.gatsby_endpoint_type.collection:
+  title: List
+  route_name: entity.gatsby_endpoint_type.collection
+  base_route: entity.gatsby_endpoint_type.collection
diff --git a/gatsby_endpoints.module b/gatsby_endpoints.module
index 645bf06acb09dc6983aaf8c61846fa58b4d67775..327e77b0460a227bbb53a5f17b59a6fdc43a396e 100644
--- a/gatsby_endpoints.module
+++ b/gatsby_endpoints.module
@@ -2,15 +2,117 @@
 
 /**
  * @file
- * Contains gatsby_endpoints.module.
+ * Provides a gatsby endpoint entity type.
  */
 
-use Drupal\Core\Entity\ContentEntityInterface;
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Render\Element;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Entity\EntityFormInterface;
 use Drupal\Core\Url;
-use Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface;
-use Drupal\node\Entity\NodeType;
+use Drupal\Core\Link;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\gatsby_endpoints\GatsbyEndpointInterface;
+
+/**
+ * Implements hook_theme().
+ */
+function gatsby_endpoints_theme() {
+  return [
+    'gatsby_endpoint' => [
+      'render element' => 'elements',
+    ]
+  ];
+}
+
+/**
+ * Override the node add list if adding content to a Gatsby Endpoint.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - content: An array of content types
+ *   - types: An array or types created in template_preprocess_node_add_list()
+ */
+function gatsby_endpoints_preprocess_node_add_list(&$variables) {
+  $current_path = \Drupal::service('path.current')->getPath();
+  $path_args = explode('/', $current_path);
+
+  if (count($path_args) >= 3 && $path_args[1] === 'gatsby_endpoint' && is_numeric($path_args[2])) {
+    foreach ($variables['types'] as $key => $value) {
+      $type = $variables['content'][$key];
+      $url = Url::fromRoute('node.add', ['node_type' => $type->id()], [
+        'query' => [
+          'gatsby_endpoint' => $path_args[2],
+        ],
+      ]);
+      $variables['types'][$key]['add_link'] = Link::fromTextAndUrl($type->label(), $url)->toString();
+    }
+  }
+}
+
+/**
+ * Prepares variables for gatsby endpoint templates.
+ *
+ * Default template: gatsby-endpoint.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - elements: An associative array containing the gatsby endpoint information and any
+ *     fields attached to the entity.
+ *   - attributes: HTML attributes for the containing element.
+ */
+function template_preprocess_gatsby_endpoint(array &$variables) {
+  foreach (Element::children($variables['elements']) as $key) {
+    $variables['content'][$key] = $variables['elements'][$key];
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function gatsby_endpoints_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  if (!isset($form['field_gatsby_endpoint'])) {
+    return;
+  }
+
+  $gatsby_endpoint = \Drupal::request()->query->get('gatsby_endpoint');
+
+  if ($gatsby_endpoint) {
+    $form['field_gatsby_endpoint']['widget']['#default_value'] = intval($gatsby_endpoint);
+    $form['field_gatsby_endpoint']['widget']['#value'] = intval($gatsby_endpoint);
+    $form['field_gatsby_endpoint']['widget']['#type'] = 'hidden';
+  }
+
+  // If this form is editing an entity, then hide the gatsby endpoint field.
+  $form_object = $form_state->getFormObject();
+  if ($form_object instanceof EntityFormInterface && $form_object->getEntity()->id()) {
+    $form['field_gatsby_endpoint']['widget']['#value'] = $form['field_gatsby_endpoint']['widget']['#default_value'];
+    $form['field_gatsby_endpoint']['widget']['#type'] = 'hidden';
+  }
+}
+
+/**
+ * Implements hook_form_ID_alter().
+ */
+function gatsby_endpoints_form_gatsby_admin_form_alter(&$form, FormStateInterface $form_state) {
+  foreach ($form as $key => $form_item) {
+    if (!empty($form_item['#type'])) {
+      unset($form[$key]);
+    }
+  }
+
+  $form['endpoints_message'] = [
+    '#markup' => '<p><strong>' . t('This form is overridden by functionality
+    provided by the Gatsby Endpoints module.') . '</strong></p>',
+  ];
+
+  $url = Url::fromRoute('entity.gatsby_endpoint.collection');
+  $form['endpoints_link'] = [
+    '#type' => 'link',
+    '#url' => $url,
+    '#title' => t('Manage your Gatsby Endpoints'),
+  ];
+}
 
 /**
  * Implements hook_entity_insert().
@@ -89,139 +191,4 @@ function _gatsby_endpoints_process_entity(GatsbyEndpointInterface $endpoint, Con
 function _gatsby_endpoints_update() {
   $gatsbyEndpointTrigger = \Drupal::service('gatsby.gatsby_endpoint_trigger');
   $gatsbyEndpointTrigger->gatsbyUpdate();
-}
-
-/**
- * Implements hook_form_alter().
- */
-function gatsby_endpoints_form_alter(&$form, FormStateInterface $form_state, $form_id) {
-  if (preg_match('/node_(\w*)_edit_form/', $form_id, $matches) && $form_id !== 'node_type_edit_form') {
-
-    // Get Preview & iFrame settings.
-    $node = $form_state->getFormObject()->getEntity();
-    $node_type = NodeType::load($node->bundle());
-    $preview_settings = $node_type->getThirdPartySetting('gatsby', 'preview');
-    $target_settings = $node_type->getThirdPartySetting('gatsby', 'target');
-
-    $preview_url = \Drupal::service('gatsby.gatsby_endpoint_manager')->getPreviewUrlForEntity($node);
-
-    // Override node edit form.
-    if (!empty($preview_settings) && $preview_url) {
-      $alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/' . $node->id());
-
-      // If this is the front-page we don't want to pass the alias
-      // (as Gatsby will likely 404).
-      if (\Drupal::service('path.matcher')->isFrontPage()) {
-        $alias = '';
-      }
-
-      // Add Gatsby Preview button.
-      $form['actions']['gatsby_preview'] = [
-        '#type' => 'button',
-        '#weight' => 5,
-      ];
-      $form['actions']['gatsby_preview']['#value'] = 'Open Gatsby Preview';
-      $form['actions']['gatsby_preview']['#attributes']['class'] = ['gatsby-preview'];
-
-      // Implement "Open Preview" action.
-      $form['actions']['gatsby_preview']['#attached'] = [
-        'drupalSettings' => [
-          'gatsby_preview_url' => $preview_url,
-          'gatsby_path' => $alias,
-          'gatsby_preview_target' => !empty($target_settings) ? $target_settings : 'window',
-        ],
-        'library' => [
-          'gatsby/open_preview',
-        ],
-      ];
-    }
-  }
-}
-
-/**
- * Implements hook_form_ID_alter().
- */
-function gatsby_endpoints_form_gatsby_admin_form_alter(&$form, FormStateInterface $form_state) {
-  foreach ($form as $key => $form_item) {
-    if (!empty($form_item['#type'])) {
-      unset($form[$key]);
-    }
-  }
-
-  $form['endpoints_message'] = [
-    '#markup' => '<p><strong>' . t('This form is overridden by functionality
-    provided by the Gatsby Endpoints module.') . '</strong></p>',
-  ];
-
-  $url = Url::fromRoute('gatsby_endpoints.gatsby_endpoints_collection');
-  $form['endpoints_link'] = [
-    '#type' => 'link',
-    '#url' => $url,
-    '#title' => t('Manage your Gatsby Endpoints'),
-  ];
-}
-
-/**
- * Implements hook_node_view().
- */
-function gatsby_endpoints_node_view(array &$build, $entity, $display, $view_mode) {
-  // Override node view page with iframe to Gatsby site.
-  if (!empty($build['#node']) && $view_mode == 'full') {
-    $node = $build['#node'];
-    $iframe_settings = NodeType::load($entity->bundle())->getThirdPartySetting('gatsby', 'iframe');
-
-    // We are wanting to render preview for this content type.
-    if (!empty($iframe_settings)) {
-      $preview_url = \Drupal::service('gatsby.gatsby_endpoint_manager')->getPreviewUrlForEntity($node);
-
-      if (!$preview_url) {
-        return;
-      }
-
-      $alias = \Drupal::service('path_alias.manager')->getAliasByPath('/node/' . $node->id());
-
-      // If this is the front-page we don't want to pass the alias
-      // (as Gatsby will likely 404).
-      if (\Drupal::service('path.matcher')->isFrontPage()) {
-        $alias = '';
-      }
-
-      $gatsby_url = preg_replace('/\/$/', '', $preview_url) . $alias;
-
-      // Render an iframe to the preview URL.
-      $build['gatsby_preview'] = [
-        '#type' => 'inline_template',
-        '#template' => '<div class="gatsby-iframe-container"><iframe class="gatsby-iframe" src="{{ url }}" /></div>',
-        '#context' => [
-          'url' => $gatsby_url,
-        ],
-        '#attached' => [
-          'library' => [
-            'gatsby/iframe_preview',
-          ],
-        ],
-      ];
-    }
-  }
-}
-
-/**
- * Implements hook_cron().
- */
-function gatsby_endpoints_cron() {
-  $gatsbyEndpointManager = \Drupal::service('gatsby.gatsby_endpoint_manager');
-  $endpoints = $gatsbyEndpointManager->getEndpoints();
-  foreach ($endpoints as $endpoint) {
-    _gatsby_endpoints_trigger_cron_builds($endpoint);
-  }
-}
-
-/**
- * Triggers Endpoint build if the build_trigger is set to trigger on cron.
- */
-function _gatsby_endpoints_trigger_cron_builds(GatsbyEndpointInterface $endpoint) {
-  if ($endpoint->getBuildTrigger() === 'cron') {
-    $gatsbyEndpointTrigger = \Drupal::service('gatsby.gatsby_endpoint_trigger');
-    $gatsbyEndpointTrigger->triggerBuildUrls($endpoint);
-  }
-}
+}
\ No newline at end of file
diff --git a/gatsby_endpoints.permissions.yml b/gatsby_endpoints.permissions.yml
index ed4abb1e8f0bbc560f62668d688bbf0918294308..511ee08ba1414bb1e8de08f20fa0686152a43174 100644
--- a/gatsby_endpoints.permissions.yml
+++ b/gatsby_endpoints.permissions.yml
@@ -1,6 +1,16 @@
-access gatsby endpoints:
-  title: 'Access Gatsby endpoints.'
-
-manage gatsby endpoints:
-  title: 'Create and manage Gatsby endpoints.'
+administer gatsby endpoint types:
+  title: 'Administer gatsby endpoint types'
+  description: 'Maintain the types of gatsby endpoint entity.'
   restrict access: true
+access gatsby endpoint overview:
+  title: 'Access gatsby endpoint overview page'
+delete gatsby endpoint:
+  title: Delete gatsby endpoint
+create gatsby endpoint:
+  title: Create gatsby endpoint
+view gatsby endpoint:
+  title: View gatsby endpoint
+edit gatsby endpoint:
+  title: Edit gatsby endpoint
+create gatsby endpoint content:
+  title: Create gatsby endpoint content
diff --git a/gatsby_endpoints.routing.yml b/gatsby_endpoints.routing.yml
index fa6b52d477e056e88fef361f8294a6c6f3806488..429e5cc529805279cc331cb5d7d0f06948e383d0 100644
--- a/gatsby_endpoints.routing.yml
+++ b/gatsby_endpoints.routing.yml
@@ -1,35 +1,23 @@
-gatsby_endpoints.gatsby_endpoint_plugin_types:
-  path: '/admin/config/services/gatsby/endpoint/endpoint_types'
-  defaults:
-    _controller: '\Drupal\gatsby_endpoints\Controller\GatsbyEndpointLibraryController::listGatsbyEndpoints'
-    _title: 'Gatsby Endpoint types'
-  requirements:
-    _permission: 'manage gatsby endpoints'
-  options:
-    _admin_route: TRUE
-gatsby_endpoints.admin_add:
-  path: '/admin/config/services/gatsby/endpoint/add/{plugin_id}'
-  defaults:
-    _controller: '\Drupal\gatsby_endpoints\Controller\GatsbyEndpointAddController::gatsbyEndpointAddConfigureForm'
-    theme: null
-    _title: 'Add new Gatsby endpoint'
-  requirements:
-    _permission: 'manage gatsby endpoints'
-gatsby_endpoints.gatsby_endpoints_collection:
-  path: '/admin/config/services/gatsby/endpoint'
-  defaults:
-    _entity_list: 'gatsby_endpoint'
-    _title: 'Gatsby Endpoints'
-  requirements:
-    _permission: 'manage gatsby endpoints'
-  options:
-    _admin_route: TRUE
 gatsby_endpoints.gatsby_endpoints_controller_sync:
-  path: '/gatsby/{endpoint_id}'
+  path: '/gatsby/{gatsby_endpoint}'
   defaults:
     _controller: '\Drupal\gatsby_endpoints\Controller\GatsbyEndpointController::sync'
     _title: 'Gatsby Endpoint'
   options:
     _auth: ['basic_auth', 'cookie']
+    parameters:
+      gatsby_endpoint:
+        type: entity:gatsby_endpoint
   requirements:
-    _permission: 'access gatsby endpoints'
+    _permission: 'access gatsby endpoint overview'
+gatsby_endpoints.add_content:
+  path: '/gatsby_endpoint/{gatsby_endpoint}/content/add'
+  defaults:
+    _controller: '\Drupal\gatsby_endpoints\Controller\ContentController::addPage'
+    _title_callback: '\Drupal\gatsby_endpoints\Controller\ContentController::getTitle'
+  requirements:
+    _permission: 'create gatsby endpoint content'
+  options:
+    parameters:
+      gatsby_endpoint:
+        type: entity:gatsby_endpoint
diff --git a/gatsby_endpoints.services.yml b/gatsby_endpoints.services.yml
index ba57cfa8eeb0ace71d24ed140be1ab4dcc3f16c6..d7d2e17814cd7ea2abeba4c4d127ad26bd5e0db1 100644
--- a/gatsby_endpoints.services.yml
+++ b/gatsby_endpoints.services.yml
@@ -1,13 +1,10 @@
 services:
-  plugin.manager.gatsby_endpoint:
-    class: Drupal\gatsby_endpoints\Plugin\GatsbyEndpointManager
-    parent: default_plugin_manager
   gatsby.gatsby_endpoint_generator:
     class: Drupal\gatsby_endpoints\GatsbyEndpointGenerator
     arguments: ['@entity_type.manager', '@entity_field.manager']
   gatsby.gatsby_endpoint_trigger:
     class: Drupal\gatsby_endpoints\GatsbyEndpointTrigger
-    arguments: ['@http_client', '@entity_type.manager', '@logger.factory', '@entity.repository', '@gatsby.gatsby_instantpreview']
+    arguments: ['@http_client', '@entity_type.manager', '@logger.factory', '@entity.repository', '@gatsby.preview']
   gatsby.gatsby_endpoint_manager:
     class: Drupal\gatsby_endpoints\GatsbyEndpointManager
     arguments: ['@entity_type.manager', '@entity_field.manager']
diff --git a/src/Controller/ContentController.php b/src/Controller/ContentController.php
new file mode 100644
index 0000000000000000000000000000000000000000..4bc969b84dce87cfef8e065b07f0083aedf30f0c
--- /dev/null
+++ b/src/Controller/ContentController.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\gatsby_endpoints\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Render\RendererInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\gatsby_endpoints\GatsbyEndpointInterface;
+
+/**
+ * Returns responses for Gatsby Endpoint Content routes.
+ */
+class ContentController extends ControllerBase implements ContainerInjectionInterface {
+
+  /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a NodeController object.
+   *
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
+   */
+  public function __construct(RendererInterface $renderer) {
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('renderer')
+    );
+  }
+
+  /**
+   * Displays add content links for available content types.
+   *
+   * Redirects to node/add/[type] if only one content type is available.
+   *
+   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
+   *   A render array for a list of the node types that can be added; however,
+   *   if there is only one node type defined for the site, the function
+   *   will return a RedirectResponse to the node add page for that one node
+   *   type.
+   */
+  public function addPage(GatsbyEndpointInterface $gatsby_endpoint) {
+    $definition = $this->entityTypeManager()->getDefinition('node_type');
+    $build = [
+      '#theme' => 'node_add_list',
+      '#cache' => [
+        'tags' => $this->entityTypeManager()->getDefinition('node_type')->getListCacheTags(),
+      ],
+    ];
+
+    $content = [];
+
+    $types = $this->entityTypeManager()->getStorage('node_type')->loadMultiple();
+    uasort($types, [$definition->getClass(), 'sort']);
+    // Only use node types the user has access to.
+    foreach ($types as $type) {
+      $access = $this->entityTypeManager()->getAccessControlHandler('node')->createAccess($type->id(), NULL, [], TRUE);
+      if ($access->isAllowed() && $gatsby_endpoint->isBuildEntitySelected('node', $type->id())) {
+        $content[$type->id()] = $type;
+      }
+      $this->renderer->addCacheableDependency($build, $access);
+    }
+
+    $build['#content'] = $content;
+
+    return $build;
+  }
+
+  /**
+   * The _title_callback for the node.add route.
+   *
+   * @param \Drupal\gatsby_endpoint_test\GatsbyEndpointInterface $gatsby_endpoint
+   *   The current Gatsby Endpoint.
+   *
+   * @return string
+   *   The page title.
+   */
+  public function getTitle(GatsbyEndpointInterface $gatsby_endpoint) {
+    return $this->t('Add content to @name', ['@name' => $gatsby_endpoint->label()]);
+  }
+}
diff --git a/src/Controller/GatsbyEndpointController.php b/src/Controller/GatsbyEndpointController.php
index 11384f007517f8efa6b4b2536b347999cdf29b58..700a23c4c2ad790b122f64bc7c5b117930cf9d98 100644
--- a/src/Controller/GatsbyEndpointController.php
+++ b/src/Controller/GatsbyEndpointController.php
@@ -6,6 +6,7 @@ use Drupal\Core\Controller\ControllerBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
+use Drupal\gatsby_endpoints\GatsbyEndpointInterface;
 
 /**
  * Defines a class for serving Gatsby endpoint routes.
@@ -51,7 +52,7 @@ class GatsbyEndpointController extends ControllerBase {
    * @return Symfony\Component\HttpFoundation\JsonResponse
    *   Returns a JsonResponse with all of the content changes since last fetch.
    */
-  public function sync(string $endpoint_id, Request $request) {
+  public function sync(GatsbyEndpointInterface $gatsby_endpoint, Request $request) {
     $sync_data = [
       'timestamp' => time(),
     ];
@@ -59,16 +60,11 @@ class GatsbyEndpointController extends ControllerBase {
     $base_url = $request->getSchemeAndHttpHost();
     $path_prefix = $this->jsonApiConfig->get('path_prefix');
 
-    $endpoint = $this->entityTypeManager->getStorage('gatsby_endpoint')->load($endpoint_id);
-
-    if ($endpoint) {
-      $links = $this->gatsbyEndpointGenerator->getEndpointLinks($endpoint);
-      foreach ($links as $key => $link) {
-        $sync_data['links'][$key]['href'] = $base_url . '/' . $path_prefix . '/' . $link;
-      }
+    $links = $this->gatsbyEndpointGenerator->getEndpointLinks($gatsby_endpoint);
+    foreach ($links as $key => $link) {
+      $sync_data['links'][$key]['href'] = $base_url . '/' . $path_prefix . '/' . $link;
     }
 
     return new JsonResponse($sync_data);
   }
-
 }
diff --git a/src/Entity/GatsbyEndpoint.php b/src/Entity/GatsbyEndpoint.php
index 16e4250a3bc55a570c86e7a90fd583fccdb4646e..1db5536bdb405124ede9d9dfc96342fb2af91da9 100644
--- a/src/Entity/GatsbyEndpoint.php
+++ b/src/Entity/GatsbyEndpoint.php
@@ -2,152 +2,171 @@
 
 namespace Drupal\gatsby_endpoints\Entity;
 
-use Drupal\gatsby_endpoints\GatsbyEndpointPluginCollection;
-use Drupal\Core\Config\Entity\ConfigEntityBase;
-use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
+use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\gatsby_endpoints\GatsbyEndpointInterface;
+use Drupal\user\UserInterface;
 
 /**
- * Defines the Gatsby Endpoint entity.
+ * Defines the gatsby endpoint entity class.
  *
- * @ConfigEntityType(
+ * @ContentEntityType(
  *   id = "gatsby_endpoint",
- *   label = @Translation("Gatsby endpoint"),
+ *   label = @Translation("Gatsby Endpoint"),
+ *   label_collection = @Translation("Gatsby Endpoints"),
+ *   bundle_label = @Translation("Gatsby Endpoint type"),
  *   handlers = {
  *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
  *     "list_builder" = "Drupal\gatsby_endpoints\GatsbyEndpointListBuilder",
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "access" = "Drupal\gatsby_endpoints\GatsbyEndpointAccessControlHandler",
  *     "form" = {
- *       "default" = "Drupal\gatsby_endpoints\Form\GatsbyEndpointForm",
+ *       "add" = "Drupal\gatsby_endpoints\Form\GatsbyEndpointForm",
  *       "edit" = "Drupal\gatsby_endpoints\Form\GatsbyEndpointForm",
- *       "delete" = "Drupal\gatsby_endpoints\Form\GatsbyEndpointDeleteForm"
+ *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm"
  *     },
  *     "route_provider" = {
- *       "html" = "Drupal\gatsby_endpoints\GatsbyEndpointHtmlRouteProvider",
- *     },
+ *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     }
  *   },
- *   config_prefix = "gatsby_endpoint",
- *   admin_permission = "manage gatsby endpoints",
+ *   base_table = "gatsby_endpoint",
+ *   admin_permission = "administer gatsby endpoint types",
  *   entity_keys = {
  *     "id" = "id",
- *     "label" = "label",
+ *     "bundle" = "bundle",
+ *     "label" = "title",
  *     "uuid" = "uuid"
  *   },
  *   links = {
- *     "canonical" = "/admin/config/services/gatsby/endpoint/{gatsby_endpoint}",
- *     "add-form" = "/admin/config/services/gatsby/endpoint/add",
- *     "edit-form" = "/admin/config/services/gatsby/endpoint/{gatsby_endpoint}/edit",
- *     "delete-form" = "/admin/config/services/gatsby/endpoint/{gatsby_endpoint}/delete"
- *   },
- *   config_export = {
- *     "id",
- *     "label",
- *     "weight",
- *     "provider",
- *     "plugin",
- *     "settings",
- *     "build_entity_types",
- *     "included_entity_types",
- *     "preview_urls",
- *     "build_urls",
- *     "build_trigger"
+ *     "add-form" = "/admin/content/gatsby-endpoint/add/{gatsby_endpoint_type}",
+ *     "add-page" = "/admin/content/gatsby-endpoint/add",
+ *     "canonical" = "/gatsby_endpoint/{gatsby_endpoint}",
+ *     "edit-form" = "/admin/content/gatsby-endpoint/{gatsby_endpoint}/edit",
+ *     "delete-form" = "/admin/content/gatsby-endpoint/{gatsby_endpoint}/delete",
+ *     "collection" = "/admin/content/gatsby-endpoint"
  *   },
+ *   bundle_entity_type = "gatsby_endpoint_type",
+ *   field_ui_base_route = "entity.gatsby_endpoint_type.edit_form"
  * )
  */
-class GatsbyEndpoint extends ConfigEntityBase implements GatsbyEndpointInterface, EntityWithPluginCollectionInterface {
+class GatsbyEndpoint extends ContentEntityBase implements GatsbyEndpointInterface {
 
-  /**
-   * The Gatsby Endpoint ID.
-   *
-   * @var string
-   */
-  protected $id;
+  use EntityChangedTrait;
 
   /**
-   * The plugin collection that holds the endpoint plugin for this entity.
+   * {@inheritdoc}
    *
-   * @var \Drupal\gatsby_endpoints\GatsbyEndpointPluginCollection
+   * When a new gatsby endpoint entity is created, set the uid entity reference
+   * to the current user as the creator of the entity.
    */
-  protected $pluginCollection;
+  public static function preCreate(EntityStorageInterface $storage_controller, array &$values) {
+    parent::preCreate($storage_controller, $values);
+    $values += ['uid' => \Drupal::currentUser()->id()];
+  }
 
   /**
-   * The plugin instance ID.
-   *
-   * @var string
+   * {@inheritdoc}
    */
-  protected $plugin;
+  public function getTitle() {
+    return $this->get('title')->value;
+  }
 
   /**
-   * The Gatsby endpoint label.
-   *
-   * @var string
+   * {@inheritdoc}
    */
-  protected $label;
+  public function setTitle($title) {
+    $this->set('title', $title);
+    return $this;
+  }
 
   /**
-   * The Gatsby endpoint build entity types.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  protected $build_entity_types;
-
+  public function getBuildTrigger() {
+    return $this->get('build_trigger')->value;
+  }
 
   /**
-   * The Gatsby endpoint preview Urls.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  protected $preview_urls;
+  public function setBuildTrigger($build_trigger) {
+    $this->set('build_trigger', $build_trigger);
+    return $this;
+  }
 
   /**
-   * The Gatsby endpoint build Urls.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  protected $build_urls;
+  public function getCreatedTime() {
+    return $this->get('created')->value;
+  }
 
   /**
-   * The Gatsby endpoint build trigger.
-   *
-   * @var string
+   * {@inheritdoc}
    */
-  protected $build_trigger;
+  public function setCreatedTime($timestamp) {
+    $this->set('created', $timestamp);
+    return $this;
+  }
 
   /**
-   * The weight of the endpoint.
-   *
-   * @var string
+   * {@inheritdoc}
    */
-  protected $weight;
+  public function getOwner() {
+    return $this->get('uid')->entity;
+  }
 
   /**
-   * The plugin instance settings.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  protected $settings = [];
+  public function getOwnerId() {
+    return $this->get('uid')->target_id;
+  }
 
   /**
    * {@inheritdoc}
    */
-  public function getEntityTypes($key) {
-    if ($key === 'build') {
-      return $this->build_entity_types;
-    }
-
-    return [];
+  public function setOwnerId($uid) {
+    $this->set('uid', $uid);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getBuildEntityTypes() {
-    return $this->build_entity_types;
+  public function setOwner(UserInterface $account) {
+    $this->set('uid', $account->id());
+    return $this;
   }
 
+  // /**
+  //  * {@inheritdoc}
+  //  */
+  // public function getEntityTypes($key)
+  // {
+  //   if ($key === 'build') {
+  //     return $this->build_entity_types;
+  //   }
+
+  //   return [];
+  // }
+
+  // /**
+  //  * {@inheritdoc}
+  //  */
+  // public function getBuildEntityTypes()
+  // {
+  //   return $this->build_entity_types;
+  // }
+
   /**
    * {@inheritDoc}
    */
   public function getBuildEntityType($entity_type) {
-    foreach ($this->build_entity_types as $build_type) {
+    foreach ($this->getBuildEntities() as $build_type) {
       if (!empty($build_type['entity_type']) && $build_type['entity_type'] === $entity_type) {
         return $build_type;
       }
@@ -156,12 +175,13 @@ class GatsbyEndpoint extends ConfigEntityBase implements GatsbyEndpointInterface
     return FALSE;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function setBuildEntityTypes($build_entity_types) {
-    $this->build_entity_types = $build_entity_types;
-  }
+  // /**
+  //  * {@inheritdoc}
+  //  */
+  // public function setBuildEntityTypes($build_entity_types)
+  // {
+  //   $this->build_entity_types = $build_entity_types;
+  // }
 
   /**
    * {@inheritDoc}
@@ -185,10 +205,9 @@ class GatsbyEndpoint extends ConfigEntityBase implements GatsbyEndpointInterface
    */
   public function getUrls($key) {
     if ($key === 'preview') {
-      return $this->preview_urls;
-    }
-    elseif ($key === 'build') {
-      return $this->build_urls;
+      return $this->getPreviewUrls();
+    } elseif ($key === 'build') {
+      return $this->getBuildUrls();
     }
 
     return [];
@@ -198,87 +217,267 @@ class GatsbyEndpoint extends ConfigEntityBase implements GatsbyEndpointInterface
    * {@inheritdoc}
    */
   public function getPreviewUrls() {
-    return $this->preview_urls;
+    return $this->get('preview_urls')->value;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setPreviewUrls($preview_urls) {
-    $this->preview_urls = $preview_urls;
+    $this->set('preview_urls', $preview_urls);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getBuildUrls() {
-    return $this->build_urls;
+    return $this->get('build_urls')->value;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setBuildUrls($build_urls) {
-    $this->build_urls = $build_urls;
+    $this->set('build_urls', $build_urls);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getWeight() {
-    return $this->weight;
+  public function getContentSyncUrl() {
+    return $this->get('contentsync_url')->value;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getBuildTrigger() {
-    return $this->build_trigger;
+  public function setContentSyncUrl($contentsync_url) {
+    $this->set('contentsync_url', $contentsync_url);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function setBuildTrigger($build_trigger) {
-    $this->build_trigger = $build_trigger;
-  }
-
-  /**
-   * Encapsulates creation of the Gatsby endpoint's LazyPluginCollection.
-   *
-   * @return \Drupal\Component\Plugin\LazyPluginCollection
-   *   The Gatsby endpoint's plugin collection.
-   */
-  protected function getPluginCollection() {
-    if (!$this->pluginCollection) {
-      $this->pluginCollection = new GatsbyEndpointPluginCollection(
-        \Drupal::service('plugin.manager.gatsby_endpoint'), $this->plugin, $this->get('settings'), $this->id()
-      );
-    }
-    return $this->pluginCollection;
+  public function getBuildEntities() {
+    return $this->get('build_entities')->getValue();
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getPlugin() {
-    return $this->getPluginCollection()->get($this->plugin);
+  public function setBuildEntities($build_entities) {
+    $this->set('build_entities', $build_entities);
+    return $this;
   }
 
   /**
-   * {@inheritdoc}
+   * {@inheritDoc}
    */
-  public function getPluginCollections() {
-    return [
-      'settings' => $this->getPluginCollection(),
-    ];
+  public function isBuildEntitySelected($entity_type, $entity_bundle) {
+    $build_entities = $this->getBuildEntities();
+
+    foreach ($build_entities as $build_entity) {
+      if ($build_entity['entity_type'] == $entity_type) {
+        foreach ($build_entity['entity_bundles'] as $bundle_name) {
+          if ($bundle_name === $entity_bundle) {
+            return TRUE;
+          }
+        }
+      }
+    }
+    return FALSE;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getSettings() {
-    return $this->settings;
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields['title'] = BaseFieldDefinition::create('string')
+    ->setLabel(t('Title'))
+    ->setDescription(t('The title of the Gatsby Endpoint.'))
+    ->setRequired(TRUE)
+      ->setSetting('max_length', 255)
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -10,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'hidden',
+        'type' => 'string',
+        'weight' => -10,
+      ])
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['build_trigger'] = BaseFieldDefinition::create('list_string')
+    ->setLabel(t('Build Trigger'))
+    ->setDescription(t(
+      'Select how Gatsby build URLs should be
+        triggered. Incremental builds require a
+        <a href="@gatsby-link">Gatsby Cloud Account</a>. This setting has no
+        effect if there are no build URLs entered above.',
+      [
+        '@gatsby-link' => 'https://gatsbyjs.com',
+      ]
+    ))
+      ->setRequired(TRUE)
+      ->setDisplayOptions('form', [
+        'type' => 'options_select',
+        'weight' => -9,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -9,
+      ])
+      ->setDisplayConfigurable('view', TRUE)
+      ->setSettings([
+        'allowed_values' => [
+          'incremental' => t("Trigger builds incrementally (requires Gatsby Cloud)"),
+          'cron' => t("Trigger builds on cron runs"),
+          'manual' => t("Trigger builds manually with the built in drush command"),
+        ],
+      ])
+      ->setDefaultValue([['value' => 'incremental']])
+      ->setInternal(TRUE);
+
+    $fields['public_url'] = BaseFieldDefinition::create('uri')
+    ->setLabel(t('Public URL'))
+    ->setDescription(t('The Public URL for this Gatsby website'))
+    ->setRequired(FALSE)
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -8,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'uri_link',
+        'weight' => -8,
+      ])
+      ->setDisplayConfigurable('view', TRUE)
+      ->setInternal(TRUE);
+
+    $fields['preview_urls'] = BaseFieldDefinition::create('uri')
+    ->setLabel(t('Preview Hooks'))
+    ->setDescription(t('Enter any Gatsby Preview URLs to trigger for this endpoint'))
+    ->setRequired(FALSE)
+      ->setCardinality(-1)
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -7,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -7,
+      ])
+      ->setDisplayConfigurable('view', TRUE)
+      ->setInternal(TRUE);
+
+    $fields['build_urls'] = BaseFieldDefinition::create('uri')
+    ->setLabel(t('Build Hooks'))
+    ->setDescription(t('Enter any Gatsby Build URLs to trigger for this endpoint'))
+    ->setRequired(FALSE)
+      ->setCardinality(-1)
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -6,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -6,
+      ])
+      ->setDisplayConfigurable('view', TRUE)
+      ->setInternal(TRUE);
+
+    $fields['contentsync_url'] = BaseFieldDefinition::create('uri')
+    ->setLabel(t('Content Sync URL'))
+    ->setDescription(t('The Content Sync Preview URL provided by Gatsby Cloud'))
+    ->setRequired(FALSE)
+      ->setDisplayOptions('form', [
+        'type' => 'string_textfield',
+        'weight' => -5,
+      ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'string',
+        'weight' => -5,
+      ])
+      ->setDisplayConfigurable('view', TRUE)
+      ->setInternal(TRUE);
+
+    $fields['build_entities'] = BaseFieldDefinition::create('map')
+    ->setLabel(t('Build Entities Data'))
+    ->setInternal(TRUE);
+
+    $fields['description'] = BaseFieldDefinition::create('text_long')
+    ->setLabel(t('Description'))
+    ->setDescription(t('A description of the Gatsby Endpoint.'))
+    ->setDisplayOptions('form', [
+      'type' => 'text_textarea',
+      'weight' => 10,
+    ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'type' => 'text_default',
+        'label' => 'above',
+        'weight' => 10,
+      ])
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['uid'] = BaseFieldDefinition::create('entity_reference')
+    ->setLabel(t('Author'))
+    ->setDescription(t('The user ID of the Gatsby Endpoint author.'))
+    ->setSetting('target_type', 'user')
+    ->setDisplayOptions('form', [
+      'type' => 'entity_reference_autocomplete',
+      'settings' => [
+        'match_operator' => 'CONTAINS',
+        'size' => 60,
+        'placeholder' => '',
+      ],
+      'weight' => 15,
+    ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('view', [
+        'label' => 'above',
+        'type' => 'author',
+        'weight' => 15,
+      ])
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['created'] = BaseFieldDefinition::create('created')
+    ->setLabel(t('Authored on'))
+    ->setDescription(t('The time that the Gatsby Endpoint was created.'))
+    ->setDisplayOptions('view', [
+      'label' => 'above',
+      'type' => 'timestamp',
+      'weight' => 20,
+    ])
+      ->setDisplayConfigurable('form', TRUE)
+      ->setDisplayOptions('form', [
+        'type' => 'datetime_timestamp',
+        'weight' => 20,
+      ])
+      ->setDisplayConfigurable('view', TRUE);
+
+    $fields['changed'] = BaseFieldDefinition::create('changed')
+    ->setLabel(t('Changed'))
+    ->setDescription(t('The time that the Gatsby Endpoint was last edited.'));
+
+    return $fields;
   }
 
 }
diff --git a/src/Entity/GatsbyEndpointType.php b/src/Entity/GatsbyEndpointType.php
new file mode 100644
index 0000000000000000000000000000000000000000..fda8ded60d19010d4e91f62f6fff0a17b82c4a21
--- /dev/null
+++ b/src/Entity/GatsbyEndpointType.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Drupal\gatsby_endpoints\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
+
+/**
+ * Defines the Gatsby Endpoint type configuration entity.
+ *
+ * @ConfigEntityType(
+ *   id = "gatsby_endpoint_type",
+ *   label = @Translation("Gatsby Endpoint type"),
+ *   handlers = {
+ *     "form" = {
+ *       "add" = "Drupal\gatsby_endpoints\Form\GatsbyEndpointTypeForm",
+ *       "edit" = "Drupal\gatsby_endpoints\Form\GatsbyEndpointTypeForm",
+ *       "delete" = "Drupal\Core\Entity\EntityDeleteForm",
+ *     },
+ *     "list_builder" = "Drupal\gatsby_endpoints\GatsbyEndpointTypeListBuilder",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     }
+ *   },
+ *   admin_permission = "administer gatsby endpoint types",
+ *   bundle_of = "gatsby_endpoint",
+ *   config_prefix = "gatsby_endpoint_type",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   },
+ *   links = {
+ *     "add-form" = "/admin/structure/gatsby_endpoint_types/add",
+ *     "edit-form" = "/admin/structure/gatsby_endpoint_types/manage/{gatsby_endpoint_type}",
+ *     "delete-form" = "/admin/structure/gatsby_endpoint_types/manage/{gatsby_endpoint_type}/delete",
+ *     "collection" = "/admin/structure/gatsby_endpoint_types"
+ *   },
+ *   config_export = {
+ *     "id",
+ *     "label",
+ *     "uuid",
+ *   }
+ * )
+ */
+class GatsbyEndpointType extends ConfigEntityBundleBase {
+
+  /**
+   * The machine name of this gatsby endpoint type.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The human-readable name of the gatsby endpoint type.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * The build entitites.
+   *
+   * @var array
+   */
+  protected $build_entities;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBuildEntities() {
+    if ($this->get('build_entities')) {
+      return $this->get('build_entities')->getValue();
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setBuildEntities($build_entities) {
+    $this->set('build_entities', $build_entities);
+    return $this;
+  }
+}
diff --git a/src/Form/GatsbyEndpointForm.php b/src/Form/GatsbyEndpointForm.php
index 335470bb37a33620ddef80aad71639011b5f3f84..54a5fab745f5edfcd3ceb22d85e36e9d873a84b5 100644
--- a/src/Form/GatsbyEndpointForm.php
+++ b/src/Form/GatsbyEndpointForm.php
@@ -2,524 +2,50 @@
 
 namespace Drupal\gatsby_endpoints\Form;
 
-use Drupal\gatsby_endpoints\Plugin\GatsbyEndpointInterface;
-use Drupal\Core\Entity\ContentEntityTypeInterface;
-use Drupal\Core\Entity\EntityForm;
-use Drupal\Core\Entity\EntityTypeBundleInfo;
+use Drupal\Core\Entity\ContentEntityForm;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Form\SubformState;
-use Drupal\Core\Plugin\PluginFormFactoryInterface;
-use Drupal\Core\Plugin\PluginWithFormsInterface;
-use Drupal\Core\Url;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
- * Provides a form for editing Gatsby Endpoint entities.
+ * Form controller for the gatsby endpoint entity edit forms.
  */
-class GatsbyEndpointForm extends EntityForm {
-
-  /**
-   * The plugin form manager.
-   *
-   * @var \Drupal\Core\Plugin\PluginFormFactoryInterface
-   */
-  protected $pluginFormFactory;
-
-  /**
-   * The entity bundle service.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeBundleInfo
-   */
-  protected $entityTypeBundleInfo;
-
-  /**
-   * GatsbyEndpointForm constructor.
-   *
-   * @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
-   *   The plugin form manager.
-   * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_type_bundle_info
-   *   The entity type bundle info service.
-   */
-  public function __construct(PluginFormFactoryInterface $plugin_form_manager,
-      EntityTypeBundleInfo $entity_type_bundle_info) {
-
-    $this->pluginFormFactory = $plugin_form_manager;
-    $this->entityTypeBundleInfo = $entity_type_bundle_info;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('plugin_form.factory'),
-      $container->get('entity_type.bundle.info')
-    );
-  }
+class GatsbyEndpointForm extends ContentEntityForm {
 
   /**
    * {@inheritdoc}
    */
   public function form(array $form, FormStateInterface $form_state) {
     $form = parent::form($form, $form_state);
-
-    /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */
-    $entity = $this->entity;
-
-    $form['label'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Label'),
-      '#maxlength' => 255,
-      '#default_value' => $entity->label(),
-      '#description' => $this->t("Label for the Gatsby endpoint."),
-      '#required' => TRUE,
-    ];
-
-    $form['id'] = [
-      '#type' => 'machine_name',
-      '#default_value' => $entity->id(),
-      '#machine_name' => [
-        'exists' => '\Drupal\gatsby_endpoints\Entity\GatsbyEndpoint::load',
-      ],
-      '#disabled' => !$entity->isNew(),
-    ];
-
-    // Render the Preview URLs AJAX enabled fieldset.
-    $preview_description = $this->t("Enter any Gatsby Live Preview URLs to trigger for this endpoint.");
-    $this->addAjaxFieldset($form, $form_state, 'preview', $preview_description);
-
-    // Render the Build URLs AJAX enabled fieldset.
-    $build_description = $this->t("Enter any Gatsby Build Hooks or Build URLs to trigger for this endpoint.");
-    $this->addAjaxFieldset($form, $form_state, 'build', $build_description);
-
-    $build_entities_description = $this->t("Select which entities should trigger builds/previews for this endpoint.");
-    $this->addEntityAjaxFieldset($form, $form_state, 'build', $build_entities_description);
-
-    $form['build_trigger'] = [
-      '#type' => 'select',
-      '#options' => [
-        'incremental' => $this->t("Trigger builds incrementally (requires Gatsby Cloud)"),
-        'cron' => $this->t("Trigger builds on cron runs"),
-        'manual' => $this->t("Trigger builds manually with the built in drush command"),
-      ],
-      '#title' => $this->t("Build Trigger"),
-      '#default_value' => $entity->getBuildTrigger() ? $entity->getBuildTrigger() : 'incremental',
-      '#description' => $this->t('Select how Gatsby build URLs should be
-        triggered. Incremental builds require a
-        <a href="@gatsby-link">Gatsby Cloud Account</a>. This setting has no
-        effect if there are no build URLs entered above.',
-        [
-          '@gatsby-link' => 'https://gatsbyjs.com',
-        ]
-      ),
-      '#required' => TRUE,
-    ];
-
-    $form['weight'] = [
-      '#type' => 'number',
-      '#title' => $this->t('Weight'),
-      '#max' => 100,
-      '#min' => -100,
-      '#size' => 3,
-      '#default_value' => $entity->getWeight() ? $entity->getWeight() : 0,
-      '#description' => $this->t("Set the weight, lighter endpoints will be rendered first."),
-      '#required' => TRUE,
-    ];
-
     $form['#tree'] = TRUE;
-    $form['settings'] = [];
-    $subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
-    $form['settings'] = $this->getPluginForm($entity->getPlugin())
-      ->buildConfigurationForm($form['settings'], $subform_state);
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Remove empty Build and Preview URLs.
-    $this->removeEmptyUrls($form_state, 'preview');
-    $this->removeEmptyUrls($form_state, 'build');
 
-    parent::submitForm($form, $form_state);
-
-    /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */
-    $entity = $this->entity;
-
-    $sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state);
-
-    // Call the plugin submit handler.
-    $this->getPluginForm($entity->getPlugin())
-      ->submitConfigurationForm($form, $sub_form_state);
-
-    $entity->save();
-
-    $this->messenger()->addStatus($this->t('The Gatsby endpoint configuration has been saved.'));
-    $form_state->setRedirectUrl(Url::fromRoute('gatsby_endpoints.gatsby_endpoints_collection'));
+    return $form;
   }
 
   /**
    * {@inheritdoc}
    */
-  protected function getPluginForm(GatsbyEndpointInterface $gatsbyEndpoint) {
-    if ($gatsbyEndpoint instanceof PluginWithFormsInterface) {
-      return $this->pluginFormFactory->createInstance($gatsbyEndpoint, 'configure');
-    }
-    return $gatsbyEndpoint;
-  }
-
-  /**
-   * Adds a Form Element to an AJAX Fieldset.
-   */
-  public function addFormElement(array &$form, FormStateInterface $form_state, $key, $element) {
-    $cnt = $form_state->get($key . '_' . $element . '_cnt');
-    $form_state->set($key . '_' . $element . '_cnt', $cnt + 1);
-    $form_state->setRebuild();
-  }
-
-  /**
-   * Removes a Form Element from an AJAX Fieldset.
-   */
-  public function removeFormElement(array &$form, FormStateInterface $form_state, $key, $element) {
-    $cnt = $form_state->get($key . '_' . $element . '_cnt');
-    if ($cnt > 1) {
-      $form_state->set($key . '_' . $element . '_cnt', $cnt - 1);
-    }
-    $form_state->setRebuild();
-  }
-
-  /**
-   * Adds a Build Url.
-   */
-  public function addBuildUrl(array &$form, FormStateInterface $form_state) {
-    $this->addFormElement($form, $form_state, 'build', 'url');
-  }
-
-  /**
-   * Removes a Build Url.
-   */
-  public function removeBuildUrl(array &$form, FormStateInterface $form_state) {
-    $this->removeFormElement($form, $form_state, 'build', 'url');
-  }
-
-  /**
-   * Ajax callback for Build Urls that returns the correct fieldset.
-   */
-  public function buildUrlCallback(array &$form, FormStateInterface $form_state) {
-    return $form['build_urls'];
-  }
-
-  /**
-   * Adds a Preview Url.
-   */
-  public function addPreviewUrl(array &$form, FormStateInterface $form_state) {
-    $this->addFormElement($form, $form_state, 'preview', 'url');
-  }
-
-  /**
-   * Removes a Preview Url.
-   */
-  public function removePreviewUrl(array &$form, FormStateInterface $form_state) {
-    $this->removeFormElement($form, $form_state, 'preview', 'url');
-  }
-
-  /**
-   * Ajax callback for Preview Urls that returns the correct fieldset.
-   */
-  public function previewUrlCallback(array &$form, FormStateInterface $form_state) {
-    return $form['preview_urls'];
-  }
-
-  /**
-   * Renders AJAX fieldsets for Build and Preview URLs.
-   */
-  public function addAjaxFieldset(array &$form, FormStateInterface $form_state, $key, $description) {
-    $label = ucfirst($key);
-    $url_cnt = $form_state->get($key . '_url_cnt');
-
+  public function save(array $form, FormStateInterface $form_state) {
     /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */
-    $entity = $this->entity;
-
-    // If there is no count yet, then this is the first time rendering.
-    if ($url_cnt === NULL) {
-      $urls = $entity->getUrls($key);
-      $url_cnt = !empty($urls) && !empty($urls[$key . '_url']) ? count($urls[$key . '_url']) : 0;
-      if ($url_cnt === 0) {
-        $url_cnt = 1;
-      }
-      $form_state->set($key . '_url_cnt', $url_cnt);
-    }
+    $entity = $this->getEntity();
 
-    $form[$key . '_urls'] = [
-      '#type' => 'fieldset',
-      '#title' => $this->t("Gatsby @label URLs", ['@label' => $label]),
-      '#prefix' => '<div id="' . $key . '-urls-fieldset-wrapper">',
-      '#suffix' => '</div>',
-    ];
-    $form[$key . '_urls']['description'] = [
-      '#markup' => $description,
-      '#weight' => -100,
-    ];
-
-    for ($i = 0; $i < $url_cnt; $i++) {
-      $form[$key . '_urls'][$key . '_url'][$i] = [
-        '#type' => 'url',
-        '#title' => $this->t('Gatsby @label URL #@cnt', [
-          '@label' => $label,
-          '@cnt' => $i + 1,
-        ]),
-        '#maxlength' => 255,
-        '#default_value' => !empty($urls[$key . '_url'][$i]) ? $urls[$key . '_url'][$i] : "",
-        '#required' => FALSE,
-      ];
-    }
-    $form[$key . '_urls']['actions'] = [
-      '#type' => 'actions',
-    ];
-    $form[$key . '_urls']['actions']['add_' . $key . '_url'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Add Additional @label URL', ['@label' => $label]),
-      '#submit' => ['::add' . $label . 'Url'],
-      '#ajax' => [
-        'callback' => '::' . $key . 'UrlCallback',
-        'wrapper' => $key . '-urls-fieldset-wrapper',
-      ],
-    ];
-    if ($url_cnt > 1) {
-      $form[$key . '_urls']['actions']['remove_' . $key . '_url'] = [
-        '#type' => 'submit',
-        '#value' => $this->t('Remove @label URL', ['@label' => $label]),
-        '#submit' => ['::remove' . $label . 'Url'],
-        '#ajax' => [
-          'callback' => '::' . $key . 'UrlCallback',
-          'wrapper' => $key . '-urls-fieldset-wrapper',
-        ],
-      ];
+    if (isset($form_state->getValues()['build_entity_types'])) {
+      $entity->setBuildEntities($form_state->getValues()['build_entity_types']);
     }
-  }
-
-  /**
-   * Removes empty URLs from preview and build fieldsets.
-   */
-  public function removeEmptyUrls(FormStateInterface $form_state, $key) {
-    $values = $form_state->getValue($key . '_urls');
-    $values[$key . '_url'] = array_values(array_filter($values[$key . '_url']));
-    $form_state->setValue($key . '_urls', $values);
-  }
 
-  /**
-   * Adds a Build Entity Fieldset.
-   */
-  public function addBuildEntityFieldset(array &$form, FormStateInterface $form_state) {
-    $this->addFormElement($form, $form_state, 'build', 'entity');
-  }
-
-  /**
-   * Removes a Build Entity Fieldset Url.
-   */
-  public function removeBuildEntityFieldset(array &$form, FormStateInterface $form_state) {
-    $this->removeFormElement($form, $form_state, 'build', 'entity');
-  }
-
-  /**
-   * Ajax callback for Build Entity Fieldsets that returns the correct fieldset.
-   */
-  public function buildEntityCallback(array &$form, FormStateInterface $form_state) {
-    return $form['build_entity_types'];
-  }
-
-  /**
-   * Ajax callback for build entities that returns the correct bundles.
-   */
-  public function buildEntityBundleCallback(array &$form, FormStateInterface $form_state) {
-    // Determine what element triggered this callback.
-    $triggering_element = $form_state->getTriggeringElement();
-    $wrapper_elements = explode('-', $triggering_element['#ajax']['wrapper']);
-    $element_id = intval(array_pop($wrapper_elements));
+    $result = $entity->save();
+    $link = $entity->toLink($this->t('View'))->toRenderable();
 
-    $form['build_entity_types'][$element_id]['#open'] = TRUE;
-    return $form['build_entity_types'][$element_id];
-  }
-
-  /**
-   * Renders AJAX fieldsets for Entity selection.
-   */
-  public function addEntityAjaxFieldset(array &$form, FormStateInterface $form_state, $key, $description) {
-    $label = ucfirst($key);
-    $entity_cnt = $form_state->get($key . '_entity_cnt');
-
-    /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */
-    $entity = $this->entity;
-
-    // If there is no count yet, then this is the first time rendering.
-    if ($entity_cnt === NULL) {
-      $entities = $entity->getEntityTypes($key);
-      $entity_cnt = !empty($entities) ? count($entities) - 1 : 0;
-      if ($entity_cnt === 0) {
-        $entity_cnt = 1;
-      }
-      $form_state->set($key . '_entity_cnt', $entity_cnt);
-    }
-
-    $form[$key . '_entity_types'] = [
-      '#type' => 'fieldset',
-      '#title' => $this->t("Gatsby @label Entities", ['@label' => $label]),
-      '#prefix' => '<div id="' . $key . '-entity-fieldset-wrapper">',
-      '#suffix' => '</div>',
-    ];
-    $form[$key . '_entity_types']['description'] = [
-      '#markup' => $description,
-      '#weight' => -100,
-    ];
-
-    for ($i = 0; $i < $entity_cnt; $i++) {
-      // Get the default entity type.
-      $entity_type = !empty($entities[$i]['entity_type']) ?
-        $entities[$i]['entity_type'] : "";
-
-      // Check if there was an entity type set in the form state.
-      $form_values = $form_state->getValues();
-      if (!empty($form_values[$key . '_entity_types'][$i]['entity_type'])) {
-        $entity_type = $form_values[$key . '_entity_types'][$i]['entity_type'];
-      }
-
-      $content_entity_types = $this->getContentEntityTypes();
-
-      $form[$key . '_entity_types'][$i] = [
-        '#type' => 'details',
-        '#title' => $this->t("Gatsby @label Entity #@cnt @entity", [
-          '@label' => $label,
-          '@cnt' => $i + 1,
-          '@entity' => !empty($entity_type) ? "(" . $content_entity_types[$entity_type] . ")" : "",
-        ]),
-        '#prefix' => '<div id="' . $key . '-entity-fieldset-' . $i . '">',
-        '#suffix' => '</div>',
-        '#open' => empty($entity_type) ? TRUE : FALSE,
-      ];
-
-      $entity_types = ['' => $this->t("-- Select Entity Type --")] + $content_entity_types;
-
-      $form[$key . '_entity_types'][$i]['entity_type'] = [
-        '#type' => 'select',
-        '#options' => $entity_types,
-        '#title' => $this->t("Entity Type"),
-        '#default_value' => $entity_type,
-      ];
-
-      $form[$key . '_entity_types'][$i]['entity_type']['#ajax'] = [
-        'callback' => '::' . $key . 'EntityBundleCallback',
-        'wrapper' => $key . '-entity-fieldset-' . $i,
-        'event' => 'change',
-        'progress' => [
-          'type' => 'throbber',
-          'message' => $this->t('Loading bundles...'),
-        ],
-      ];
-
-      if ($entity_type) {
-        $entity_bundles = !empty($entities[$i]['entity_bundles']) ?
-          $entities[$i]['entity_bundles'] : [];
-        $include_entities = !empty($entities[$i]['include_entities']) ?
-          $entities[$i]['include_entities'] : [];
-
-        $form[$key . '_entity_types'][$i]['entity_bundles'] = [
-          '#type' => 'checkboxes',
-          '#options' => $this->getContentEntityBundles($entity_type),
-          '#title' => $this->t("Entity Bundle(s)"),
-          '#default_value' => $entity_bundles,
-        ];
-
-        if ($entity_type == 'node') {
-          $build_published = !empty($entities[$i]['build_published']) ?
-            $entities[$i]['build_published'] : FALSE;
-          $form[$key . '_entity_types'][$i]['build_published'] = [
-            '#type' => 'checkbox',
-            '#title' => $this->t('Only trigger builds for published content'),
-            '#description' => $this->t('Depending on your content workflow, you may only
-              want builds to be triggered for published content. By checking this box
-              only published content will trigger a build.'),
-            '#default_value' => $build_published,
-            '#weight' => 3,
-          ];
-        }
-
-        $included_entities_description = $this->t("Select which entities should not trigger builds/previews but should be
-          included in builds/previews. This is commonly used for entities such as media, files, or paragraphs where you don't
-          want these items to trigger a new build, but you do want to make sure they are sent to Gatsby if they are
-          attached to an entity that triggers a build.");
-        $form[$key . '_entity_types'][$i]['include_entities'] = [
-          '#type' => 'checkboxes',
-          '#options' => $content_entity_types,
-          '#title' => $this->t("Include Entities"),
-          '#description' => $included_entities_description,
-          '#default_value' => $include_entities,
-          '#weight' => 4,
-        ];
-      }
-    }
-    $form[$key . '_entity_types']['actions'] = [
-      '#type' => 'actions',
-    ];
-    $form[$key . '_entity_types']['actions']['add_' . $key . '_entity'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Add Additional @label Entity', ['@label' => $label]),
-      '#submit' => ['::add' . $label . 'EntityFieldset'],
-      '#ajax' => [
-        'callback' => '::' . $key . 'EntityCallback',
-        'wrapper' => $key . '-entity-fieldset-wrapper',
-      ],
-    ];
-    if ($entity_cnt > 1) {
-      $form[$key . '_entity_types']['actions']['remove_' . $key . '_entity'] = [
-        '#type' => 'submit',
-        '#value' => $this->t('Remove @label Entity', ['@label' => $label]),
-        '#submit' => ['::remove' . $label . 'EntityFieldset'],
-        '#ajax' => [
-          'callback' => '::' . $key . 'EntityCallback',
-          'wrapper' => $key . '-entity-fieldset-wrapper',
-        ],
-      ];
-    }
-  }
-
-  /**
-   * Gets a list of all the defined content entities in the system.
-   *
-   * @return array
-   *   An array of content entities definitions.
-   */
-  private function getContentEntityTypes() {
-    $content_entity_types = [];
-    $allEntityTypes = $this->entityTypeManager->getDefinitions();
-
-    foreach ($allEntityTypes as $entity_type_id => $entity_type) {
-      // Add all content entity types but not the gatsby log entity provided
-      // by the gatsby_fastbuilds module (if it exists).
-      if ($entity_type instanceof ContentEntityTypeInterface &&
-        $entity_type_id !== 'gatsby_log_entity') {
-
-        $content_entity_types[$entity_type_id] = $entity_type->getLabel();
-      }
-    }
-    return $content_entity_types;
-  }
-
-  /**
-   * Gets a list of all the defined bundles for a content entity type.
-   *
-   * @return array
-   *   An array of bundles for a specific content entity type.
-   */
-  private function getContentEntityBundles($entity_type) {
-    $bundle_definitions = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
+    $message_arguments = ['%label' => $this->entity->label()];
+    $logger_arguments = $message_arguments + ['link' => render($link)];
 
-    $bundles = [];
-    foreach ($bundle_definitions as $bundle => $bundle_definition) {
-      $bundles[$bundle] = $bundle_definition['label'];
+    if ($result == SAVED_NEW) {
+      $this->messenger()->addStatus($this->t('New Gatsby Endpoint %label has been created.', $message_arguments));
+      $this->logger('gatsby_endpoints')->notice('Created new Gatsby Endpoint %label', $logger_arguments);
+    } else {
+      $this->messenger()->addStatus($this->t('The Gatsby Endpoint %label has been updated.', $message_arguments));
+      $this->logger('gatsby_endpoints')->notice('Updated new Gatsby Endpoint %label.', $logger_arguments);
     }
 
-    return $bundles;
+    $form_state->setRedirect('entity.gatsby_endpoint.canonical', ['gatsby_endpoint' => $entity->id()]);
   }
 
 }
diff --git a/src/Form/GatsbyEndpointTypeForm.php b/src/Form/GatsbyEndpointTypeForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..924069aa20b3d487df1563aa4e62cfe2ed28d36b
--- /dev/null
+++ b/src/Form/GatsbyEndpointTypeForm.php
@@ -0,0 +1,328 @@
+<?php
+
+namespace Drupal\gatsby_endpoints\Form;
+
+use Drupal\Core\Entity\BundleEntityFormBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Form handler for gatsby endpoint type forms.
+ */
+class GatsbyEndpointTypeForm extends BundleEntityFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    $entity_type = $this->entity;
+    if ($this->operation == 'add') {
+      $form['#title'] = $this->t('Add gatsby endpoint type');
+    } else {
+      $form['#title'] = $this->t(
+        'Edit %label gatsby endpoint type',
+        ['%label' => $entity_type->label()]
+      );
+    }
+
+    $form['label'] = [
+      '#title' => $this->t('Label'),
+      '#type' => 'textfield',
+      '#default_value' => $entity_type->label(),
+      '#description' => $this->t('The human-readable name of this gatsby endpoint type.'),
+      '#required' => TRUE,
+      '#size' => 30,
+    ];
+
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#default_value' => $entity_type->id(),
+      '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
+      '#machine_name' => [
+        'exists' => ['Drupal\gatsby_endpoints\Entity\GatsbyEndpointType', 'load'],
+        'source' => ['label'],
+      ],
+      '#description' => $this->t('A unique machine-readable name for this gatsby endpoint type. It must only contain lowercase letters, numbers, and underscores.'),
+    ];
+
+    $this->addEntityAjaxFieldset($form, $form_state);
+    $form['#tree'] = TRUE;
+
+    return $this->protectBundleIdElement($form);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = $this->t('Save gatsby endpoint type');
+    $actions['delete']['#value'] = $this->t('Delete gatsby endpoint type');
+    return $actions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointType $entity_type */
+    $entity_type = $this->entity;
+
+    if (isset($form_state->getValues()['build_entity_types'])) {
+      $entity_type->setBuildEntities($form_state->getValues()['build_entity_types']);
+    }
+
+    $entity_type->set('id', trim($entity_type->id()));
+    $entity_type->set('label', trim($entity_type->label()));
+
+    $status = $entity_type->save();
+
+    $t_args = ['%name' => $entity_type->label()];
+    if ($status == SAVED_UPDATED) {
+      $message = $this->t('The gatsby endpoint type %name has been updated.', $t_args);
+    } elseif ($status == SAVED_NEW) {
+      $message = $this->t('The gatsby endpoint type %name has been added.', $t_args);
+    }
+    $this->messenger()->addStatus($message);
+
+    $form_state->setRedirectUrl($entity_type->toUrl('collection'));
+  }
+
+  /**
+   * Adds a Form Element to an AJAX Fieldset.
+   */
+  public function addFormElement(array &$form, FormStateInterface $form_state, $key, $element) {
+    $cnt = $form_state->get($key . '_' . $element . '_cnt');
+    $form_state->set($key . '_' . $element . '_cnt', $cnt + 1);
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Removes a Form Element from an AJAX Fieldset.
+   */
+  public function removeFormElement(array &$form, FormStateInterface $form_state, $key, $element) {
+    $cnt = $form_state->get($key . '_' . $element . '_cnt');
+    if ($cnt > 1) {
+      $form_state->set($key . '_' . $element . '_cnt', $cnt - 1);
+    }
+    $form_state->setRebuild();
+  }
+
+  /**
+   * Adds a Build Entity Fieldset.
+   */
+  public function addBuildEntityFieldset(array &$form, FormStateInterface $form_state) {
+    $this->addFormElement($form, $form_state, 'build', 'entity');
+  }
+
+  /**
+   * Removes a Build Entity Fieldset Url.
+   */
+  public function removeBuildEntityFieldset(array &$form, FormStateInterface $form_state) {
+    $this->removeFormElement($form, $form_state, 'build', 'entity');
+  }
+
+  /**
+   * Ajax callback for Build Entity Fieldsets that returns the correct fieldset.
+   */
+  public function buildEntityCallback(array &$form, FormStateInterface $form_state) {
+    return $form['build_entity_types'];
+  }
+
+  /**
+   * Ajax callback for build entities that returns the correct bundles.
+   */
+  public function buildEntityBundleCallback(array &$form, FormStateInterface $form_state) {
+    // Determine what element triggered this callback.
+    $triggering_element = $form_state->getTriggeringElement();
+    $wrapper_elements = explode('-', $triggering_element['#ajax']['wrapper']);
+    $element_id = intval(array_pop($wrapper_elements));
+
+    $form['build_entity_types'][$element_id]['#open'] = TRUE;
+    return $form['build_entity_types'][$element_id];
+  }
+
+  /**
+   * Renders AJAX fieldsets for Entity selection.
+   */
+  public function addEntityAjaxFieldset(array &$form, FormStateInterface $form_state) {
+    $entity_cnt = $form_state->get('build_entity_cnt');
+
+    /** @var \Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface $entity */
+    $entity = $this->entity;
+
+    // If there is no count yet, then this is the first time rendering.
+    if ($entity_cnt === NULL) {
+      $entities = $entity->getBuildEntities();
+      $entity_cnt = !empty($entities) ? count($entities) - 1 : 0;
+      if ($entity_cnt === 0) {
+        $entity_cnt = 1;
+      }
+      $form_state->set('build_entity_cnt', $entity_cnt);
+    }
+
+    $form['build_entity_types'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t("Gatsby Build Entities"),
+      '#prefix' => '<div id="build-entity-fieldset-wrapper">',
+      '#suffix' => '</div>',
+    ];
+    $form['build_entity_types']['description'] = [
+      '#markup' => $this->t("Select which entities should trigger builds/previews for this endpoint."),
+      '#weight' => -100,
+    ];
+
+    for ($i = 0; $i < $entity_cnt; $i++) {
+      // Get the default entity type.
+      $entity_type = !empty($entities[$i]['entity_type']) ?
+        $entities[$i]['entity_type'] : "";
+
+      // Check if there was an entity type set in the form state.
+      $form_values = $form_state->getValues();
+      if (!empty($form_values['build_entity_types'][$i]['entity_type'])) {
+        $entity_type = $form_values['build_entity_types'][$i]['entity_type'];
+      }
+
+      $content_entity_types = $this->getContentEntityTypes();
+
+      $form['build_entity_types'][$i] = [
+        '#type' => 'details',
+        '#title' => $this->t("Gatsby Build Entity #@cnt @entity", [
+          '@cnt' => $i + 1,
+          '@entity' => !empty($entity_type) ? "(" . $content_entity_types[$entity_type] . ")" : "",
+        ]),
+        '#prefix' => '<div id="build-entity-fieldset-' . $i . '">',
+        '#suffix' => '</div>',
+        '#open' => empty($entity_type) ? TRUE : FALSE,
+      ];
+
+      $entity_types = ['' => $this->t("-- Select Entity Type --")] + $content_entity_types;
+
+      $form['build_entity_types'][$i]['entity_type'] = [
+        '#type' => 'select',
+        '#options' => $entity_types,
+        '#title' => $this->t("Entity Type"),
+        '#default_value' => $entity_type,
+      ];
+
+      $form['build_entity_types'][$i]['entity_type']['#ajax'] = [
+        'callback' => '::buildEntityBundleCallback',
+        'wrapper' => 'build-entity-fieldset-' . $i,
+        'event' => 'change',
+        'progress' => [
+          'type' => 'throbber',
+          'message' => $this->t('Loading bundles...'),
+        ],
+      ];
+
+      if ($entity_type) {
+        $entity_bundles = !empty($entities[$i]['entity_bundles']) ?
+          $entities[$i]['entity_bundles'] : [];
+        $include_entities = !empty($entities[$i]['include_entities']) ?
+          $entities[$i]['include_entities'] : [];
+
+        $form['build_entity_types'][$i]['entity_bundles'] = [
+          '#type' => 'checkboxes',
+          '#options' => $this->getContentEntityBundles($entity_type),
+          '#title' => $this->t("Entity Bundle(s)"),
+          '#default_value' => $entity_bundles,
+        ];
+
+        if ($entity_type == 'node') {
+          $build_published = !empty($entities[$i]['build_published']) ?
+            $entities[$i]['build_published'] : FALSE;
+          $form['build_entity_types'][$i]['build_published'] = [
+            '#type' => 'checkbox',
+            '#title' => $this->t('Only trigger builds for published content'),
+            '#description' => $this->t('Depending on your content workflow, you may only
+              want builds to be triggered for published content. By checking this box
+              only published content will trigger a build.'),
+            '#default_value' => $build_published,
+            '#weight' => 3,
+          ];
+        }
+
+        $included_entities_description = $this->t("Select which entities should not trigger builds/previews but should be
+          included in builds/previews. This is commonly used for entities such as media, files, or paragraphs where you don't
+          want these items to trigger a new build, but you do want to make sure they are sent to Gatsby if they are
+          attached to an entity that triggers a build.");
+        $form['build_entity_types'][$i]['include_entities'] = [
+          '#type' => 'checkboxes',
+          '#options' => $content_entity_types,
+          '#title' => $this->t("Include Entities"),
+          '#description' => $included_entities_description,
+          '#default_value' => $include_entities,
+          '#weight' => 4,
+        ];
+      }
+    }
+    $form['build_entity_types']['actions'] = [
+      '#type' => 'actions',
+    ];
+    $form['build_entity_types']['actions']['add_build_entity'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Add Additional Build Entity'),
+      '#submit' => ['::addBuildEntityFieldset'],
+      '#ajax' => [
+        'callback' => '::buildEntityCallback',
+        'wrapper' => 'build-entity-fieldset-wrapper',
+      ],
+    ];
+    if ($entity_cnt > 1) {
+      $form['build_entity_types']['actions']['remove_build_entity'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Remove Build Entity'),
+        '#submit' => ['::removeBuildEntityFieldset'],
+        '#ajax' => [
+          'callback' => '::buildEntityCallback',
+          'wrapper' => 'build-entity-fieldset-wrapper',
+        ],
+      ];
+    }
+  }
+
+  /**
+   * Gets a list of all the defined content entities in the system.
+   *
+   * @return array
+   *   An array of content entities definitions.
+   */
+  private function getContentEntityTypes() {
+    $content_entity_types = [];
+    $allEntityTypes = $this->entityTypeManager->getDefinitions();
+
+    foreach ($allEntityTypes as $entity_type_id => $entity_type) {
+      // Add all content entity types but not the gatsby log entity provided
+      // by the gatsby_fastbuilds module or the gatsby_endpoint entity provided
+      // by this module.
+      if (
+        $entity_type instanceof ContentEntityTypeInterface &&
+        !in_array($entity_type_id, ['gatsby_log_entity', 'gatsby_endpoint'])
+      ) {
+
+        $content_entity_types[$entity_type_id] = $entity_type->getLabel();
+      }
+    }
+    return $content_entity_types;
+  }
+
+  /**
+   * Gets a list of all the defined bundles for a content entity type.
+   *
+   * @return array
+   *   An array of bundles for a specific content entity type.
+   */
+  private function getContentEntityBundles($entity_type) {
+    $bundle_definitions = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
+
+    $bundles = [];
+    foreach ($bundle_definitions as $bundle => $bundle_definition) {
+      $bundles[$bundle] = $bundle_definition['label'];
+    }
+
+    return $bundles;
+  }
+}
diff --git a/src/GatsbyEndpointAccessControlHandler.php b/src/GatsbyEndpointAccessControlHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..275c2463441710d7974cdacf255bec6e8c744433
--- /dev/null
+++ b/src/GatsbyEndpointAccessControlHandler.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\gatsby_endpoints;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityAccessControlHandler;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines the access control handler for the gatsby endpoint entity type.
+ */
+class GatsbyEndpointAccessControlHandler extends EntityAccessControlHandler {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
+
+    switch ($operation) {
+      case 'view':
+        return AccessResult::allowedIfHasPermission($account, 'view gatsby endpoint');
+
+      case 'update':
+        return AccessResult::allowedIfHasPermissions($account, ['edit gatsby endpoint', 'administer gatsby endpoint'], 'OR');
+
+      case 'delete':
+        return AccessResult::allowedIfHasPermissions($account, ['delete gatsby endpoint', 'administer gatsby endpoint'], 'OR');
+
+      default:
+        // No opinion.
+        return AccessResult::neutral();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
+    return AccessResult::allowedIfHasPermissions($account, ['create gatsby endpoint', 'administer gatsby endpoint'], 'OR');
+  }
+}
diff --git a/src/GatsbyEndpointGenerator.php b/src/GatsbyEndpointGenerator.php
index 9ba54384dbd5da04f2bb316beffd70119e89d854..029c3999592a2c13dd6e48be4c603e579e0ae437 100644
--- a/src/GatsbyEndpointGenerator.php
+++ b/src/GatsbyEndpointGenerator.php
@@ -5,7 +5,7 @@ namespace Drupal\gatsby_endpoints;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\field\Entity\FieldConfig;
-use Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface;
+use Drupal\gatsby_endpoints\GatsbyEndpointInterface;
 
 /**
  * Class GatsbyEndpointGenerator.
@@ -31,8 +31,10 @@ class GatsbyEndpointGenerator {
   /**
    * Constructs a new GatsbyEndpointManager object.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager,
-      EntityFieldManagerInterface $entity_field_manager) {
+  public function __construct(
+    EntityTypeManagerInterface $entity_type_manager,
+    EntityFieldManagerInterface $entity_field_manager
+  ) {
     $this->entityTypeManager = $entity_type_manager;
     $this->entityFieldManager = $entity_field_manager;
   }
@@ -41,7 +43,7 @@ class GatsbyEndpointGenerator {
    * Generates JSON:API links for a specific Gatsby Endpoint.
    */
   public function getEndpointLinks(GatsbyEndpointInterface $endpoint) {
-    $build_types = $endpoint->getBuildEntityTypes();
+    $build_types = $endpoint->getBuildEntities();
     $links = [];
 
     foreach ($build_types as $build_type) {
@@ -53,13 +55,15 @@ class GatsbyEndpointGenerator {
 
       // Check if this has bundles.
       foreach ($build_type['entity_bundles'] as $bundle_id => $bundle_label) {
-        if ($bundle_label === $bundle_id) {
+        if ($bundle_label === $bundle_id && $this->hasGatsbyEndpointReferenceField($build_type['entity_type'], $bundle_id)) {
           $params = $this->getUrlParameters($build_type['entity_type'], $bundle_id, $endpoint, $include_types);
           $entity_key = $build_type['entity_type'] . '--' . $bundle_id;
           $links[$entity_key] = $build_type['entity_type'] . '/' . $bundle_id . $params;
         }
       }
 
+      // Add the link to get the info for the Gatsby Endpoint entity.
+      $links['gatsby_endpoint'] = 'gatsby_endpoint/' . $endpoint->bundle() . '?filter[drupal_internal__id]=' . $endpoint->id();
     }
 
     return $links;
@@ -69,8 +73,12 @@ class GatsbyEndpointGenerator {
    * Gets the correct JSON:API url parameters string.
    */
   private function getUrlParameters($entity_type, $bundle, GatsbyEndpointInterface $endpoint, $include_types) {
-    $url_params = $this->loadUrlFiltersAndIncludes($entity_type,
-      $bundle, $endpoint, $include_types);
+    $url_params = $this->loadUrlFiltersAndIncludes(
+      $entity_type,
+      $bundle,
+      $endpoint,
+      $include_types
+    );
 
     $param_string = '';
 
@@ -86,6 +94,21 @@ class GatsbyEndpointGenerator {
     return $param_string ? '?' . $param_string : $param_string;
   }
 
+  /**
+   * Determines if this entity type and bundle have a Gatsby Reference field.
+   */
+  private function hasGatsbyEndpointReferenceField($entity_type, $bundle) {
+    $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
+
+    foreach ($definitions as $field) {
+      $field_type = $field->getType();
+      if ($field_type == 'gatsby_endpoint_reference') {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
   /**
    * Gets the includes and filter parameters for a JSON:API url.
    */
@@ -112,8 +135,7 @@ class GatsbyEndpointGenerator {
       $field_type = $field->getType();
       if ($field_type == 'gatsby_endpoint_reference') {
         $params['filter'] = 'filter[' . $field_name . '.meta.drupal_internal__target_id]=' . $endpoint->id();
-      }
-      elseif (in_array($field_type, $core_reference_fields)) {
+      } elseif (in_array($field_type, $core_reference_fields)) {
 
         // Check if this field references an included entity type.
         if (in_array($field_type, $include_types)) {
@@ -122,8 +144,7 @@ class GatsbyEndpointGenerator {
           }
           $params['include'][] = $field_name;
         }
-      }
-      elseif (in_array($field_type, [
+      } elseif (in_array($field_type, [
         'entity_reference',
         'entity_reference_revisions',
       ])) {
@@ -142,11 +163,13 @@ class GatsbyEndpointGenerator {
           $handler_settings = $field->getSetting('handler_settings');
           if (!empty($handler_settings['target_bundles'])) {
             foreach ($handler_settings['target_bundles'] as $target_bundle) {
-              $reference_params = $this->loadUrlFiltersAndIncludes($reference_type[1],
+              $reference_params = $this->loadUrlFiltersAndIncludes(
+                $reference_type[1],
                 $target_bundle,
                 $endpoint,
                 $include_types,
-                $field_name);
+                $field_name
+              );
 
               if (!empty($reference_params['include'])) {
                 $params['include'] = array_merge($params['include'], $reference_params['include']);
@@ -159,5 +182,4 @@ class GatsbyEndpointGenerator {
 
     return $params;
   }
-
 }
diff --git a/src/GatsbyEndpointInterface.php b/src/GatsbyEndpointInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..511934a78e2a76466d01b90c7b361805dbc28883
--- /dev/null
+++ b/src/GatsbyEndpointInterface.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace Drupal\gatsby_endpoints;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\user\EntityOwnerInterface;
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Provides an interface defining a gatsby endpoint entity type.
+ */
+interface GatsbyEndpointInterface extends ContentEntityInterface, EntityOwnerInterface, EntityChangedInterface {
+
+  /**
+   * Gets the Gatsby Endpoint title.
+   *
+   * @return string
+   *   Title of the Gatsby Endpoint.
+   */
+  public function getTitle();
+
+  /**
+   * Sets the Gatsby Endpoint title.
+   *
+   * @param string $title
+   *   The Gatsby Endpoint title.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called Gatsby Endpoint entity.
+   */
+  public function setTitle($title);
+
+  /**
+   * Gets the build trigger for the Gatsby Endpoint.
+   *
+   * @return string
+   *   Build trigger of the Gatsby Endpoint.
+   */
+  public function getBuildTrigger();
+
+  /**
+   * Sets the build trigger for the Gatsby Endpoint.
+   *
+   * @param string $build_trigger
+   *   The Build Trigger option for the Gatsby Endpoint.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called Gatsby Endpoint entity.
+   */
+  public function setBuildTrigger($build_trigger);
+
+  /**
+   * Gets the gatsby endpoint creation timestamp.
+   *
+   * @return int
+   *   Creation timestamp of the gatsby endpoint.
+   */
+  public function getCreatedTime();
+
+  /**
+   * Sets the gatsby endpoint creation timestamp.
+   *
+   * @param int $timestamp
+   *   The gatsby endpoint creation timestamp.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setCreatedTime($timestamp);
+
+  /**
+   * Gets the owner of the user that owns Gatsby Endpoint.
+   *
+   * @return \Drupal\user\UserInterface
+   *   The user entity.
+   */
+  public function getOwner();
+
+  /**
+   * Sets the owner of the user that will own the Gatsby Endpoint.
+   *
+   * @param \Drupal\user\UserInterface $account
+   *   The user entity.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setOwner(UserInterface $account);
+
+  /**
+   * Gets the owner id of the user that owns the Gatsby Endpoint.
+   *
+   * @return int
+   *   The user id.
+   */
+  public function getOwnerId();
+
+  /**
+   * Sets the owner id for the user that will own the Gatsby Endpoint.
+   *
+   * @param int $uid
+   *   The user id.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setOwnerId($uid);
+
+  /**
+   * Gets the urls for a specific build type for the Gatsby Endpoint.
+   *
+   * @param string $key
+   *   The build type key (preview or build).
+   *
+   * @return array
+   *   The corresponding build type urls.
+   */
+  public function getUrls($key);
+
+  /**
+   * Gets the preview urls for the Gatsby Endpoint.
+   *
+   * @return array
+   *   The preview urls.
+   */
+  public function getPreviewUrls();
+
+  /**
+   * Sets the preview urls for the Gatsby Endpoint.
+   *
+   * @param array $preview_urls
+   *   The preview urls.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setPreviewUrls(array $preview_urls);
+
+  /**
+   * Gets the build urls for the Gatsby Endpoint.
+   *
+   * @return array
+   *   The build urls.
+   */
+  public function getBuildUrls();
+
+  /**
+   * Sets the build urls for the Gatsby Endpoint.
+   *
+   * @param array $build_urls
+   *   The build urls.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setBuildUrls(array $build_urls);
+
+  /**
+   * Gets the Content Sync url for the Gatsby Endpoint.
+   *
+   * @return string
+   *   The content sync url.
+   */
+  public function getContentSyncUrl();
+
+  /**
+   * Sets the content sync url for the Gatsby Endpoint.
+   *
+   * @param string $contentsync_url
+   *   The content sync url.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setContentSyncUrl($contentsync_url);
+
+  /**
+   * Gets the build entities for the Gatsby Endpoint.
+   *
+   * @return object
+   *   An array containing the build entity configuration.
+   */
+  public function getBuildEntities();
+
+  /**
+   * Sets the build entities for the Gatsby Endpoint.
+   *
+   * @param array $build_entities
+   *   The build entities configuration.
+   *
+   * @return \Drupal\gatsby_endpoints\GatsbyEndpointInterface
+   *   The called gatsby endpoint entity.
+   */
+  public function setBuildEntities(array $build_entities);
+
+  /**
+   * Determines if a entity type and bundle will trigger Gatsby webhooks.
+   *
+   * @param string $entity_type
+   *   The entity type to check for.
+   * @param string $entity_bundle
+   *   The bundle to check for.
+   *
+   * @return bool
+   *   Whether the entity type and bundle is selected as a build entity.
+   */
+  public function isBuildEntitySelected($entity_type, $entity_bundle);
+
+  /**
+   * Gets the build entity type.
+   *
+   * @param string $entity_type
+   *   The entity type to search for.
+   *
+   * @return array
+   *   An array containing the included build config for this entity type.
+   */
+  public function getBuildEntityType($entity_type);
+
+  /**
+   * Gets the included entity for a build type.
+   *
+   * @param array $build_type
+   *   The build type.
+   *
+   * @return array
+   *   An array containing the included entity types.
+   */
+  public function getIncludedEntityTypes($build_type);
+}
diff --git a/src/GatsbyEndpointListBuilder.php b/src/GatsbyEndpointListBuilder.php
index 437ffe6c4cf722087117c31e521a62f2f561376e..cba3870917d49a34e24517a963d48c4ee59f7e6d 100644
--- a/src/GatsbyEndpointListBuilder.php
+++ b/src/GatsbyEndpointListBuilder.php
@@ -2,27 +2,32 @@
 
 namespace Drupal\gatsby_endpoints;
 
-use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityListBuilder;
+use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Link;
-use Drupal\Core\Url;
-use Drupal\gatsby_endpoints\Entity\GatsbyEndpoint;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Routing\RedirectDestinationInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
- * Provides a listing of Gatsby endpoint entities.
+ * Provides a list controller for the gatsby endpoint entity type.
  */
-class GatsbyEndpointListBuilder extends ConfigEntityListBuilder {
+class GatsbyEndpointListBuilder extends EntityListBuilder {
+
+  /**
+   * The date formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatterInterface
+   */
+  protected $dateFormatter;
 
   /**
-   * The current request object.
+   * The redirect destination service.
    *
-   * @var \Symfony\Component\HttpFoundation\Request
+   * @var \Drupal\Core\Routing\RedirectDestinationInterface
    */
-  protected $request;
+  protected $redirectDestination;
 
   /**
    * Constructs a new GatsbyEndpointListBuilder object.
@@ -31,12 +36,15 @@ class GatsbyEndpointListBuilder extends ConfigEntityListBuilder {
    *   The entity type definition.
    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
    *   The entity storage class.
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   Current request object.
+   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
+   *   The date formatter service.
+   * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
+   *   The redirect destination service.
    */
-  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, Request $request) {
+  public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, RedirectDestinationInterface $redirect_destination) {
     parent::__construct($entity_type, $storage);
-    $this->request = $request;
+    $this->dateFormatter = $date_formatter;
+    $this->redirectDestination = $redirect_destination;
   }
 
   /**
@@ -46,18 +54,35 @@ class GatsbyEndpointListBuilder extends ConfigEntityListBuilder {
     return new static(
       $entity_type,
       $container->get('entity_type.manager')->getStorage($entity_type->id()),
-      $container->get('request_stack')->getCurrentRequest()
+      $container->get('date.formatter'),
+      $container->get('redirect.destination')
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    $build['table'] = parent::render();
+
+    $total = $this->getStorage()
+      ->getQuery()
+      ->count()
+      ->execute();
+
+    $build['summary']['#markup'] = $this->t('Total Gatsby Endpoints: @total', ['@total' => $total]);
+    return $build;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function buildHeader() {
-    $header['label'] = $this->t('Endpoint Name');
-    $header['plugin'] = $this->t('Type');
-    $header['id'] = $this->t('Machine name');
-    $header['url'] = $this->t('Endpoint URL');
+    $header['id'] = $this->t('ID');
+    $header['title'] = $this->t('Title');
+    $header['uid'] = $this->t('Author');
+    $header['created'] = $this->t('Created');
+    $header['changed'] = $this->t('Updated');
     return $header + parent::buildHeader();
   }
 
@@ -65,17 +90,27 @@ class GatsbyEndpointListBuilder extends ConfigEntityListBuilder {
    * {@inheritdoc}
    */
   public function buildRow(EntityInterface $entity) {
-    if (!($entity instanceof GatsbyEndpoint)) {
-      return parent::buildRow($entity);
-    }
-
-    $url = $this->request->getSchemeAndHttpHost() . '/gatsby/' . $entity->id();
-
-    $row['label'] = $entity->label();
-    $row['plugin'] = $entity->getPlugin()->getPluginDefinition()['label'];
+    /* @var $entity \Drupal\gatsby_endpoints\GatsbyEndpointInterface */
     $row['id'] = $entity->id();
-    $row['url'] = Link::fromTextAndUrl($url, Url::fromUri($url))->toString();
+    $row['title'] = $entity->toLink();
+    $row['uid']['data'] = [
+      '#theme' => 'username',
+      '#account' => $entity->getOwner(),
+    ];
+    $row['created'] = $this->dateFormatter->format($entity->getCreatedTime());
+    $row['changed'] = $this->dateFormatter->format($entity->getChangedTime());
     return $row + parent::buildRow($entity);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    $destination = $this->redirectDestination->getAsArray();
+    foreach ($operations as $key => $operation) {
+      $operations[$key]['query'] = $destination;
+    }
+    return $operations;
+  }
 }
diff --git a/src/GatsbyEndpointManager.php b/src/GatsbyEndpointManager.php
index 12593bbd4edb9b11bc676b267d547e4fbdbcf572..f6663406b186364e643b0af2c0d711570d1c7c27 100644
--- a/src/GatsbyEndpointManager.php
+++ b/src/GatsbyEndpointManager.php
@@ -5,7 +5,6 @@ namespace Drupal\gatsby_endpoints;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
-use Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface;
 
 /**
  * Provides a service to manage Gatsby endpoints.
@@ -29,8 +28,10 @@ class GatsbyEndpointManager {
   /**
    * Constructs a new GatsbyEndpointManager object.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager,
-      EntityFieldManagerInterface $entity_field_manager) {
+  public function __construct(
+    EntityTypeManagerInterface $entity_type_manager,
+    EntityFieldManagerInterface $entity_field_manager
+  ) {
     $this->entityTypeManager = $entity_type_manager;
     $this->entityFieldManager = $entity_field_manager;
   }
@@ -50,7 +51,6 @@ class GatsbyEndpointManager {
   public function getEndpoints() {
     $query = $this->entityTypeManager->getStorage('gatsby_endpoint');
     $endpoint_ids = $query->getQuery()
-      ->sort('weight')
       ->execute();
 
     $endpoints = [];
@@ -68,7 +68,7 @@ class GatsbyEndpointManager {
    */
   public function checkEntity(GatsbyEndpointInterface $endpoint, ContentEntityInterface $entity, $op) {
     // Check if this entity is selected in the build types.
-    if (!$this->checkBuildEntityTypeAndBundle($endpoint->getBuildEntityTypes(), $entity->getEntityTypeId(), $entity->bundle())) {
+    if (!$this->checkBuildEntityTypeAndBundle($endpoint->getBuildEntities(), $entity->getEntityTypeId(), $entity->bundle())) {
       return FALSE;
     }
 
@@ -82,7 +82,7 @@ class GatsbyEndpointManager {
     // If this entity is selected, the endpoint needs to track the entity.
     $in_selected = $this->checkEndpointValues(
       $entity->get($reference_field)->getValue(),
-      $endpoint->id()
+      (int) $endpoint->id()
     );
     if ($in_selected) {
       return $op;
@@ -93,7 +93,7 @@ class GatsbyEndpointManager {
     if ($op == 'update') {
       $in_original = $this->checkEndpointValues(
         $entity->original->get($reference_field)->getValue(),
-        $endpoint->id()
+        (int) $endpoint->id()
       );
 
       if ($in_original) {
@@ -118,7 +118,6 @@ class GatsbyEndpointManager {
       if ($field_type == 'gatsby_endpoint_reference' && !empty($field_name)) {
         return $field_name;
       }
-
     }
 
     return FALSE;
@@ -140,8 +139,7 @@ class GatsbyEndpointManager {
 
     if ($ref_field && !$entity->get($ref_field)->isEmpty() && $endpoint = $entity->{$ref_field}->entity) {
       return $this->getFirstPreviewUrl($endpoint);
-    }
-    else {
+    } else {
       $endpoints = $this->getEndpoints();
 
       foreach ($endpoints as $endpoint) {
diff --git a/src/GatsbyEndpointTrigger.php b/src/GatsbyEndpointTrigger.php
index aebfafec4bcc8e746aeca0a5e7e6edeb886ff99c..feb5f72f4ca7eae2386cd26b4f7de24d22e410ab 100644
--- a/src/GatsbyEndpointTrigger.php
+++ b/src/GatsbyEndpointTrigger.php
@@ -8,8 +8,8 @@ use Drupal\node\NodeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\EntityRepository;
 use Drupal\Core\Logger\LoggerChannelFactoryInterface;
-use Drupal\gatsby_endpoints\Entity\GatsbyEndpointInterface;
-use Drupal\gatsby_instantpreview\GatsbyInstantPreview;
+use Drupal\gatsby_endpoints\GatsbyEndpointInterface;
+use Drupal\gatsby\GatsbyPreview;
 
 /**
  * Class GatsbyEndpointTrigger.
@@ -47,25 +47,27 @@ class GatsbyEndpointTrigger {
   private $entityRepository;
 
   /**
-   * Drupal\gatsby_instantpreview\GatsbyInstantPreview definition.
+   * Drupal\gatsby\GatsbyPreview definition.
    *
-   * @var \Drupal\gatsby_instantpreview\GatsbyInstantPreview
+   * @var \Drupal\gatsby\GatsbyPreview
    */
-  private $gatsbyInstantPreview;
+  private $gatsbyPreview;
 
   /**
    * Constructs a new GatsbyPreview object.
    */
-  public function __construct(ClientInterface $http_client,
-      EntityTypeManagerInterface $entity_type_manager,
-      LoggerChannelFactoryInterface $logger,
-      EntityRepository $entity_repository,
-      GatsbyInstantPreview $gatsby_instant_preview) {
+  public function __construct(
+    ClientInterface $http_client,
+    EntityTypeManagerInterface $entity_type_manager,
+    LoggerChannelFactoryInterface $logger,
+    EntityRepository $entity_repository,
+    GatsbyPreview $gatsby_preview
+  ) {
     $this->httpClient = $http_client;
     $this->entityTypeManager = $entity_type_manager;
     $this->logger = $logger->get('gatsby');
     $this->entityRepository = $entity_repository;
-    $this->gatsbyInstantPreview = $gatsby_instant_preview;
+    $this->gatsbyPreview = $gatsby_preview;
   }
 
   /**
@@ -75,48 +77,18 @@ class GatsbyEndpointTrigger {
    * being sent to the preview or incremental builds servers if mulutiple
    * Drupal entities are update/inserted/deleted in a single request.
    */
-  public function gatsbyPrepareData(GatsbyEndpointInterface $endpoint,
+  public function gatsbyPrepareData(
+    GatsbyEndpointInterface $endpoint,
     ContentEntityInterface $entity = NULL,
     string $action = 'update'
   ) {
 
-    $json = $this->gatsbyInstantPreview->getJson($entity);
-    if (!$json) {
-      return;
-    }
-    $json['id'] = $entity->uuid();
-    $json['action'] = $action;
     $build_type = $endpoint->getBuildEntityType($entity->getEntityTypeId());
 
-    // If there is a secret key we add it to the JSON.
-    $secret = $this->getSecretKey($endpoint);
-    if ($secret) {
-      $json['secret'] = $secret;
-    }
-
-    // Build the entity relationships to send along with the data.
-    if (!empty($json['data']['relationships'])) {
-      // Generate JSON for all related entities to send to Gatsby.
-      $entity_data = [];
-      $included_types = $endpoint->getIncludedEntityTypes($build_type);
-      $this->gatsbyInstantPreview->buildRelationshipJson($json['data']['relationships'], $entity_data, $included_types);
-
-      if (!empty($entity_data)) {
-        // Remove the uuid keys from the array.
-        $entity_data = array_values($entity_data);
-
-        $original_data = $json['data'];
-        $entity_data[] = $original_data;
-        $json['data'] = $entity_data;
-      }
-    }
-
-    $preview_path = "/__refresh";
     $preview_urls = $endpoint->getPreviewUrls();
     if (!empty($preview_urls) && !empty($preview_urls['preview_url'])) {
       foreach ($preview_urls['preview_url'] as $preview_url) {
-        $preview_json = $this->gatsbyInstantPreview->bundleData('preview', $preview_url, $json);
-        $this->gatsbyInstantPreview->updateData('preview', $preview_url, $preview_json, $preview_path);
+        $this->gatsbyPreview->updateData('preview', $preview_url);
       }
     }
 
@@ -127,7 +99,7 @@ class GatsbyEndpointTrigger {
 
     // Verify build URLs are set.
     $build_urls = $endpoint->getBuildUrls();
-    if (empty($build_urls) || empty($build_urls['build_url'])) {
+    if (empty($build_urls)) {
       return;
     }
 
@@ -139,16 +111,16 @@ class GatsbyEndpointTrigger {
       }
     }
 
-    foreach ($build_urls['build_url'] as $build_url) {
-      $build_json = $this->gatsbyInstantPreview->bundleData('incrementalbuild', $build_url, $json);
-      $this->gatsbyInstantPreview->updateData('incrementalbuild', $build_url, $build_json);
-    }
+    //foreach ($build_urls['build_url'] as $build_url) {
+    $this->gatsbyPreview->updateData('incrementalbuild', $build_urls);
+    //}
   }
 
   /**
    * Triggers the refreshing of Gatsby preview and incremental builds.
    */
-  public function gatsbyPrepareDelete(GatsbyEndpointInterface $endpoint,
+  public function gatsbyPrepareDelete(
+    GatsbyEndpointInterface $endpoint,
     ContentEntityInterface $entity = NULL
   ) {
 
@@ -157,24 +129,17 @@ class GatsbyEndpointTrigger {
       'action' => 'delete',
     ];
 
-    // If there is a secret key we add it to the JSON.
-    $secret = $this->getSecretKey($endpoint);
-    if ($secret) {
-      $json['secret'] = $secret;
-    }
-
-    $preview_path = "/__refresh";
     $preview_urls = $endpoint->getPreviewUrls();
     if (!empty($preview_urls) && !empty($preview_urls['preview_url'])) {
       foreach ($preview_urls['preview_url'] as $preview_url) {
-        $this->gatsbyInstantPreview->updateData('preview', $preview_url, $json, $preview_path);
+        $this->gatsbyPreview->updateData('preview', $preview_url, $json);
       }
     }
 
     $build_urls = $endpoint->getBuildUrls();
     if (!empty($build_urls) && !empty($build_urls['build_url'])) {
       foreach ($build_urls['build_url'] as $build_url) {
-        $this->gatsbyInstantPreview->updateData('incrementalbuild', $build_url, $json);
+        $this->gatsbyPreview->updateData('incrementalbuild', $build_url, $json);
       }
     }
   }
@@ -183,7 +148,7 @@ class GatsbyEndpointTrigger {
    * Triggers the refreshing of Gatsby preview and incremental builds.
    */
   public function gatsbyUpdate() {
-    $this->gatsbyInstantPreview->gatsbyUpdate();
+    $this->gatsbyPreview->gatsbyUpdate();
   }
 
   /**
@@ -193,22 +158,9 @@ class GatsbyEndpointTrigger {
     $build_urls = $endpoint->getBuildUrls();
     if (!empty($build_urls) && !empty($build_urls['build_url'])) {
       foreach ($build_urls['build_url'] as $build_url) {
-        $this->gatsbyInstantPreview->triggerRefresh($build_url);
+        $this->gatsbyPreview->triggerRefresh($build_url);
       }
     }
   }
 
-  /**
-   * Tries to get the secret key for a Gatsby Endpoint if it exists.
-   */
-  private function getSecretKey(GatsbyEndpointInterface $endpoint) {
-    $settings = $endpoint->getSettings();
-
-    if (!empty($settings['secret_key'])) {
-      return $settings['secret_key'];
-    }
-
-    return FALSE;
-  }
-
 }
diff --git a/src/GatsbyEndpointTypeListBuilder.php b/src/GatsbyEndpointTypeListBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..a851930609f02c2cf272ac18d2533adbad617726
--- /dev/null
+++ b/src/GatsbyEndpointTypeListBuilder.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\gatsby_endpoints;
+
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Url;
+
+/**
+ * Defines a class to build a listing of gatsby endpoint type entities.
+ *
+ * @see \Drupal\gatsby_endpoints\Entity\GatsbyEndpointType
+ */
+class GatsbyEndpointTypeListBuilder extends ConfigEntityListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    $header['title'] = $this->t('Label');
+
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['title'] = [
+      'data' => $entity->label(),
+      'class' => ['menu-label'],
+    ];
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    $build = parent::render();
+
+    $build['table']['#empty'] = $this->t(
+      'No Gatsby Endpoint types available. <a href=":link">Add Gatsby Endpoint type</a>.',
+      [':link' => Url::fromRoute('entity.gatsby_endpoint_type.add_form')->toString()]
+    );
+
+    return $build;
+  }
+}
diff --git a/src/Plugin/Field/FieldFormatter/GatsbyEndpointsFormatter.php b/src/Plugin/Field/FieldFormatter/GatsbyEndpointsFormatter.php
index f56752c372981d3a5213d700aa27dc772c3065a5..01ebd2d9c4450a0a1177de6b73c8787309e443d4 100644
--- a/src/Plugin/Field/FieldFormatter/GatsbyEndpointsFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/GatsbyEndpointsFormatter.php
@@ -17,5 +17,4 @@ use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceLabelFormatter;
  * )
  */
 class GatsbyEndpointsFormatter extends EntityReferenceLabelFormatter {
-
 }
diff --git a/src/Plugin/Field/FieldType/GatsbyEndpointsReferenceField.php b/src/Plugin/Field/FieldType/GatsbyEndpointsReferenceField.php
index 768e206ce82eb1c168333fa71456a309b8c1ed97..7cd340733347086c99ac05311765aadc335891e9 100644
--- a/src/Plugin/Field/FieldType/GatsbyEndpointsReferenceField.php
+++ b/src/Plugin/Field/FieldType/GatsbyEndpointsReferenceField.php
@@ -49,5 +49,4 @@ class GatsbyEndpointsReferenceField extends EntityReferenceItem {
   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
     return [];
   }
-
 }
diff --git a/src/Plugin/Field/FieldWidget/GatsbyEndpointsAutocompleteWidget.php b/src/Plugin/Field/FieldWidget/GatsbyEndpointsAutocompleteWidget.php
index 30f1fa467b7e2164ce26751e87eaf60f604c1019..3c25a1e7c38ade6a555377c3f1b199adc63f5cf8 100644
--- a/src/Plugin/Field/FieldWidget/GatsbyEndpointsAutocompleteWidget.php
+++ b/src/Plugin/Field/FieldWidget/GatsbyEndpointsAutocompleteWidget.php
@@ -17,5 +17,4 @@ use Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteWidget
  * )
  */
 class GatsbyEndpointsAutocompleteWidget extends EntityReferenceAutocompleteWidget {
-
 }
diff --git a/src/Plugin/Field/FieldWidget/GatsbyEndpointsSelectWidget.php b/src/Plugin/Field/FieldWidget/GatsbyEndpointsSelectWidget.php
index 36c2c2cad5107fda6ab2a762a88c00829fef11de..8c950dd5bac9bad50ed5d7a08c4a198602ffcd9a 100644
--- a/src/Plugin/Field/FieldWidget/GatsbyEndpointsSelectWidget.php
+++ b/src/Plugin/Field/FieldWidget/GatsbyEndpointsSelectWidget.php
@@ -18,5 +18,4 @@ use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget;
  * )
  */
 class GatsbyEndpointsSelectWidget extends OptionsSelectWidget {
-
 }
diff --git a/templates/gatsby-endpoint.html.twig b/templates/gatsby-endpoint.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..4176689de58b09288e5d5471df86f76dc06f90ed
--- /dev/null
+++ b/templates/gatsby-endpoint.html.twig
@@ -0,0 +1,28 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present a gatsby endpoint entity.
+ *
+ * This template is used when viewing a registered gatsby endpoint's page,
+ * e.g., /admin/content/gatsby-endpoint)/123. 123 being the gatsby endpoint's ID.
+ *
+ * Available variables:
+ * - content: A list of content items. Use 'content' to print all content, or
+ *   print a subset such as 'content.title'.
+ * - attributes: HTML attributes for the container element.
+ *
+ * @see template_preprocess_gatsby_endpoint()
+ */
+#}
+<article{{ attributes }}>
+  <h2>Create your Gatsby Site</h2>
+  <a target="blank" class="button button--action button--primary" href="https://www.gatsbyjs.com/dashboard/deploynow?url=https://github.com/smthomas/siteforge-example">Connect to Gatsby Cloud</a>
+  <p>
+    Click here to create your Gatsby site on Gatsby Cloud. For now you will
+    need to manually enter your environment variables and then copy your
+    build/preview hooks from Gatsby Cloud.
+  </p>
+  {% if content %}
+    {{- content -}}
+  {% endif %}
+</article>