diff --git a/js/plugins/tokenbrowser/plugin.js b/js/plugins/tokenbrowser/plugin.js
new file mode 100644
index 0000000..4470832
--- /dev/null
+++ b/js/plugins/tokenbrowser/plugin.js
@@ -0,0 +1,63 @@
+/**
+ * @file
+ * Drupal Entity embed plugin.
+ */
+
+(function ($, Drupal, CKEDITOR) {
+
+  "use strict";
+
+  CKEDITOR.plugins.add('tokenbrowser', {
+
+    // The plugin initialization logic goes inside this method.
+    beforeInit: function (editor) {
+
+      // Generic command.
+      editor.addCommand('edittokenbrowser', {
+        modes: {wysiwyg: 1},
+        canUndo: true,
+        exec: function (editor, data) {
+          data = data || {};
+
+          // We have no current existingValues.
+          var existingValues = {};
+
+          // Set all options for the model.
+          var dialogOptions = {
+            dialogClass: 'token-browser-dialog',
+            autoResize: false,
+            modal: false,
+            draggable: true,
+          };
+          var dialogSettings = drupalSettings.dialog;
+
+          // We have no current saveCallback.
+          var saveCallback = function (values) {};
+
+          // Set the active CKEditor id.
+          Drupal.ckeditorActiveId = editor.name;
+
+          // Open token browser dialog.
+          Drupal.ckeditor.openDialog(editor, data.link, existingValues, saveCallback, dialogOptions);
+
+        }
+      });
+
+      // Register the toolbar buttons.
+      if (editor.ui.addButton) {
+        for (var key in editor.config.TokenBrowser_buttons) {
+          var button = editor.config.TokenBrowser_buttons[key];
+          editor.ui.addButton(button.id, {
+            label: button.label,
+            data: button,
+            click: function (editor) {
+              editor.execCommand('edittokenbrowser', this.data);
+            },
+            icon: button.image
+          });
+        }
+      }
+    }
+  });
+
+})(jQuery, Drupal, CKEDITOR);
diff --git a/src/Plugin/CKEditorPlugin/TokenBrowser.php b/src/Plugin/CKEditorPlugin/TokenBrowser.php
new file mode 100644
index 0000000..06ebca2
--- /dev/null
+++ b/src/Plugin/CKEditorPlugin/TokenBrowser.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\token_filter\Plugin\CKEditorPlugin;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\ckeditor\CKEditorPluginBase;
+use Drupal\editor\Entity\Editor;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines the "tokenbrowser" plugin.
+ *
+ * NOTE: The plugin ID ('id' key) corresponds to the CKEditor plugin name.
+ * It is the first argument of the CKEDITOR.plugins.add() function in the
+ * plugin.js file.
+ *
+ * @CKEditorPlugin(
+ *   id = "tokenbrowser",
+ *   label = @Translation("Token browser")
+ * )
+ */
+class TokenBrowser extends CKEditorPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The CSRF token manager service.
+   *
+   * @var Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $csrfTokenService;
+
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('csrf_token')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param Drupal\Core\Access\CsrfTokenGenerator $csrf_token_service
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, CsrfTokenGenerator $csrf_token_service) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->csrfTokenService = $csrf_token_service;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * NOTE: The keys of the returned array corresponds to the CKEditor button
+   * names. They are the first argument of the editor.ui.addButton() or
+   * editor.ui.addRichCombo() functions in the plugin.js file.
+   */
+  public function getButtons() {
+    return [
+      'tokenbrowser' => [
+        'id' => 'tokenbrowser',
+        'label' => t('Token browser'),
+        'image' => file_create_url($this->getImage()),
+        'link' => $this->getUrl()->toString(),
+      ],
+    ];
+  }
+
+  /**
+   * Fetches the URL.
+   *
+   * @return Drupal\Core\Url
+   *   The URL.
+   *
+   * @see TokenTreeController::outputTree().
+   */
+  protected function getUrl() {
+    $url = Url::fromRoute('token.tree');
+    $options['query'] = [
+      'options' => Json::encode($this->getQueryOptions()),
+      'token' => $this->csrfTokenService->get($url->getInternalPath()),
+    ];
+    $url->setOptions($options);
+    return $url;
+  }
+
+  /**
+   * Fetches the list of query options.
+   *
+   * @return array
+   *   The list of query options.
+   *
+   * @see TreeBuilderInterface::buildRenderable() for option definitions.
+   */
+  protected function getQueryOptions() {
+    return [
+      'token_types' => 'all',
+      'global_types' => FALSE,
+      'click_insert' => TRUE,
+      'show_restricted' => FALSE,
+      'show_nested' => FALSE,
+      'recursion_limit' => 3,
+    ];
+  }
+
+  /**
+   * Fetches the path to the image.
+   *
+   * Make sure that the path to the image matches the file structure of the
+   * CKEditor plugin you are implementing.
+   *
+   * @return string
+   *   The string representation of the path to the image.
+   */
+  protected function getImage() {
+    return $this->getModulePath() . '/js/plugins/tokenbrowser/tokenbrowser.png';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Make sure that the path to the plugin.js matches the file structure of the
+   * CKEditor plugin you are implementing.
+   */
+  public function getFile() {
+    return $this->getModulePath() . '/js/plugins/tokenbrowser/plugin.js';
+  }
+
+  /**
+   * Fetches the path to this module.
+   *
+   * @return string
+   *   The string representation of the module's path.
+   */
+  protected function getModulePath() {
+    return drupal_get_path('module', 'token_filter');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfig(Editor $editor) {
+    return [
+      'TokenBrowser_buttons' => $this->getButtons(),
+    ];
+  }
+
+}
