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..20d3b41
--- /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. Creating and update entities of this time will queue the collection to be sent to the remote endpoint during the next cron run.'),
+      '#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. The collection has been queued for sending during the next cron run.', [
+          '%label' => $chatbot_api_entities_collection->label(),
+        ]));
+        break;
+
+      default:
+        drupal_set_message($this->t('Saved the %label collection. The collection has been queued for sending during the next cron run.', [
+          '%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..a1d2438
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/QueryHandlerBase.php
@@ -0,0 +1,11 @@
+<?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 {
+}
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..d289e34
--- /dev/null
+++ b/modules/chatbot_api_entities/src/Plugin/QueryHandlerInterface.php
@@ -0,0 +1,40 @@
+<?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);
+
+}
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..e7fea59
--- /dev/null
+++ b/modules/chatbot_api_entities/tests/src/Functional/ChatbotApiEntitiesFunctionalTest.php
@@ -0,0 +1,106 @@
+<?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.
+ *
+ * @group chatbot_api
+ */
+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..034e1bd
--- /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
+ */
+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();
+  }
+
+}
