diff --git a/modules/chatbot_api_entities/chatbot_api_entities.info.yml b/modules/chatbot_api_entities/chatbot_api_entities.info.yml
new file mode 100644
index 0000000..6fb1e65
--- /dev/null
+++ b/modules/chatbot_api_entities/chatbot_api_entities.info.yml
@@ -0,0 +1,6 @@
+name: Chatbot API Entities
+description: Provides API for pushing Drupal entities to chatbot services
+type: module
+core: 8.x
+dependencies:
+  - chatbot_api
diff --git a/modules/chatbot_api_entities/chatbot_api_entities.links.action.yml b/modules/chatbot_api_entities/chatbot_api_entities.links.action.yml
new file mode 100644
index 0000000..64c50d7
--- /dev/null
+++ b/modules/chatbot_api_entities/chatbot_api_entities.links.action.yml
@@ -0,0 +1,6 @@
+entity.chatbot_api_entities_collection.add_form:
+  route_name: entity.chatbot_api_entities_collection.add_form
+  title: 'Add collection'
+  appears_on:
+    - entity.chatbot_api_entities_collection.collection
+
diff --git a/modules/chatbot_api_entities/chatbot_api_entities.links.menu.yml b/modules/chatbot_api_entities/chatbot_api_entities.links.menu.yml
new file mode 100644
index 0000000..2ba4ddf
--- /dev/null
+++ b/modules/chatbot_api_entities/chatbot_api_entities.links.menu.yml
@@ -0,0 +1,7 @@
+entity.chatbot_api_entities_collection.collection:
+  title: 'Entity collections'
+  route_name: entity.chatbot_api_entities_collection.collection
+  description: 'Chatbot API entity collections'
+  parent: system.admin_config_services
+  weight: 99
+
diff --git a/modules/chatbot_api_entities/chatbot_api_entities.module b/modules/chatbot_api_entities/chatbot_api_entities.module
new file mode 100644
index 0000000..d064377
--- /dev/null
+++ b/modules/chatbot_api_entities/chatbot_api_entities.module
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains main module functions.
+ */
+
+use Drupal\chatbot_api_entities\EntityHookDispatcher;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+
+/**
+ * Implements hook_help().
+ */
+function chatbot_api_entities_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the helloworld_intent module.
+    case 'help.page.chatbot_api_entities':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('Chatbot API - entities provides a framework for pushing collections of entities to Chatbot services.') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function chatbot_api_entities_entity_delete(EntityInterface $entity) {
+  \Drupal::service('class_resolver')->getInstanceFromDefinition(EntityHookDispatcher::class)->handleEntityEvent($entity, 'delete');
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function chatbot_api_entities_entity_insert(EntityInterface $entity) {
+  \Drupal::service('class_resolver')->getInstanceFromDefinition(EntityHookDispatcher::class)->handleEntityEvent($entity);
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function chatbot_api_entities_entity_update(EntityInterface $entity) {
+  \Drupal::service('class_resolver')->getInstanceFromDefinition(EntityHookDispatcher::class)->handleEntityEvent($entity, 'update');
+}
diff --git a/modules/chatbot_api_entities/chatbot_api_entities.permissions.yml b/modules/chatbot_api_entities/chatbot_api_entities.permissions.yml
new file mode 100644
index 0000000..a1f9fcb
--- /dev/null
+++ b/modules/chatbot_api_entities/chatbot_api_entities.permissions.yml
@@ -0,0 +1,4 @@
+administer chatbot api entities:
+  title: 'Administer Chatbot API entities'
+  description: 'Configure sending of entity information to remote Chatbot APIs'
+  restrict access: true
diff --git a/modules/chatbot_api_entities/chatbot_api_entities.services.yml b/modules/chatbot_api_entities/chatbot_api_entities.services.yml
new file mode 100644
index 0000000..82666d6
--- /dev/null
+++ b/modules/chatbot_api_entities/chatbot_api_entities.services.yml
@@ -0,0 +1,7 @@
+services:
+  plugin.manager.chatbot_api_entities_query_handler:
+    class: Drupal\chatbot_api_entities\Plugin\QueryHandlerManager
+    parent: default_plugin_manager
+  plugin.manager.chatbot_api_entities_push_handler:
+    class: Drupal\chatbot_api_entities\Plugin\PushHandlerManager
+    parent: default_plugin_manager
diff --git a/modules/chatbot_api_entities/config/schema/chatbot_api_entities_collection.schema.yml b/modules/chatbot_api_entities/config/schema/chatbot_api_entities_collection.schema.yml
new file mode 100644
index 0000000..a5de2f7
--- /dev/null
+++ b/modules/chatbot_api_entities/config/schema/chatbot_api_entities_collection.schema.yml
@@ -0,0 +1,71 @@
+chatbot_api_entities.chatbot_api_entities_collection.*:
+  type: config_entity
+  label: 'Entity collection'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    label:
+      type: label
+      label: 'Label'
+    uuid:
+      type: string
+    entity_type:
+      type: string
+      label: 'Entity Type ID'
+    bundle:
+      type: string
+      label: 'Bundle'
+    synonyms:
+      type: string
+      label: 'Synonyms'
+    query_handlers:
+      type: sequence
+      label: 'Enabled query handlers'
+      sequence:
+        type: chatbot_api_entities_query_handler
+    push_handlers:
+      type: sequence
+      label: 'Enabled push handlers'
+      sequence:
+        type: chatbot_api_entities_push_handler
+
+chatbot_api_entities_push_handler:
+  type: mapping
+  label: 'Push handler'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    provider:
+      type: string
+      label: 'Provider'
+    settings:
+      type: chatbot_api_entities_push_handler_settings.[%parent.id]
+
+chatbot_api_entities_push_handler_settings.*:
+  type: sequence
+  label: 'Push settings'
+  sequence:
+    type: string
+    label: 'Value'
+
+chatbot_api_entities_query_handler:
+  type: mapping
+  label: 'Query handler'
+  mapping:
+    id:
+      type: string
+      label: 'ID'
+    provider:
+      type: string
+      label: 'Provider'
+    settings:
+      type: chatbot_api_entities_query_handler_settings.[%parent.id]
+
+chatbot_api_entities_push_handler_settings.*:
+  type: sequence
+  label: 'Query settings'
+  sequence:
+    type: string
+    label: 'Value'
diff --git a/modules/chatbot_api_entities/src/Annotation/PushHandler.php b/modules/chatbot_api_entities/src/Annotation/PushHandler.php
new file mode 100644
index 0000000..078c24e
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Annotation/PushHandler.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Push handler item annotation object.
+ *
+ * @see \Drupal\chatbot_api_entities\Plugin\PushHandlerManager
+ * @see plugin_api
+ *
+ * @Annotation
+ */
+class PushHandler extends Plugin {
+
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The label of the plugin.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $label;
+
+}
diff --git a/modules/chatbot_api_entities/src/Annotation/QueryHandler.php b/modules/chatbot_api_entities/src/Annotation/QueryHandler.php
new file mode 100644
index 0000000..2f354c6
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Annotation/QueryHandler.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Query handler item annotation object.
+ *
+ * @see \Drupal\chatbot_api_entities\Plugin\QueryHandlerManager
+ * @see plugin_api
+ *
+ * @Annotation
+ */
+class QueryHandler extends Plugin {
+
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The label of the plugin.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $label;
+
+}
diff --git a/modules/chatbot_api_entities/src/Entity/EntityCollection.php b/modules/chatbot_api_entities/src/Entity/EntityCollection.php
new file mode 100644
index 0000000..0ba28e0
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Entity/EntityCollection.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
+use Drupal\Core\Plugin\DefaultLazyPluginCollection;
+
+/**
+ * Defines the Entity collection entity.
+ *
+ * @ConfigEntityType(
+ *   id = "chatbot_api_entities_collection",
+ *   label = @Translation("Entity collection"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
+ *     "list_builder" = "Drupal\chatbot_api_entities\EntityCollectionListBuilder",
+ *     "form" = {
+ *       "add" = "Drupal\chatbot_api_entities\Form\EntityCollectionForm",
+ *       "edit" = "Drupal\chatbot_api_entities\Form\EntityCollectionForm",
+ *       "delete" = "Drupal\chatbot_api_entities\Form\EntityCollectionDeleteForm"
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     },
+ *   },
+ *   config_prefix = "chatbot_api_entities_collection",
+ *   admin_permission = "administer chatbot api entities",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   },
+ *   links = {
+ *     "canonical" = "/admin/config/service/chatbot-api-entity-collection/{chatbot_api_entities_collection}",
+ *     "add-form" = "/admin/config/service/chatbot-api-entity-collection/add",
+ *     "edit-form" = "/admin/config/service/chatbot-api-entity-collection/{chatbot_api_entities_collection}/edit",
+ *     "delete-form" = "/admin/config/service/chatbot-api-entity-collection/{chatbot_api_entities_collection}/delete",
+ *     "collection" = "/admin/config/service/chatbot-api-entity-collection"
+ *   },
+ *   config_export = {
+ *     "label",
+ *     "id",
+ *     "synonyms",
+ *     "query_handlers",
+ *     "push_handlers",
+ *     "entity_type",
+ *     "bundle"
+ *   }
+ * )
+ */
+class EntityCollection extends ConfigEntityBase implements EntityCollectionInterface, EntityWithPluginCollectionInterface {
+
+  /**
+   * The Entity collection ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The Entity collection label.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * Field name for deriving synonyms.
+   *
+   * @var string
+   */
+  protected $synonyms;
+
+  /**
+   * Bundle for this collection.
+   *
+   * @var string
+   */
+  protected $bundle;
+
+  /**
+   * Entity type ID for this collection.
+   *
+   * @var string
+   */
+  protected $entity_type;
+
+  /**
+   * Query handler configurations.
+   *
+   * @var []
+   */
+  protected $query_handlers = [];
+
+  /**
+   * Push handler configurations.
+   *
+   * @var []
+   */
+  protected $push_handlers = [];
+
+  /**
+   * Query plugin collection.
+   *
+   * @var \Drupal\Core\Plugin\DefaultLazyPluginCollection
+   */
+  protected $queryHandlerCollection;
+
+  /**
+   * Push plugin collection.
+   *
+   * @var \Drupal\Core\Plugin\DefaultLazyPluginCollection
+   */
+  protected $pushHandlerCollection;
+
+  /**
+   * Constructs a new EntityCollection object.
+   *
+   * @param array $values
+   *   Values.
+   * @param string $entity_type
+   *   Entity type.
+   */
+  public function __construct(array $values, $entity_type) {
+    parent::__construct($values, $entity_type);
+    $this->initializePluginCollections();
+  }
+
+  /**
+   * Gets array of synonyms for the given entity if applicable.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   Entity to get synonyms for.
+   *
+   * @return string[]
+   *   Synonyms.
+   */
+  public function getSynonyms(ContentEntityInterface $entity) {
+    if (!$this->synonyms || !$entity->hasField($this->synonyms) || $entity->get($this->synonyms)->isEmpty()) {
+      return [];
+    }
+    $synonyms = [];
+    /** @var \Drupal\Core\Field\FieldItemInterface $field_item */
+    foreach ($entity->get($this->synonyms) as $field_item) {
+      $synonyms[] = $field_item->get('value')->getValue();
+    }
+    return $synonyms;
+  }
+
+  /**
+   * Generates the collection of entities using the query handlers and pushes.
+   *
+   * Calls each query handler in sequence to build up a list of entities then
+   * passes them to each push handler to send to the remote chatbot endpoints.
+   */
+  public function queryAndPush(EntityTypeManagerInterface $entityTypeManager) {
+    $entities = [];
+    /** @var \Drupal\chatbot_api_entities\Plugin\QueryHandlerInterface $plugin */
+    foreach ($this->queryHandlerCollection as $plugin) {
+      $entities = $plugin->query($entityTypeManager, $entities, $this);
+    }
+    if (!$entities) {
+      // Nothing matched.
+      return;
+    }
+    /** @var \Drupal\chatbot_api_entities\Plugin\PushHandlerInterface $plugin */
+    foreach ($this->pushHandlerCollection as $plugin) {
+      $plugin->pushEntities($entities, $this);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginCollections() {
+    return [
+      'query_handlers' => $this->queryHandlerCollection,
+      'push_handlers' => $this->pushHandlerCollection,
+    ];
+  }
+
+  /**
+   * Gets the entity type ID for the entities represented collection.
+   *
+   * @return string
+   *   Entity type ID.
+   */
+  public function getCollectionEntityTypeId() {
+    return $this->entity_type;
+  }
+
+  /**
+   * Gets the bundle for entities represented by the collection.
+   *
+   * @return string
+   *   Bundle.
+   */
+  public function getCollectionBundle() {
+    return $this->bundle;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    $configuration = $this->pushHandlerCollection->getConfiguration();
+    /** @var \Drupal\chatbot_api_entities\Plugin\PushHandlerInterface $plugin */
+    foreach ($this->pushHandlerCollection as $instance_id => $plugin) {
+      $instance_configuration = [];
+      if (isset($configuration[$instance_id])) {
+        $instance_configuration = $configuration[$instance_id];
+      }
+      $instance_configuration = $plugin->saveConfiguration($this, $instance_configuration);
+      $this->setPushHandlerConfiguration($instance_id, $instance_configuration);
+    }
+    $return = parent::save();
+    // Queue it for updating.
+    \Drupal::queue('chatbot_api_entities_push')->createItem([
+      'collection_id' => $this->id(),
+      'created' => time(),
+    ]);
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setQueryHandlerConfiguration($instance_id, array $configuration) {
+    $this->query_handlers[$instance_id] = $configuration;
+    if (isset($this->queryHandlerCollection)) {
+      $this->queryHandlerCollection->setInstanceConfiguration($instance_id, $configuration);
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPushHandlerConfiguration($instance_id, array $configuration) {
+    $this->push_handlers[$instance_id] = $configuration;
+    if (isset($this->pushHandlerCollection)) {
+      $this->pushHandlerCollection->setInstanceConfiguration($instance_id, $configuration);
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSynonymField() {
+    return $this->synonyms;
+  }
+
+  /**
+   * Sets up plugin collections.
+   */
+  protected function initializePluginCollections() {
+    $this->queryHandlerCollection = new DefaultLazyPluginCollection(\Drupal::service('plugin.manager.chatbot_api_entities_query_handler'), $this->query_handlers);
+    $this->pushHandlerCollection = new DefaultLazyPluginCollection(\Drupal::service('plugin.manager.chatbot_api_entities_push_handler'), $this->push_handlers);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __wakeup() {
+    $this->initializePluginCollections();
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Entity/EntityCollectionInterface.php b/modules/chatbot_api_entities/src/Entity/EntityCollectionInterface.php
new file mode 100644
index 0000000..f78a8d4
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Entity/EntityCollectionInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+
+/**
+ * Provides an interface for defining Entity collection entities.
+ */
+interface EntityCollectionInterface extends ConfigEntityInterface {
+
+  /**
+   * Gets array of synonyms for the given entity if applicable.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   Entity to get synonyms for.
+   *
+   * @return string[]
+   *   Synonyms.
+   */
+  public function getSynonyms(ContentEntityInterface $entity);
+
+  /**
+   * Generates the collection of entities using the query handlers and pushes.
+   *
+   * Calls each query handler in sequence to build up a list of entities then
+   * passes them to each push handler to send to the remote chatbot endpoints.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   */
+  public function queryAndPush(EntityTypeManagerInterface $entityTypeManager);
+
+  /**
+   * Gets the entity type ID for the entities represented collection.
+   *
+   * @return string
+   *   Entity type ID.
+   */
+  public function getCollectionEntityTypeId();
+
+  /**
+   * Gets the bundle for entities represented by the collection.
+   *
+   * @return string
+   *   Bundle.
+   */
+  public function getCollectionBundle();
+
+  /**
+   * Sets configuration for handler.
+   *
+   * @param string $instance_id
+   *   Handler instance.
+   * @param array $configuration
+   *   Configuration.
+   *
+   * @return $this
+   */
+  public function setQueryHandlerConfiguration($instance_id, array $configuration);
+
+  /**
+   * Sets configuration for handler.
+   *
+   * @param string $instance_id
+   *   Handler instance.
+   * @param array $configuration
+   *   Configuration.
+   *
+   * @return $this
+   */
+  public function setPushHandlerConfiguration($instance_id, array $configuration);
+
+  /**
+   * Gets the name of the synonym field.
+   *
+   * @return string
+   */
+  public function getSynonymField();
+
+}
diff --git a/modules/chatbot_api_entities/src/EntityCollectionListBuilder.php b/modules/chatbot_api_entities/src/EntityCollectionListBuilder.php
new file mode 100644
index 0000000..db66cc4
--- /dev/null
+++ b/modules/chatbot_api_entities/src/EntityCollectionListBuilder.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\chatbot_api_entities;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityListBuilder;
+
+/**
+ * Defines a list builder for entity collections.
+ */
+class EntityCollectionListBuilder extends EntityListBuilder {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildRow(EntityInterface $entity) {
+    return ['collection' => $entity->toLink($entity->label(), 'edit-form')] + parent::buildRow($entity);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildHeader() {
+    return ['collection' => $this->t('Collection')] + parent::buildHeader();
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/EntityHookDispatcher.php b/modules/chatbot_api_entities/src/EntityHookDispatcher.php
new file mode 100644
index 0000000..ae7a51f
--- /dev/null
+++ b/modules/chatbot_api_entities/src/EntityHookDispatcher.php
@@ -0,0 +1,96 @@
+<?php
+
+namespace Drupal\chatbot_api_entities;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Queue\QueueFactory;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class EntityHookDispatcher implements ContainerInjectionInterface {
+
+  /**
+   * Entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Collection storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $collectionStorage;
+
+  /**
+   * Queue factory.
+   *
+   * @var \Drupal\Core\Queue\QueueFactory
+   */
+  protected $queueFactory;
+
+  /**
+   * Constructs a new EntityHookDispatcher object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   * @param \Drupal\Core\Queue\QueueFactory $queueFactory
+   */
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, QueueFactory $queueFactory) {
+    $this->entityTypeManager = $entityTypeManager;
+    $this->collectionStorage = $entityTypeManager->getStorage('chatbot_api_entities_collection');
+    $this->queueFactory = $queueFactory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('queue')
+    );
+  }
+
+  /**
+   * Handles entity lifecycle events.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   Entity that was created, updated or deleted.
+   * @param string $event_name
+   *   Event that occurred
+   */
+  public function handleEntityEvent(EntityInterface $entity, $event_name = 'insert') {
+    $queue = $this->queueFactory->get('chatbot_api_entities_push');
+    foreach ($this->getRelatedChatbotEntityCollections($entity) as $collection) {
+      $queue->createItem([
+        'collection_id' => $collection->id(),
+        'created' => time(),
+      ]);
+    }
+  }
+
+  /**
+   * Gets related chatbot entity collections.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   Entity to find matching collections for.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface[]
+   *   Matching chatbot entity collections.
+   */
+  protected function getRelatedChatbotEntityCollections(EntityInterface $entity) {
+    if (!$entity instanceof ContentEntityInterface) {
+      // We don't do anything for non content entities.
+      return [];
+    }
+    return $this->collectionStorage->loadMultiple($this->collectionStorage->getQuery()
+      ->condition('entity_type', $entity->getEntityTypeId())
+      ->condition('bundle', $entity->bundle())
+      ->execute());
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Form/EntityCollectionDeleteForm.php b/modules/chatbot_api_entities/src/Form/EntityCollectionDeleteForm.php
new file mode 100644
index 0000000..6b983f3
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Form/EntityCollectionDeleteForm.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Form;
+
+use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+
+/**
+ * Builds the form to delete Entity collection entities.
+ */
+class EntityCollectionDeleteForm extends EntityConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete %name?', ['%name' => $this->entity->label()]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return new Url('entity.chatbot_api_entities_collection.collection');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Delete');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $this->entity->delete();
+
+    drupal_set_message(
+      $this->t('Collection deleted: @label.', [
+        '@label' => $this->entity->label(),
+      ])
+    );
+
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Form/EntityCollectionForm.php b/modules/chatbot_api_entities/src/Form/EntityCollectionForm.php
new file mode 100644
index 0000000..3a447d8
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Form/EntityCollectionForm.php
@@ -0,0 +1,365 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Form;
+
+use Drupal\chatbot_api_entities\Plugin\PushHandlerManager;
+use Drupal\chatbot_api_entities\Plugin\QueryHandlerManager;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class EntityCollectionForm.
+ */
+class EntityCollectionForm extends EntityForm {
+
+  /**
+   * Entity bundle info.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $entityTypeBundleInfo;
+
+  /**
+   * Entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected $entityFieldManager;
+
+  /**
+   * Push handler manager.
+   *
+   * @var \Drupal\chatbot_api_entities\Plugin\PushHandlerManager
+   */
+  protected $pushHandlerManager;
+
+  /**
+   * Query handler manager.
+   *
+   * @var \Drupal\chatbot_api_entities\Plugin\QueryHandlerManager
+   */
+  protected $queryHandlerManager;
+
+  /**
+   * Entity being edited.
+   *
+   * @var \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $collection
+   */
+  protected $entity;
+
+  /**
+   * Constructs a new EntityCollectionForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo
+   *   Entity bundle info.
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
+   *   Field manager.
+   * @param \Drupal\chatbot_api_entities\Plugin\PushHandlerManager $pushHandlerManager
+   *   Push handler.
+   * @param \Drupal\chatbot_api_entities\Plugin\QueryHandlerManager $queryHandlerManager
+   *   Query handler.
+   */
+  public function __construct(EntityTypeManagerInterface $entityTypeManager, EntityTypeBundleInfoInterface $entityTypeBundleInfo, EntityFieldManagerInterface $entityFieldManager, PushHandlerManager $pushHandlerManager, QueryHandlerManager $queryHandlerManager) {
+    $this->entityTypeManager = $entityTypeManager;
+    $this->entityTypeBundleInfo = $entityTypeBundleInfo;
+    $this->entityFieldManager = $entityFieldManager;
+    $this->pushHandlerManager = $pushHandlerManager;
+    $this->queryHandlerManager = $queryHandlerManager;
+  }
+
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager'),
+      $container->get('entity_type.bundle.info'),
+      $container->get('entity_field.manager'),
+      $container->get('plugin.manager.chatbot_api_entities_push_handler'),
+      $container->get('plugin.manager.chatbot_api_entities_query_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+
+    $collection = $this->entity;
+    $form['label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $collection->label(),
+      '#description' => $this->t("Label for the Entity collection."),
+      '#required' => TRUE,
+    ];
+
+    $form['id'] = [
+      '#type' => 'machine_name',
+      '#default_value' => $collection->id(),
+      '#machine_name' => [
+        'exists' => '\Drupal\chatbot_api_entities\Entity\EntityCollection::load',
+      ],
+      '#disabled' => !$collection->isNew(),
+    ];
+
+    $entityTypes = $this->entityTypeManager->getDefinitions();
+    $options = [];
+    foreach ($entityTypes as $id => $entityType) {
+      if (!$entityType->entityClassImplements(ContentEntityInterface::class)) {
+        continue;
+      }
+      foreach ($this->entityTypeBundleInfo->getBundleInfo($id) as $bundleId => $bundle) {
+        $options[(string) $entityType->getLabel()]["$id:$bundleId"] = $bundle['label'];
+      }
+    }
+    $form['entity_type'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['container-inline'],
+      ],
+    ];
+    $default_entity_type = $this->getEntityTypeForCollection($form_state);
+    $form['entity_type']['entity_type'] = [
+      '#type' => 'select',
+      '#options' => $options,
+      '#title' => $this->t('Entity Type'),
+      '#description' => $this->t('Choose the entity type for the entities to comprise this collection.'),
+      '#default_value' => $default_entity_type,
+      '#ajax' => [
+        'wrapper' => 'edit-configuration-container',
+        'callback' => '::changeEntityTypeCallback',
+        'trigger_as' => [
+          'name' => 'op',
+          'value' => $this->t('Change Entity type'),
+        ],
+      ],
+    ];
+    $form['entity_type']['change'] = [
+      '#limit_validation_errors' => [['entity_type']],
+      '#type' => 'submit',
+      '#value' => $this->t('Change Entity type'),
+      '#attributes' => [
+        'class' => ['js-hide'],
+      ],
+      '#submit' => ['::changeEntityType'],
+      '#ajax' => [
+        'callback' => '::changeEntityTypeCallback',
+        'wrapper' => 'edit-configuration-container',
+      ]
+    ];
+
+    $fields = [];
+    $form['configuration'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'id' => 'edit-configuration-container',
+      ],
+    ];
+    if ($default_entity_type) {
+      list ($entity_type, $bundle) = explode(':', $default_entity_type, 2);
+      $fieldsDefinitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
+      foreach ($fieldsDefinitions as $name => $field) {
+        if ($field->getType() !== 'string') {
+          continue;
+        }
+        if ($field->getName() === $this->entityTypeManager->getDefinition($entity_type)->getKey('label')) {
+          // Bypass the title/label fields.
+          continue;
+        }
+        $fields[$name] = $field->getLabel();
+      }
+      if ($fields) {
+        $form['configuration']['synonyms'] = [
+          '#type' => 'select',
+          '#empty_option' => $this->t('None'),
+          '#empty_values' => '',
+          '#options' => $fields,
+          '#title' => $this->t('Synonyms field'),
+          '#description' => $this->t('Select the field to use for synonyms'),
+          '#default_value' => $collection->getSynonymField(),
+        ];
+      }
+      else {
+        $form['configuration']['synonyms'] = [
+          '#type' => 'markup',
+          '#markup' => $this->t('There are no text fields for this entity type. Add an <em>Text (plain)</em> field to enable synonyms support.')
+        ];
+      }
+      $query_handlers = $this->queryHandlerManager->getDefinitions();
+      $query_options = [];
+      $query_config = $collection->get('query_handlers');
+      foreach ($query_handlers as $id => $query_handler) {
+        $instance = $this->queryHandlerManager->createInstance($id, isset($query_config[$id]) ? $query_config[$id] : []);
+        if ($instance->applies($entity_type)) {
+          $query_options[$id] = $query_handler['label'];
+        }
+      }
+      $form['configuration']['enabled_query_handlers'] = [
+        '#type' => 'checkboxes',
+        '#title' => $this->t('Enabled query handlers'),
+        '#options' => $query_options,
+        '#required' => TRUE,
+        '#default_value' => array_keys($collection->get('query_handlers')),
+      ];
+    }
+    $push_handlers = $this->pushHandlerManager->getDefinitions();
+    $push_options = [];
+    $push_config = $collection->get('push_handlers');
+    $form['enabled_push_handlers'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Enabled push handlers'),
+      '#required' => TRUE,
+      '#default_value' => array_keys($collection->get('push_handlers')),
+    ];
+    foreach ($push_handlers as $id => $push_handler) {
+      $instance = $this->pushHandlerManager->createInstance($id, isset($push_config[$id]) ? $push_config[$id] : []);
+      $push_options[$id] = $push_handler['label'];
+      if ($push_form = $instance->getSettingsForm($collection, $form, $form_state)) {
+        $form += [
+          'push_handler_configuration' => [
+            '#type' => 'vertical_tabs',
+            '#title' => $this->t('Push handler configuration'),
+          ]
+        ];
+        $form['push_handlers']['settings'][$id] = [
+          '#type' => 'details',
+          '#tree' => TRUE,
+          '#open' => TRUE,
+          '#title' => $push_handler['label'],
+          '#group' => 'push_handler_configuration',
+          '#parents' => ['push_handler_configuration', $id, 'settings'],
+        ] + $push_form;
+      }
+    }
+    $form['enabled_push_handlers']['#options'] = $push_options;
+    return $form;
+  }
+
+  /**
+   * Submit handler.
+   *
+   * @param array $form
+   *   Form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form state.
+   */
+  public function changeEntityType(array $form, FormStateInterface $form_state) {
+    $form_state->setRebuild(TRUE);
+  }
+
+  /**
+   * Ajax callback.
+   *
+   * @param array $form
+   *   Form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form state.
+   *
+   * @return array
+   *   Form elements.
+   */
+  public function changeEntityTypeCallback(array $form, FormStateInterface $form_state) {
+    if (!isset($form['configuration'])) {
+      return [];
+    }
+    return $form['configuration'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $chatbot_api_entities_collection = $this->entity;
+    $status = $chatbot_api_entities_collection->save();
+
+    switch ($status) {
+      case SAVED_NEW:
+        drupal_set_message($this->t('Created the %label collection.', [
+          '%label' => $chatbot_api_entities_collection->label(),
+        ]));
+        break;
+
+      default:
+        drupal_set_message($this->t('Saved the %label collection.', [
+          '%label' => $chatbot_api_entities_collection->label(),
+        ]));
+    }
+    $form_state->setRedirectUrl($chatbot_api_entities_collection->toUrl('collection'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEntity(array $form, FormStateInterface $form_state) {
+    $entity = parent::buildEntity($form, $form_state);
+    list ($entity_type, $bundle) = explode(':', $form_state->getValue('entity_type'), 2);
+    $entity->set('entity_type', $entity_type);
+    $entity->set('bundle', $bundle);
+    // Reset these to empty.
+    $entity->set('queryHandlers', []);
+    $entity->set('pushHandlers', []);
+    foreach (array_filter($form_state->getValue('enabled_query_handlers') ?: []) as $query_handler) {
+      $entity->setQueryHandlerConfiguration($query_handler, ['id' => $query_handler]);
+    }
+    foreach (array_filter($form_state->getValue('enabled_push_handlers') ?: []) as $push_handler) {
+      $entity->setPushHandlerConfiguration($push_handler, [
+        'id' => $push_handler,
+      ] + $form_state->getValue(['push_handler_configuration', $push_handler]));
+    }
+
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+    $push_handlers = $this->pushHandlerManager->getDefinitions();
+    $push_config = $this->entity->get('pushHandlers');
+    $enabled = array_filter($form_state->getValue('enabled_push_handlers'));
+    foreach ($push_handlers as $id => $push_handler) {
+      if (empty($enabled[$id])) {
+        continue;
+      }
+      $instance = $this->pushHandlerManager->createInstance($id, isset($push_config[$id]) ? $push_config[$id] : []);
+      $instance->validateSettingsForm($this->entity, $form, $form_state);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __wakeup() {
+    $this->entityTypeManager = \Drupal::service('entity_type.manager');
+    $this->entityTypeBundleInfo = \Drupal::service('entity_type.bundle.info');
+    $this->entityFieldManager = \Drupal::service('entity_field.manager');
+    $this->pushHandlerManager = \Drupal::service('plugin.manager.chatbot_api_entities_push_handler');
+    $this->queryHandlerManager = \Drupal::service('plugin.manager.chatbot_api_entities_query_handler');
+  }
+
+  /**
+   * Gets the entity type being edited.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form state.
+   *
+   * @return string
+   *   Entity type.
+   */
+  protected function getEntityTypeForCollection(FormStateInterface $form_state) {
+    $default_entity_type = $form_state->getValue('entity_type');
+    if (!$default_entity_type && $this->entity->getCollectionEntityTypeId()) {
+      $default_entity_type = $this->entity->getCollectionEntityTypeId() . ':' . $this->entity->getCollectionBundle();
+    }
+    return $default_entity_type;
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/ChatbotApiEntities/QueryHandler/DefaultEntity.php b/modules/chatbot_api_entities/src/Plugin/ChatbotApiEntities/QueryHandler/DefaultEntity.php
new file mode 100644
index 0000000..39c3a0e
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/ChatbotApiEntities/QueryHandler/DefaultEntity.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin\ChatbotApiEntities\QueryHandler;
+
+use Drupal\chatbot_api_entities\Annotation\QueryHandler;
+use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
+use Drupal\chatbot_api_entities\Plugin\QueryHandlerBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+
+/**
+ * Defines a query handler that just uses entity query to limit as appropriate.
+ *
+ * @QueryHandler(
+ *   id = "default",
+ *   label = @Translation("Default"),
+ *   deriver = "Drupal\chatbot_api_entities\Plugin\Derivative\DefaultEntityDeriver"
+ * )
+ */
+class DefaultEntity extends QueryHandlerBase {
+
+  /**
+   * Query entities to be pushed.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $existing
+   *   Existing entities.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface[]
+   *   Loaded entities.
+   */
+  public function query(EntityTypeManagerInterface $entityTypeManager, array $existing = [], EntityCollectionInterface $collection) {
+    $entity_type_id = $collection->getCollectionEntityTypeId();
+    $entity_type = $entityTypeManager->getDefinition($entity_type_id);
+    $query = $this->getQuery($entityTypeManager, $collection, $entity_type);
+    return $existing + $entityTypeManager->getStorage($entity_type_id)->loadMultiple($query->execute());
+  }
+
+  /**
+   * Check if the query handler applies for the given entity type.
+   *
+   * @param string $entity_type_id
+   *   Entity type ID.
+   *
+   * @return bool
+   *   TRUE if applies.
+   */
+  public function applies($entity_type_id) {
+    return $this->pluginDefinition['entity_type'] === $entity_type_id;
+  }
+
+  /**
+   * Generates the query for execution.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $collection
+   *   Collection.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   Entity type.
+   *
+   * @return \Drupal\Core\Entity\Query\QueryInterface
+   *   Query to execute.
+   */
+  protected function getQuery(EntityTypeManagerInterface $entityTypeManager, EntityCollectionInterface $collection, $entity_type) {
+    $query = $entityTypeManager->getStorage($entity_type->id())->getQuery();
+    if ($entity_type->hasKey('bundle')) {
+      $query->condition($entity_type->getKey('bundle'), $collection->getCollectionBundle());
+    }
+    if ($entity_type->hasKey('status')) {
+      $query->condition($entity_type->getKey('status'), TRUE);
+    }
+    return $query;
+  }
+
+}
+
diff --git a/modules/chatbot_api_entities/src/Plugin/ChatbotApiEntities/QueryHandler/User.php b/modules/chatbot_api_entities/src/Plugin/ChatbotApiEntities/QueryHandler/User.php
new file mode 100644
index 0000000..8a01e2a
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/ChatbotApiEntities/QueryHandler/User.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin\ChatbotApiEntities\QueryHandler;
+
+use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+
+/**
+ * Defines a query handler that just uses entity query to limit as appropriate.
+ *
+ * @QueryHandler(
+ *   id = "default:user",
+ *   label = @Translation("User query")
+ * )
+ */
+class User extends DefaultEntity {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getQuery(EntityTypeManagerInterface $entityTypeManager, EntityCollectionInterface $collection, $entity_type) {
+    $query = parent::getQuery($entityTypeManager, $collection, $entity_type);
+    $query->condition('status', 1);
+    return $query;
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/Derivative/DefaultEntityDeriver.php b/modules/chatbot_api_entities/src/Plugin/Derivative/DefaultEntityDeriver.php
new file mode 100644
index 0000000..025f2e7
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/Derivative/DefaultEntityDeriver.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a plugin deriver for default entity query handler.
+ */
+class DefaultEntityDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  /**
+   * The entity type manager
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Creates an DefaultEntityDeriver object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entityTypeManager) {
+    $this->entityTypeManager = $entityTypeManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
+      if (!$entity_type->entityClassImplements(ContentEntityInterface::class)) {
+        continue;
+      }
+      $this->derivatives[$entity_type_id] = $base_plugin_definition;
+      $this->derivatives[$entity_type_id]['label'] = t('@entity_type query', ['@entity_type' => $entity_type->getLabel()]);
+      $this->derivatives[$entity_type_id]['base_plugin_label'] = (string) $base_plugin_definition['label'];
+      $this->derivatives[$entity_type_id]['entity_type'] = $entity_type_id;
+    }
+
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/PushHandlerBase.php b/modules/chatbot_api_entities/src/Plugin/PushHandlerBase.php
new file mode 100644
index 0000000..477d95d
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/PushHandlerBase.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin;
+
+use Drupal\chatbot_api_entities\Entity\EntityCollection;
+use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use GuzzleHttp\ClientInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for Push handler plugins.
+ */
+abstract class PushHandlerBase extends PluginBase implements PushHandlerInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * HTTP client.
+   *
+   * @var \GuzzleHttp\ClientInterface
+   */
+  protected $httpClient;
+
+  /**
+   * Constructs a new PushHandlerBase object.
+   *
+   * @param array $configuration
+   *   Configuration.
+   * @param string $plugin_id
+   *   Plugin ID.
+   * @param mixed $plugin_definition
+   *   Plugin definition.
+   * @param \GuzzleHttp\ClientInterface $httpClient
+   *   HTTP client.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, ClientInterface $httpClient) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->httpClient = $httpClient;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('http_client')
+    );
+  }
+
+  /**
+   * Format entities for pushing.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
+   *   Array of entities to push.
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollection $entityCollection
+   *   Configuration object for the collection.
+   *
+   * @return array
+   *   Array of entries each with keys 'value' and 'synonyms'
+   */
+  protected function formatEntries(array $entities, EntityCollection $entityCollection) {
+    $formatted = [];
+    foreach ($entities as $entity) {
+      $formatted[] = [
+        'value' => $entity->label(),
+        'synonyms' => $entityCollection->getSynonyms($entity),
+      ];
+    }
+    return $formatted;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveConfiguration(EntityCollectionInterface $entityCollection, array $configuration) {
+    return $configuration;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEnabled() {
+    return !empty($this->configuration['status']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettingsForm(EntityCollectionInterface $entityCollection, array $form, FormStateInterface $form_state) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateSettingsForm(EntityCollectionInterface $entityCollection, array $form, FormStateInterface $form_state) {
+    return;
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/PushHandlerInterface.php b/modules/chatbot_api_entities/src/Plugin/PushHandlerInterface.php
new file mode 100644
index 0000000..2d03022
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/PushHandlerInterface.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin;
+
+use Drupal\chatbot_api_entities\Entity\EntityCollection;
+use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Defines an interface for Push handler plugins.
+ */
+interface PushHandlerInterface extends PluginInspectionInterface {
+
+  /**
+   * Push Drupal entities to a remote end-point.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
+   *   Array of entities to push.
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollection $entityCollection
+   *   Configuration object for the collection.
+   *
+   * @return $this
+   */
+  public function pushEntities(array $entities, EntityCollection $entityCollection);
+
+  /**
+   * Gives the plugin a chance to modify its configuration.
+   *
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $entityCollection
+   *   Collection being saved.
+   * @param array $configuration
+   *   Existing configuration.
+   *
+   * @return array
+   *   Updated configuration.
+   */
+  public function saveConfiguration(EntityCollectionInterface $entityCollection, array $configuration);
+
+  /**
+   * Check if plugin is enabled.
+   *
+   * @return bool
+   *   TRUE if enabled.
+   */
+  public function isEnabled();
+
+  /**
+   * Get the settings form.
+   *
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $entityCollection
+   *   Collection being edited.
+   * @param array $form
+   *   The entire form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form state.
+   *
+   * @return array
+   *   Configuration form for this item.
+   */
+  public function getSettingsForm(EntityCollectionInterface $entityCollection, array $form, FormStateInterface $form_state);
+
+  /**
+   * Validate the settings form.
+   *
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $entityCollection
+   *   Collection being edited.
+   * @param array $form
+   *   The entire form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Form state.
+   */
+  public function validateSettingsForm(EntityCollectionInterface $entityCollection, array $form, FormStateInterface $form_state);
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/PushHandlerManager.php b/modules/chatbot_api_entities/src/Plugin/PushHandlerManager.php
new file mode 100644
index 0000000..9ccbfdb
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/PushHandlerManager.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin;
+
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+
+/**
+ * Provides the Push handler plugin manager.
+ */
+class PushHandlerManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a new PushHandlerManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/ChatbotApiEntities/PushHandler', $namespaces, $module_handler, 'Drupal\chatbot_api_entities\Plugin\PushHandlerInterface', 'Drupal\chatbot_api_entities\Annotation\PushHandler');
+
+    $this->alterInfo('chatbot_api_entities_chatbot_api_entities_push_handler_info');
+    $this->setCacheBackend($cache_backend, 'chatbot_api_entities_chatbot_api_entities_push_handler_plugins');
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/QueryHandlerBase.php b/modules/chatbot_api_entities/src/Plugin/QueryHandlerBase.php
new file mode 100644
index 0000000..c11df8e
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/QueryHandlerBase.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin;
+
+use Drupal\Component\Plugin\PluginBase;
+
+/**
+ * Base class for Query handler plugins.
+ */
+abstract class QueryHandlerBase extends PluginBase implements QueryHandlerInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEnabled() {
+    return !empty($this->configuration['status']);
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/QueryHandlerInterface.php b/modules/chatbot_api_entities/src/Plugin/QueryHandlerInterface.php
new file mode 100644
index 0000000..b6b80c2
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/QueryHandlerInterface.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin;
+
+use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+
+/**
+ * Defines an interface for Query handler plugins.
+ */
+interface QueryHandlerInterface extends PluginInspectionInterface {
+
+  /**
+   * Query entities to be pushed.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $existing
+   *   Existing entities.
+   * @param \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $collection
+   *   Collection object.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface[]
+   *   Loaded entities.
+   */
+  public function query(EntityTypeManagerInterface $entityTypeManager, array $existing = [], EntityCollectionInterface $collection);
+
+  /**
+   * Check if the query handler applies for the given entity type.
+   *
+   * @param string $entity_type_id
+   *   Entity type ID.
+   *
+   * @return bool
+   *   TRUE if applies.
+   */
+  public function applies($entity_type_id);
+
+  /**
+   * Check if plugin is enabled.
+   *
+   * @return bool
+   *   TRUE if enabled.
+   */
+  public function isEnabled();
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/QueryHandlerManager.php b/modules/chatbot_api_entities/src/Plugin/QueryHandlerManager.php
new file mode 100644
index 0000000..7cc7afe
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/QueryHandlerManager.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin;
+
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+
+/**
+ * Provides the Query handler plugin manager.
+ */
+class QueryHandlerManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a new QueryHandlerManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/ChatbotApiEntities/QueryHandler', $namespaces, $module_handler, 'Drupal\chatbot_api_entities\Plugin\QueryHandlerInterface', 'Drupal\chatbot_api_entities\Annotation\QueryHandler');
+
+    $this->alterInfo('chatbot_api_entities_chatbot_api_entities_query_handler_info');
+    $this->setCacheBackend($cache_backend, 'chatbot_api_entities_chatbot_api_entities_query_handler_plugins');
+  }
+
+}
diff --git a/modules/chatbot_api_entities/src/Plugin/QueueWorker/ChatbotEntityPushWorker.php b/modules/chatbot_api_entities/src/Plugin/QueueWorker/ChatbotEntityPushWorker.php
new file mode 100644
index 0000000..5936b1b
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/QueueWorker/ChatbotEntityPushWorker.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\chatbot_api_entities\Plugin\QueueWorker;
+
+use Drupal\Core\Annotation\QueueWorker;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Queue\QueueWorkerBase;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a queue worker for pushing collections to remote chatbot services.
+ *
+ * @QueueWorker(
+ *   id = "chatbot_api_entities_push",
+ *   title = @Translation("Chatbot API entities push"),
+ *   cron = {"time" = 60}
+ * )
+ */
+class ChatbotEntityPushWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * State handler.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs a new ChatbotEntityPushWorker object.
+   *
+   * @param array $configuration
+   *   Configuration.
+   * @param string $plugin_id
+   *   Plugin ID.
+   * @param mixed $plugin_definition
+   *   Plugin definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   State storage.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager, StateInterface $state) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->entityTypeManager = $entityTypeManager;
+    $this->state = $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('state')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processItem($data) {
+    $last = $this->state->get('chatbot_api_entities_last_' . $data['collection_id'], 0);
+    if ($last > $data['created']) {
+      // We've already done this since it was created, so no need to do it
+      // again. This can occur if multiple entity updates result in the same
+      // collection ID being queued more than once.
+      return;
+    }
+    /** @var \Drupal\chatbot_api_entities\Entity\EntityCollectionInterface $collection */
+    if ($collection = $this->entityTypeManager->getStorage('chatbot_api_entities_collection')->load($data['collection_id'])) {
+      $collection->queryAndPush($this->entityTypeManager);
+    }
+    $this->state->set('chatbot_api_entities_last_' . $data['collection_id'], time());
+  }
+
+}
diff --git a/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/chatbot_api_entities_test.info.yml b/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/chatbot_api_entities_test.info.yml
new file mode 100644
index 0000000..623776c
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/chatbot_api_entities_test.info.yml
@@ -0,0 +1,7 @@
+name: Chatbot API Entities test
+description: Provides test implementations for chatbot API entities
+type: module
+hidden: TRUE
+core: 8.x
+depdencies:
+  - chatbot_api_entities
diff --git a/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/config/schema/chatbot_api_entities_test.schema.yml b/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/config/schema/chatbot_api_entities_test.schema.yml
new file mode 100644
index 0000000..7f0aed3
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/config/schema/chatbot_api_entities_test.schema.yml
@@ -0,0 +1,9 @@
+chatbot_api_entities_push_handler_settings.chatbot_api_entities_test:
+  type: chatbot_api_entities_push_handler
+  mapping:
+    remote_id:
+      type: string
+      label: 'Remote ID'
+    added_at_save_time:
+      type: string
+      label: 'Added at save time'
diff --git a/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/src/Plugin/ChatbotApiEntities/PushHandler/ChatbotApiEntitiesTestHandler.php b/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/src/Plugin/ChatbotApiEntities/PushHandler/ChatbotApiEntitiesTestHandler.php
new file mode 100644
index 0000000..43e3306
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/modules/chatbot_api_entities_test/src/Plugin/ChatbotApiEntities/PushHandler/ChatbotApiEntitiesTestHandler.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Drupal\chatbot_api_entities_test\Plugin\ChatbotApiEntities\PushHandler;
+
+use Drupal\chatbot_api_entities\Entity\EntityCollection;
+use Drupal\chatbot_api_entities\Entity\EntityCollectionInterface;
+use Drupal\chatbot_api_entities\Plugin\PushHandlerBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use GuzzleHttp\ClientInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a test push handler for storing pushed entities in state.
+ *
+ * @PushHandler(
+ *   id = "chatbot_api_entities_test",
+ *   label = @Translation("Test handler")
+ * )
+ */
+class ChatbotApiEntitiesTestHandler extends PushHandlerBase {
+
+  const STATE_KEY = 'chatbot_api_entities_test';
+
+  /**
+   * State storage.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * Constructs a new ChatbotApiEntitiesTestHandler object.
+   *
+   * @param array $configuration
+   *   Configuration.
+   * @param string $plugin_id
+   *   Plugin ID.
+   * @param mixed $plugin_definition
+   *   Plugin definition.
+   * @param \GuzzleHttp\ClientInterface $httpClient
+   *   HTTP client.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   State storage.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, ClientInterface $httpClient, StateInterface $state) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $httpClient);
+    $this->state = $state;
+    $this->configuration += ['settings' => ['remote_id' => '']];
+  }
+
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('http_client'),
+      $container->get('state')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function pushEntities(array $entities, EntityCollection $entityCollection) {
+    $remote_id = $this->configuration['settings']['remote_id'];
+    $stored = $this->state->get(self::STATE_KEY, []);
+    $stored[$remote_id] = $this->formatEntries($entities, $entityCollection);
+    $this->state->set(self::STATE_KEY, $stored);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveConfiguration(EntityCollectionInterface $entityCollection, array $configuration) {
+    $configuration['settings']['added_at_save_time'] = $entityCollection->id();
+    return parent::saveConfiguration($entityCollection, $configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSettingsForm(EntityCollectionInterface $entityCollection, array $form, FormStateInterface $form_state) {
+    return [
+      'remote_id' => [
+        '#type' => 'textfield',
+        '#title' => new TranslatableMarkup('Remote name'),
+        '#description' => new TranslatableMarkup('Give the collection a name on the remote API'),
+        '#default_value' => $this->configuration['settings']['remote_id'],
+      ]
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateSettingsForm(EntityCollectionInterface $entityCollection, array $form, FormStateInterface $form_state) {
+    if (!$form_state->getValue(['push_handler_configuration', $this->pluginId, 'settings', 'remote_id'])) {
+      $form_state->setError($form['push_handlers']['settings'][$this->pluginId], new TranslatableMarkup('Remote ID is required'));
+    }
+  }
+
+}
diff --git a/modules/chatbot_api_entities/tests/src/Functional/ChatbotApiEntitiesFunctionalTest.php b/modules/chatbot_api_entities/tests/src/Functional/ChatbotApiEntitiesFunctionalTest.php
new file mode 100644
index 0000000..40293d1
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/src/Functional/ChatbotApiEntitiesFunctionalTest.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\Tests\chatbot_api_entities\Functional;
+
+use Drupal\chatbot_api_entities\Entity\EntityCollection;
+use Drupal\chatbot_api_entities_test\Plugin\ChatbotApiEntities\PushHandler\ChatbotApiEntitiesTestHandler;
+use Drupal\Core\Url;
+use Drupal\simpletest\BlockCreationTrait;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\chatbot_api_entities\Traits\ChatbotApiEntitiesTestTrait;
+
+/**
+ * Tests Chatbot API Entities UI functionality.
+ */
+class ChatbotApiEntitiesFunctionalTest extends BrowserTestBase {
+
+  use ChatbotApiEntitiesTestTrait;
+  use BlockCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'entity_test',
+    'chatbot_api',
+    'chatbot_api_entities',
+    'chatbot_api_entities_test',
+    'field',
+    'text',
+    'system',
+    'block',
+    'user',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->setupEntityTestBundle();
+    $this->placeBlock('local_actions_block');
+  }
+
+  /**
+   * Tests admin UI.
+   */
+  public function testAdminUI() {
+    $assert = $this->assertSession();
+    $collectionUrl = Url::fromRoute('entity.chatbot_api_entities_collection.collection');
+    $this->drupalGet($collectionUrl);
+    $assert->statusCodeEquals(403);
+    $admin = $this->createUser(['administer chatbot api entities']);
+    $this->drupalLogin($admin);
+    $this->drupalGet($collectionUrl);
+    $assert->statusCodeEquals(200);
+    $assert->pageTextContains('Entity collection');
+    $this->clickLink('Add collection');
+    $assert->statusCodeEquals(200);
+    $label = 'Send entity test some bundle';
+    $id = 'entity_test_some_bundle';
+    $this->submitForm([
+      'id' => $id,
+      'label' => $label,
+      'entity_type' => 'entity_test:some_bundle',
+    ], 'Change Entity type');
+    // Now should be a button.
+    $this->submitForm([
+      'synonyms' => 'field_synonyms',
+      'enabled_query_handlers[default:entity_test]' => 1,
+      'enabled_push_handlers[chatbot_api_entities_test]' => 1,
+      // Intentionally blank.
+      'push_handler_configuration[chatbot_api_entities_test][settings][remote_id]' => '',
+    ], 'Save');
+    $assert->pageTextContains('Remote ID is required');
+    $this->submitForm([
+      'push_handler_configuration[chatbot_api_entities_test][settings][remote_id]' => 'Foobar',
+    ], 'Save');
+    $assert->responseContains(t('Created the %label collection.', [
+      '%label' => $label,
+    ]));
+    $this->cronRun();
+    $config = EntityCollection::load($id);
+    $this->assertNotEmpty($config);
+    $asArray = $config->toArray();
+    $this->assertEquals([
+      'chatbot_api_entities_test' => [
+        'settings' => [
+          'remote_id' => 'Foobar',
+          'added_at_save_time' => 'entity_test_some_bundle',
+        ],
+        'id' => 'chatbot_api_entities_test',
+      ],
+    ], $asArray['push_handlers']);
+    $this->assertEquals([
+      'default:entity_test' => [
+        'id' => 'default:entity_test',
+      ],
+    ], $asArray['query_handlers']);
+    $this->drupalGet($config->toUrl('edit-form'));
+    // Make sure defaults are shown.
+    $assert->fieldValueEquals('push_handler_configuration[chatbot_api_entities_test][settings][remote_id]', 'Foobar');
+  }
+
+}
diff --git a/modules/chatbot_api_entities/tests/src/Kernel/ChatbotApiEntityIntegrationTest.php b/modules/chatbot_api_entities/tests/src/Kernel/ChatbotApiEntityIntegrationTest.php
new file mode 100644
index 0000000..2e23de1
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/src/Kernel/ChatbotApiEntityIntegrationTest.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\Tests\chatbot_api_entities\Kernel;
+
+use Drupal\chatbot_api_entities_test\Plugin\ChatbotApiEntities\PushHandler\ChatbotApiEntitiesTestHandler;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\chatbot_api_entities\Entity\EntityCollection;
+use Drupal\Tests\chatbot_api_entities\Traits\ChatbotApiEntitiesTestTrait;
+
+/**
+ * Tests chatbot api integration with entity hooks.
+ *
+ * @group chatbot_api_entities
+ */
+class ChatbotApiEntityIntegrationTest extends KernelTestBase {
+
+  use ChatbotApiEntitiesTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'entity_test',
+    'chatbot_api',
+    'chatbot_api_entities',
+    'chatbot_api_entities_test',
+    'field',
+    'text',
+    'system',
+    'user',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('user');
+    $this->installSchema('system', ['sequences']);
+    $this->installEntitySchema('entity_test');
+    $this->setupEntityTestBundle();
+  }
+
+  /**
+   * Test entity events.
+   */
+  public function testEntityEventsTriggerUpdatesInPlugin() {
+    $config = EntityCollection::create([
+      'id' => 'entity_test_some_bundle',
+      'label' => 'Send entity test some bundle',
+      'entity_type' => 'entity_test',
+      'bundle' => 'some_bundle',
+      'synonyms' => 'field_synonyms',
+      'query_handlers' => [
+        'default:entity_test' => [
+          'id' => 'default:entity_test',
+        ],
+      ],
+      'push_handlers' => [
+        ChatbotApiEntitiesTestHandler::STATE_KEY => [
+          'settings' => ['remote_id' => 'Foobar'],
+          'id' => ChatbotApiEntitiesTestHandler::STATE_KEY,
+        ],
+      ],
+    ]);
+    $config->save();
+    $asArray = $config->toArray();
+    $this->assertEquals('entity_test_some_bundle', $asArray['push_handlers'][ChatbotApiEntitiesTestHandler::STATE_KEY]['settings']['added_at_save_time']);
+
+    // Create first entity.
+    $entity_test = EntityTest::create([
+      'type' => 'some_bundle',
+      'field_synonyms' => [
+        'bob',
+        'alice',
+        'terry',
+      ],
+      'name' => 'davo',
+    ]);
+    $entity_test->save();
+    $this->cronRun();
+
+    // Assert push handler was called.
+    $sent = $this->container->get('state')
+      ->get(ChatbotApiEntitiesTestHandler::STATE_KEY, []);
+    $this->assertEquals([
+      'Foobar' => [
+        ['value' => 'davo', 'synonyms' => ['bob', 'alice', 'terry']],
+      ],
+    ], $sent);
+
+    // Create another entity.
+    $second_entity = EntityTest::create([
+      'type' => 'some_bundle',
+      'field_synonyms' => [
+        'wayne',
+        'tez',
+        'barry',
+      ],
+      'name' => 'rod',
+    ]);
+    $second_entity->save();
+    $this->cronRun();
+
+    // Assert both have been sent.
+    $sent = $this->container->get('state')
+      ->get(ChatbotApiEntitiesTestHandler::STATE_KEY, []);
+    $this->assertEquals([
+      'Foobar' => [
+        ['value' => 'davo', 'synonyms' => ['bob', 'alice', 'terry']],
+        ['value' => 'rod', 'synonyms' => ['wayne', 'tez', 'barry']],
+      ],
+    ], $sent);
+
+    // Now update the first one.
+    $entity_test->field_synonyms = [
+      'bob',
+      'alice',
+      'terry',
+      'jimmy',
+    ];
+    $entity_test->save();
+    $this->cronRun();
+
+    // Assert updated.
+    $sent = $this->container->get('state')
+      ->get(ChatbotApiEntitiesTestHandler::STATE_KEY, []);
+    $this->assertEquals([
+      'Foobar' => [
+        ['value' => 'davo', 'synonyms' => ['bob', 'alice', 'terry', 'jimmy']],
+        ['value' => 'rod', 'synonyms' => ['wayne', 'tez', 'barry']],
+      ],
+    ], $sent);
+
+    // Now delete.
+    $second_entity->delete();
+    $this->cronRun();
+
+    // Assert updated.
+    $sent = $this->container->get('state')
+      ->get(ChatbotApiEntitiesTestHandler::STATE_KEY, []);
+    $this->assertEquals([
+      'Foobar' => [
+        ['value' => 'davo', 'synonyms' => ['bob', 'alice', 'terry', 'jimmy']],
+      ],
+    ], $sent);
+  }
+
+
+}
diff --git a/modules/chatbot_api_entities/tests/src/Traits/ChatbotApiEntitiesTestTrait.php b/modules/chatbot_api_entities/tests/src/Traits/ChatbotApiEntitiesTestTrait.php
new file mode 100644
index 0000000..1f561d8
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/src/Traits/ChatbotApiEntitiesTestTrait.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Tests\chatbot_api_entities\Traits;
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+
+/**
+ * Defines a trait for common test functionality.
+ */
+trait ChatbotApiEntitiesTestTrait {
+
+  /**
+   * Creates a new entity test bundle and adds synonyms field.
+   */
+  protected function setupEntityTestBundle() {
+    $field_name = 'field_synonyms';
+    $entity_type = 'entity_test';
+    $bundle = 'some_bundle';
+    entity_test_create_bundle($bundle);
+    FieldStorageConfig::create([
+      'field_name' => $field_name,
+      'entity_type' => $entity_type,
+      'type' => 'string',
+      'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+    ])->save();
+
+    $field_config = FieldConfig::create([
+      'field_name' => $field_name,
+      'label' => $field_name,
+      'entity_type' => $entity_type,
+      'bundle' => $bundle,
+      'required' => FALSE,
+    ]);
+    $field_config->save();
+  }
+
+  /**
+   * Runs cron.
+   */
+  protected function cronRun() {
+    $this->container->get('cron')->run();
+  }
+
+}
diff --git a/modules/chatbot_api_entities/tests/todo.txt b/modules/chatbot_api_entities/tests/todo.txt
new file mode 100644
index 0000000..1f9dbfe
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/todo.txt
@@ -0,0 +1,2 @@
+- config dependencies
+- config form
