diff --git a/gist_filter.info.yml b/gist_filter.info.yml
new file mode 100644
index 0000000..e8294db
--- /dev/null
+++ b/gist_filter.info.yml
@@ -0,0 +1,5 @@
+name: Gist Input Filter
+type: module
+description: 'Provides the ability to embed gists from Github.'
+package: Filter
+core: '8.x'
diff --git a/gist_filter.module b/gist_filter.module
index 667ce2e..fbec2b6 100644
--- a/gist_filter.module
+++ b/gist_filter.module
@@ -10,7 +10,7 @@
  */
 
 /**
- * Implementation of hook_theme().
+ * Implements hook_theme().
  */
 function gist_filter_theme() {
   return array(
@@ -19,153 +19,3 @@ function gist_filter_theme() {
     ),
   );
 }
-
-/**
- * Implementation of hook_filter_info().
- */
-function gist_filter_filter_info() {
-  $filters['gist_filter'] = array(
-    'title' => t('Gist filter (Github Gists)'),
-    'description' => t('Substitutes [gist:xx] tags with the gist located at http://gist.github.com/xx.'),
-    'process callback'  => 'gist_filter_gist_filter_process',
-    'default settings' => array(
-      'gist_filter_display_method' => 'embed',
-    ),
-    'settings callback' => 'gist_filter_gist_filter_settings',
-    'tips callback' => 'gist_filter_filter_tips',
-    'cache' => TRUE,
-  );
-  return $filters;
-}
-
-/**
- * Process callback for hook_filter_info().
- */
-function gist_filter_gist_filter_process($text, $filter, $format) {
-  $display = $filter->settings['gist_filter_display_method'];
-  $callback = '_gist_display_' . $display;
-  return preg_replace_callback('@\[gist\:(\d+)\:?([^\]])+?\]@', $callback, $text);
-}
-
-/**
- * Settings callback for gist_filter.
- */
-function gist_filter_gist_filter_settings($form, $form_state, $filter, $format, $defaults) {
-  $settings['gist_filter_display_method'] = array(
-    '#title' => t('Gist display method'),
-    '#type' => 'select',
-    '#options' => array(
-      'code' => t('Code tags'),
-      'embed' => t('Embed'),
-      'link' => t('Link'),
-    ),
-    '#default_value' => isset($filter->settings['gist_filter_display_method']) ? $filter->settings['gist_filter_display_method'] : $defaults['gist_filter_display_method'],
-  );
-  return $settings;
-}
-
-/**
- * Implements hook_filter_tips().
- */
-function gist_filter_filter_tips($filter, $format, $long = FALSE) {
-  $display = $filter->settings['gist_filter_display_method'];
-  $action = $display == 'embed' ? t('embed the gist') : t('create a link to the gist');
-  return t('Use [gist:####] where #### is your gist number to %action.', array('%action' => $action));
-}
-
-/**
- * Theme function to render the contents of a gist file in <code> tags.
- */
-function theme_gist_filter_code($vars) {
-  $file = $vars['file'];
-
-  return '<div class="drupal-gist-file"><pre type="' . drupal_strtolower($file['language']) . '">' . check_plain($file['content']) . '</pre></div>';
-}
-
-/**
- * Replace the text with the content of the Gist, wrapped in <code> tags.
- */
-function _gist_display_code($matches) {
-  // Get the Gist from the Github API.
-  $data = gist_filter_get_gist($matches[1]);
-
-  $output = '';
-
-  // If a file was specified, just render that one file.
-  if (isset($matches[2]) && !empty($matches[2]) && isset($data['files'][$matches[2]])) {
-    $output = theme('gist_filter_code', array(
-      'file' => $data['files'][$matches[2]],
-    ));
-  }
-  // Otherwise, render all files.
-  else {
-    foreach ($data['files'] as $file) {
-      $output .= theme('gist_filter_code', array(
-        'file' => $file,
-      ));
-    }
-  }
-
-  return $output;
-}
-
-/**
- * Replace the text with embedded script.
- */
-function _gist_display_embed($matches) {
-  $gist_url = 'http://gist.github.com/' . $matches[1];
-  $gist_url = isset($matches[2]) && !empty($matches[2]) ? $gist_url . '.js?file=' . $matches[2] : $gist_url . '.js';
-
-  // Also grab the content and display it in code tags (in case the user does not have JS).
-  $output = '<noscript>' . _gist_display_code($matches) . '</noscript>';
-
-  $output .= '<script src="' . $gist_url . '"></script>';
-  return $output;
-}
-
-/**
- * Replace the text with a link.
- */
-function _gist_display_link($matches) {
-  $gist_url = 'http://gist.github.com/' . $matches[1];
-  $gist_url = isset($matches[2]) && !empty($matches[2])? $gist_url . '#file_' . $matches[2] : $gist_url;
-  return l($gist_url, $gist_url);
-}
-
-/**
- * Helper function to retrieve a Gist from the Github API.
- *
- * @param $id
- *   The ID of the Gist to grab.
- *
- * @return array
- *   The data from the Github API.
- */
-function gist_filter_get_gist($id) {
-  static $gists = array();
-
-  // First, try the static cache.
-  if (!isset($gists[$id])) {
-    // Cache ID
-    $cid = 'gist_filter:gist:' . $id;
-    // Check if this gist is already in the cache
-    $gist = cache_get($cid);
-
-    if ($cached = cache_get($cid)) {
-      $gist = $cached->data;
-    }
-    else {
-      // Not available in the cache, so retrive the gist from Github
-      $url = 'https://api.github.com/gists/' . $id;
-      $response = drupal_http_request($url, array('headers' => array('Content-Type' => 'application/json')));
-      $gist = drupal_json_decode($response->data);
-
-      // Cache the gist until the next cache flush
-      cache_set($cid, $gist, 'cache', CACHE_TEMPORARY);
-    }
-
-    $gists[$id] = $gist;
-  }
-
-  return $gists[$id];
-}
diff --git a/gist_filter.test b/gist_filter.test
deleted file mode 100644
index 618bbd4..0000000
--- a/gist_filter.test
+++ /dev/null
@@ -1,179 +0,0 @@
-<?php
-
-/**
- * @file
- * Tests for the Gist Filter module.
- */
-
-/**
- * Test class.
- */
-class GistFilterTestCase extends DrupalWebTestCase {
-  protected $profile = 'testing';
-  protected $user;
-  protected $contentType;
-
-  public static function getInfo() {
-    return array(
-      'name' => t('Gist Filter Tests'),
-      'description' => t('Tests for the Gist Filter module.'),
-      'group' => t('Input filters'),
-    );
-  }
-
-  /**
-   * Setup stuff.
-   */
-  public function setUp() {
-    parent::setUp('gist_filter');
-
-    // Create a content type to test the filters (with default format)
-    $this->contentType = $this->drupalCreateContentType();
-
-    // Create and log in our user.
-    $this->user = $this->drupalCreateUser(array(
-      'create ' . $this->contentType->type . ' content',
-      'administer filters',
-    ));
-    $this->drupalLogin($this->user);
-  }
-  
-  /**
-   * Testing the embedded gist option.
-   */
-  function testEmbedStyle() {
-
-    // Turn on our input filter and set the option to embed.
-    $edit = array(
-      'filters[gist_filter][status]' => 1,
-      'filters[gist_filter][settings][gist_filter_display_method]' => 'embed',
-    );
-    $this->drupalPost('admin/config/content/formats/plain_text', $edit, t('Save configuration'));
-
-    // Create a test node
-    $langcode = LANGUAGE_NONE;
-    $edit = array(
-      "title" => $this->randomName(),
-      "body[$langcode][0][value]" => 'Hello! [gist:865412]',
-    );
-    $result = $this->drupalPost('node/add/' . $this->contentType->type, $edit, t('Save'));
-    $this->assertResponse(200);
-    $this->assertRaw("Hello! ");
-    $this->assertRaw('<script src="http://gist.github.com/865412.js"></script>');
-
-  }
-
-  /**
-   * Testing the embedded gist option with a file parameter.
-   */
-  function testEmbedStyleWithFile() {
-
-    // Turn on our input filter and set the option to embed.
-    $edit = array(
-      'filters[gist_filter][status]' => 1,
-      'filters[gist_filter][settings][gist_filter_display_method]' => 'embed',
-    );
-    $this->drupalPost('admin/config/content/formats/plain_text', $edit, t('Save configuration'));
-
-    // Create a test node
-    $langcode = LANGUAGE_NONE;
-    $edit = array(
-      "title" => $this->randomName(),
-      "body[$langcode][0][value]" => 'Hello! [gist:865412:php_file.php]',
-    );
-    $result = $this->drupalPost('node/add/' . $this->contentType->type, $edit, t('Save'));
-    $this->assertResponse(200);
-    $this->assertRaw("Hello! ");
-    $this->assertRaw('<script src="http://gist.github.com/865412.js?file=php_file.php"></script>');
-
-  }
-
-  /**
-   * Testing the link option. 
-   */
-  function testLinkStyle() {
-
-    // Turn on our input filter and set the option to link.
-    $edit = array(
-      'filters[gist_filter][status]' => 1,
-      'filters[gist_filter][settings][gist_filter_display_method]' => 'link',
-    );
-    $this->drupalPost('admin/config/content/formats/plain_text', $edit, t('Save configuration'));
-
-    // Create a test node
-    $langcode = LANGUAGE_NONE;
-    $edit = array(
-      "title" => $this->randomName(),
-      "body[$langcode][0][value]" => 'Hello! [gist:865412]',
-    );
-    $result = $this->drupalPost('node/add/' . $this->contentType->type, $edit, t('Save'));
-    $this->assertResponse(200);
-    $this->assertRaw('Hello! <a href="http://gist.github.com/865412">http://gist.github.com/865412</a>');
-
-  }
-
-  /**
-   * Testing the link option. 
-   */
-  function testLinkStyleWithFile() {
-
-    // Turn on our input filter and set the option to link.
-    $edit = array(
-      'filters[gist_filter][status]' => 1,
-      'filters[gist_filter][settings][gist_filter_display_method]' => 'link',
-    );
-    $this->drupalPost('admin/config/content/formats/plain_text', $edit, t('Save configuration'));
-
-    // Create a test node
-    $langcode = LANGUAGE_NONE;
-    $edit = array(
-      "title" => $this->randomName(),
-      "body[$langcode][0][value]" => 'Hello! [gist:865412:php_file.php]',
-    );
-    $result = $this->drupalPost('node/add/' . $this->contentType->type, $edit, t('Save'));
-    $this->assertResponse(200);
-    $this->assertRaw('Hello! <a href="http://gist.github.com/865412#file_php_file.php">http://gist.github.com/865412#file_php_file.php</a>');
-
-  }
-
-  /**
-   * Testing the code tag option. 
-   */
-  function testCodeTagStyle() {
-
-    // Turn on our input filter and set the option to link.
-    $edit = array(
-      'filters[gist_filter][status]' => 1,
-      'filters[gist_filter][settings][gist_filter_display_method]' => 'code',
-    );
-    $this->drupalPost('admin/config/content/formats/plain_text', $edit, t('Save configuration'));
-
-    // Create a test node
-    $langcode = LANGUAGE_NONE;
-    $edit = array(
-      "title" => $this->randomName(),
-      "body[$langcode][0][value]" => 'Hello! [gist:865412]',
-    );
-    $result = $this->drupalPost('node/add/' . $this->contentType->type, $edit, t('Save'));
-    $this->assertResponse(200);
-    $this->assertPattern("@<pre type=\"php\">(.*)echo(.*)</pre>@sm");
-    $this->assertPattern("@<pre type=\"ruby\">(.*)a = 1\nputs a</pre>@");
-
-  }
-
-  /**
-   * Test that our API retrieval function caches calls to the Github API.
-   */
-  function testGistCachingTest() {
-    // Make sure our cache is all cleared first.
-    cache_clear_all('gist_filter:gist', 'cache', TRUE);
-    $this->assertFalse(cache_get('gist_filter:gist:865412'));
-
-    gist_filter_get_gist(865412);
-    
-    // Now the cache should be set.
-    $cached = cache_get('gist_filter:gist:865412');
-    $this->assertTrue($cached->data);
-  }
-
-}
diff --git a/src/GistFilterCacheDecoratorClient.php b/src/GistFilterCacheDecoratorClient.php
new file mode 100644
index 0000000..89b5d1b
--- /dev/null
+++ b/src/GistFilterCacheDecoratorClient.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\gist_filter\GistFilterCacheDecoratorClient.
+ */
+
+namespace Drupal\gist_filter;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+
+/**
+ * Implementation of that caches the results of the wrapped GistFilterClientInterface injected.
+ */
+class GistFilterCacheDecoratorClient implements GistFilterClientInterface {
+
+  /**
+   * Wrapped GistFilterClientInterface implementation.
+   *
+   * @var GistFilterClientInterface
+   */
+  protected $client;
+
+  /**
+   * Cache backend.
+   *
+   * @var CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * Array with already processed gists.
+   *
+   * @var array
+   */
+  protected $gists = [];
+
+  /**
+   * Constructor for proxy implementation.
+   *
+   * @param GistFilterClientInterface $client
+   *   Wrapped client.
+   * @param CacheBackendInterface $cache_backend
+   *   Cache backend.
+   */
+  public function __construct(GistFilterClientInterface $client, CacheBackendInterface $cache_backend) {
+    $this->client = $client;
+    $this->cacheBackend = $cache_backend;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  public function getGist($id) {
+    // First, try the static cache.
+    if (!isset($this->gists[$id])) {
+      // Cache ID.
+      $cid = 'gist_filter:gist:' . $id;
+      // Check if this gist is already in the cache.
+      if ($cached = $this->cacheBackend->get($cid)) {
+        $gist = $cached->data;
+      }
+      else {
+        $gist = $this->client->getGist($id);
+        $this->cacheBackend->set($cid, $gist);
+      }
+
+      $this->gists[$id] = $gist;
+    }
+
+    return $this->gists[$id];
+  }
+
+}
diff --git a/src/GistFilterClientInterface.php b/src/GistFilterClientInterface.php
new file mode 100644
index 0000000..2d5e01d
--- /dev/null
+++ b/src/GistFilterClientInterface.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\gist_filter\GistFilterClientInterface.
+ */
+
+namespace Drupal\gist_filter;
+
+/**
+ * Interface for client implementations.
+ */
+interface GistFilterClientInterface {
+
+  /**
+   * Retrieves a gist from its identifier.
+   *
+   * @param string $id
+   *   The gist identifier.
+   *
+   * @return array
+   *   An array representing the gist.
+   */
+  public function getGist($id);
+
+}
diff --git a/src/GistFilterGitHubClient.php b/src/GistFilterGitHubClient.php
new file mode 100644
index 0000000..6c96027
--- /dev/null
+++ b/src/GistFilterGitHubClient.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\gist_filter\GistFilterGitHubClient.
+ */
+
+namespace Drupal\gist_filter;
+
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Exception\RequestException;
+
+/**
+ * Implementation of GistFilterClientInterface that requests GitHub api to retrieve a gist.
+ */
+class GistFilterGitHubClient implements GistFilterClientInterface {
+
+  /**
+   * The HTTP client to fetch the gist data with.
+   *
+   * @var \GuzzleHttp\ClientInterface
+   */
+  private $httpClient;
+
+  /**
+   * Constructs a GistFilterGitHubClient instance.
+   *
+   * @param \GuzzleHttp\ClientInterface $http_client
+   *   The Guzzle HTTP client.
+   */
+  public function __construct(ClientInterface $http_client) {
+    $this->httpClient = $http_client;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @throws GitHubRequestException
+   *   When the request to the API fails.
+   */
+  public function getGist($id) {
+    try {
+      $url = 'https://api.github.com/gists/' . $id;
+      $response = $this->httpClient->request('GET', $url, array('headers' => array('Accept' => 'application/json')));
+      $data = (string) $response->getBody();
+    }
+    catch (RequestException $e) {
+      throw new GitHubRequestException(sprintf('"%s" error while requesting %s', $e->getMessage(), $url));
+    }
+
+    if (!empty($data)) {
+      $gist = json_decode($data, TRUE);
+    }
+    else {
+      $gist = NULL;
+    }
+
+    return $gist;
+  }
+
+}
diff --git a/src/GitHubRequestException.php b/src/GitHubRequestException.php
new file mode 100644
index 0000000..19c0245
--- /dev/null
+++ b/src/GitHubRequestException.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\gist_filter\GitHubRequestException.
+ */
+
+namespace Drupal\gist_filter;
+
+/**
+ * An exception thrown when the communication with GitHub gist API fails.
+ */
+class GitHubRequestException extends \RuntimeException {
+}
diff --git a/src/Plugin/Filter/GistFilter.php b/src/Plugin/Filter/GistFilter.php
new file mode 100644
index 0000000..5e5bd18
--- /dev/null
+++ b/src/Plugin/Filter/GistFilter.php
@@ -0,0 +1,190 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\gist_filter\Plugin\Filter\GistFilter.
+ */
+
+namespace Drupal\gist_filter\Plugin\Filter;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\filter\FilterProcessResult;
+use Drupal\filter\Plugin\FilterBase;
+use Drupal\gist_filter\GistFilterClientInterface;
+use Drupal\gist_filter\GistFilterGitHubClient;
+use Drupal\gist_filter\GistFilterCacheDecoratorClient;
+use Drupal\gist_filter\GitHubRequestException;
+use Drupal\Core\Routing\LinkGeneratorTrait;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Url;
+
+/**
+ * Provides a filter to substitute [gist:xx] tags with the gist located at "http://gist.github.com/xx".
+ *
+ * @Filter(
+ *   id = "gist_filter",
+ *   title = @Translation("Gist filter (Github Gists)"),
+ *   description = @Translation("Substitutes [gist:xx] tags with the gist located at http://gist.github.com/xx.'"),
+ *   type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE,
+ *   settings = {
+ *     "gist_filter_display_method" = "embed"
+ *   }
+ * )
+ */
+class GistFilter extends FilterBase implements ContainerFactoryPluginInterface {
+
+  use LinkGeneratorTrait;
+
+  /**
+   * Renderer used to display.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * @var GistFilterGitHubClient
+   */
+  protected $gitHubClient;
+
+  /**
+   * @var \Psr\Log\LoggerInterface
+   */
+  protected $logger;
+
+  /**
+   * @param array $configuration
+   * @param string $plugin_id
+   * @param mixed $plugin_definition
+   * @param GistFilterClientInterface $github_client
+   * @param RendererInterface $renderer
+   * @param LoggerInterface $logger
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, GistFilterClientInterface $github_client, RendererInterface $renderer, LoggerInterface $logger) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->gitHubClient = $github_client;
+    $this->renderer = $renderer;
+    $this->logger = $logger;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      new GistFilterCacheDecoratorClient(new GistFilterGitHubClient($container->get('http_client')), $container->get('cache.default')),
+      $container->get('renderer'),
+      $container->get('logger.factory')->get('filter')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $form['gist_filter_display_method'] = array(
+      '#title' => $this->t('Gist display method'),
+      '#type' => 'select',
+      '#options' => array(
+        'code' => $this->t('Code tags'),
+        'embed' => $this->t('Embed'),
+        'link' => $this->t('Link'),
+      ),
+      '#default_value' => $this->settings['gist_filter_display_method'],
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process($text, $language) {
+    $display = $this->settings['gist_filter_display_method'];
+    $callback = 'gistDisplay' . ucfirst($display);
+    $text = preg_replace_callback('@\[gist\:(?<id>[\w/]+)(?:\:(?<file>[\w\.]+))?\]@', array($this, $callback), $text);
+
+    return new FilterProcessResult($text);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tips($long = FALSE) {
+    $display = $this->settings['gist_filter_display_method'];
+    $action = $display == 'embed' ? $this->t('embed the gist') : $this->t('create a link to the gist');
+
+    return $this->t('Use [gist:####] where #### is your gist number to %action.', array('%action' => $action));
+  }
+
+
+  /**
+   * Replace the text with the content of the Gist, wrapped in <pre> tags.
+   */
+  protected function gistDisplayCode(array $matches) {
+    // Get the Gist from the Github API.
+    try {
+      $data = $this->gitHubClient->getGist($matches['id']);
+
+      $build = [];
+      // If a file was specified, just render that one file.
+      if (isset($matches['file']) && !empty($matches['file']) && isset($data['files'][$matches['file']])) {
+        $build[] = array(
+          '#theme' => 'gist_filter_code',
+          '#file' => $data['files'][$matches['file']],
+        );
+
+      }
+      // Otherwise, render all files.
+      else {
+        foreach ($data['files'] as $file) {
+          $build[] = array(
+            '#theme' => 'gist_filter_code',
+            '#file' => $file,
+          );
+        }
+      }
+
+      return $this->renderer->renderPlain($build);
+
+    }
+    catch (GitHubRequestException $e) {
+      $this->logger->notice('Error retrieving gist %gist: %error', array('%gist' => $matches['id'], '%error' => $e->getMessage()));
+    }
+  }
+
+  /**
+   * Replace the text with embedded script.
+   */
+  protected function gistDisplayEmbed(array $matches) {
+    $gist_url = '//gist.github.com/' . $matches['id'];
+    $gist_url = isset($matches['file']) && !empty($matches['file'])
+      ? $gist_url . '.js?file=' . $matches['file']
+      : $gist_url . '.js';
+
+    // Also grab the content and display it in code tags (in case the user does not have JS).
+    $output = '<noscript>' . $this->gistDisplayCode($matches) . '</noscript>';
+    $output .= '<script src="' . $gist_url . '"></script>';
+
+    return $output;
+  }
+
+  /**
+   * Replace the text with a link.
+   */
+  protected function gistDisplayLink(array $matches) {
+    $gist_url = 'http://gist.github.com/' . $matches['id'];
+    $gist_url = isset($matches['file']) && !empty($matches['file'])
+      ? $gist_url . '#file_' . $matches['file']
+      : $gist_url;
+
+    return $this->l($gist_url, Url::fromUri($gist_url));
+  }
+
+}
diff --git a/src/Tests/GistFilterTestCase.php b/src/Tests/GistFilterTestCase.php
new file mode 100644
index 0000000..be0b0b2
--- /dev/null
+++ b/src/Tests/GistFilterTestCase.php
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Tests for the Gist Filter module.
+ */
+
+namespace Drupal\gist_filter\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+
+/**
+ * Test the gist_filter module.
+ *
+ * @group filter
+ */
+class GistFilterTestCase extends WebTestBase {
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['filter', 'node', 'gist_filter'];
+
+  protected $user;
+  protected $contentType;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create a content type to test the filters (with default format).
+    $this->contentType = $this->drupalCreateContentType();
+
+    // Create and log in our user.
+    $this->user = $this->drupalCreateUser(array(
+      'create ' . $this->contentType->id() . ' content',
+      'administer filters',
+    ));
+    $this->drupalLogin($this->user);
+  }
+
+  /**
+   * Testing the embedded gist option.
+   */
+  public function testEmbedStyle() {
+    // Turn on our input filter and set the option to embed.
+    $edit = array(
+      'filters[gist_filter][status]' => 1,
+      'filters[gist_filter][settings][gist_filter_display_method]' => 'embed',
+    );
+
+    $this->drupalPostForm('admin/config/content/formats/manage/plain_text', $edit, t('Save configuration'));
+
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'body[0][value]' => 'Hello! [gist:865412]',
+    );
+
+    $this->drupalPostForm('node/add/' . $this->contentType->id(), $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertRaw("Hello! ");
+    $this->assertRaw('<script src="//gist.github.com/865412.js"></script>');
+
+  }
+
+  /**
+   * Testing the embedded gist option with a file parameter.
+   */
+  public function testEmbedStyleWithFile() {
+    // Turn on our input filter and set the option to embed.
+    $edit = array(
+      'filters[gist_filter][status]' => 1,
+      'filters[gist_filter][settings][gist_filter_display_method]' => 'embed',
+    );
+
+    $this->drupalPostForm('admin/config/content/formats/manage/plain_text', $edit, t('Save configuration'));
+
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'body[0][value]' => 'Hello! [gist:865412:php_file.php]',
+    );
+
+    $this->drupalPostForm('node/add/' . $this->contentType->id(), $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertRaw("Hello! ");
+    $this->assertRaw('<script src="//gist.github.com/865412.js?file=php_file.php"></script>');
+
+  }
+
+  /**
+   * Testing the link option.
+   */
+  public function testLinkStyle() {
+
+    // Turn on our input filter and set the option to link.
+    $edit = array(
+      'filters[gist_filter][status]' => 1,
+      'filters[gist_filter][settings][gist_filter_display_method]' => 'link',
+    );
+
+    $this->drupalPostForm('admin/config/content/formats/manage/plain_text', $edit, t('Save configuration'));
+
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'body[0][value]' => 'Hello! [gist:865412]',
+    );
+
+    $this->drupalPostForm('node/add/' . $this->contentType->id(), $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertRaw('Hello! <a href="http://gist.github.com/865412">http://gist.github.com/865412</a>');
+
+  }
+
+  /**
+   * Testing the link option.
+   */
+  public function testLinkStyleWithFile() {
+
+    // Turn on our input filter and set the option to link.
+    $edit = array(
+      'filters[gist_filter][status]' => 1,
+      'filters[gist_filter][settings][gist_filter_display_method]' => 'link',
+    );
+
+    $this->drupalPostForm('admin/config/content/formats/manage/plain_text', $edit, t('Save configuration'));
+
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'body[0][value]' => 'Hello! [gist:865412:php_file.php]',
+    );
+
+    $this->drupalPostForm('node/add/' . $this->contentType->id(), $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertRaw('Hello! <a href="http://gist.github.com/865412#file_php_file.php">http://gist.github.com/865412#file_php_file.php</a>');
+
+  }
+
+  /**
+   * Testing the code tag option.
+   */
+  public function testCodeTagStyle() {
+
+    // Turn on our input filter and set the option to code.
+    $edit = array(
+      'filters[gist_filter][status]' => 1,
+      'filters[gist_filter][settings][gist_filter_display_method]' => 'code',
+    );
+
+    $this->drupalPostForm('admin/config/content/formats/manage/plain_text', $edit, t('Save configuration'));
+
+    $edit = array(
+      'title[0][value]' => $this->randomMachineName(),
+      'body[0][value]' => 'Hello! [gist:865412]',
+    );
+
+    $this->drupalPostForm('node/add/' . $this->contentType->id(), $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertPattern("@<pre type=\"PHP\">(.*)echo(.*)</pre>@sm");
+    $this->assertPattern("@<pre type=\"Ruby\">(.*)a = 1\nputs a</pre>@");
+
+  }
+
+}
diff --git a/templates/gist-filter-code.html.twig b/templates/gist-filter-code.html.twig
new file mode 100644
index 0000000..741f45a
--- /dev/null
+++ b/templates/gist-filter-code.html.twig
@@ -0,0 +1,19 @@
+{#
+/**
+ * @file
+ * Default theme implementation to present a gist content.
+ *
+ * Available variables:
+ * - file:
+ *   An array containing the following:
+ *   - language: programming language.
+ *   - type: content type of the file.
+ *   - content: Contents of the file.
+ *   - raw_url: url to access the file on GitHub.
+ *
+ * @ingroup themeable
+ */
+#}
+<div class="drupal-gist-file">
+  <pre type="{{ file.language }}">{{ file.content }}</pre>
+</div>
\ No newline at end of file
diff --git a/tests/src/Unit/GistFilterCacheDecoratorClientTest.php b/tests/src/Unit/GistFilterCacheDecoratorClientTest.php
new file mode 100644
index 0000000..ce51389
--- /dev/null
+++ b/tests/src/Unit/GistFilterCacheDecoratorClientTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\gist_filter\Unit\GistFilterCacheDecoratorClientTest.
+ */
+
+namespace Drupal\Tests\gist_filter\Unit;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\gist_filter\GistFilterCacheDecoratorClient;
+
+/**
+ * Tests GistFilterCacheDecoratorClientTest functionality.
+ *
+ * @coversDefaultClass \Drupal\gist_filter\GistFilterCacheDecoratorClient
+ *
+ * @group filter
+ */
+class GistFilterCacheDecoratorClientTest extends UnitTestCase {
+
+  /**
+   * Cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheBackend;
+
+  /**
+   * GitHub client mock.
+   *
+   * @var \Drupal\gist_filter\GistFilterClientInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $client;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->cacheBackend = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface');
+    $this->client = $this->getMock('\Drupal\gist_filter\GistFilterClientInterface');
+  }
+
+  /**
+   * Test that responses are cached.
+   */
+  public function testCacheMissProxiesCallToWrappedClientAndCacheTheResponse() {
+    $this->client->expects($this->once())
+      ->method('getGist')
+      ->with('foo')
+      ->willReturn('response');
+
+    $this->cacheBackend->expects($this->once())
+      ->method('get');
+
+    $this->cacheBackend->expects($this->once())
+      ->method('set')
+      ->with($this->anything(), 'response');
+
+    $cacheClient = new GistFilterCacheDecoratorClient($this->client, $this->cacheBackend);
+    $this->assertEquals('response', $cacheClient->getGist('foo'));
+
+  }
+
+  /**
+   * Tests that on cache hit, wrapped client is not invoked.
+   */
+  public function testIfCachedGistClientIsNotCalled() {
+    $this->client->expects($this->never())
+      ->method('getGist');
+
+    $this->cacheBackend->expects($this->once())
+      ->method('get')
+      ->with($this->anything())
+      ->willReturn((object) ['data' => 'response']);
+
+    $this->cacheBackend->expects($this->never())
+      ->method('set');
+
+    $cacheClient = new GistFilterCacheDecoratorClient($this->client, $this->cacheBackend);
+    $this->assertEquals('response', $cacheClient->getGist('foo'));
+
+  }
+
+  /**
+   * Tests that consecutive calls with same id use internal cache.
+   */
+  public function testConsecutiveCallsWithSameId() {
+    $this->client->expects($this->never())
+      ->method('getGist');
+
+    $this->cacheBackend->expects($this->once())
+      ->method('get')
+      ->with($this->anything())
+      ->willReturn((object) ['data' => 'response']);
+
+    $this->cacheBackend->expects($this->never())
+      ->method('set');
+
+    $cacheClient = new GistFilterCacheDecoratorClient($this->client, $this->cacheBackend);
+    $cacheClient->getGist('foo');
+    $cacheClient->getGist('foo');
+
+  }
+
+}
diff --git a/tests/src/Unit/GistFilterGithubClientTest.php b/tests/src/Unit/GistFilterGithubClientTest.php
new file mode 100644
index 0000000..b2d2c49
--- /dev/null
+++ b/tests/src/Unit/GistFilterGithubClientTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\gist_filter\Unit\GistFilterGitHubClientTest.
+ */
+
+namespace Drupal\Tests\gist_filter\Unit;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\gist_filter\GistFilterGitHubClient;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Client as GuzzleClient;
+use GuzzleHttp\Psr7\Response as GuzzleResponse;
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+
+/**
+ * Tests GistFilterGitHubClient functionality.
+ *
+ * @coversDefaultClass \Drupal\gist_filter\GistFilterGitHubClient
+ *
+ * @group filter
+ */
+class GistFilterGitHubClientTest extends UnitTestCase {
+
+  /**
+   * Test that our API retrieval function caches calls to the Github API.
+   */
+  public function testGistFilterGitHubClientRequestsToGitHubApi() {
+    $response = $this->getMockBuilder('\Psr\Http\Message\ResponseInterface')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $response->expects($this->once())
+      ->method('getBody');
+
+    $httpClient = $this->getMockBuilder('\GuzzleHttp\ClientInterface')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $httpClient->expects($this->once())
+      ->method('request')
+      ->will($this->returnValue($response));
+
+    $client = new GistFilterGitHubClient($httpClient);
+    $client->getGist('foo');
+
+  }
+
+  /**
+   * @expectedException \Drupal\gist_filter\GitHubRequestException
+   */
+  public function testGistFilterGitHubClientThrowsExceptionIfCommunicationWithApiFails() {
+    $httpClient = $this->getHttpClient([
+      new RequestException('', $this->getMock('\Psr\Http\Message\RequestInterface')),
+    ]);
+
+    $client = new GistFilterGitHubClient($httpClient);
+    $client->getGist('foo');
+
+  }
+
+  /**
+   * Test that returned json data is converted into an array.
+   */
+  public function testWeCanReturnGistAsAnArray() {
+    $httpClient = $this->getHttpClient([
+      new GuzzleResponse(200, [], '{"foo": "bar"}'),
+    ]);
+
+    $client = new GistFilterGitHubClient($httpClient);
+    $result = $client->getGist('foo');
+
+    $this->assertEquals(["foo" => "bar"], $result);
+
+  }
+
+
+  /**
+   * Generates a GuzzleClient with the mocked responses.
+   *
+   * @param array $responses
+   *   Collection of responses returned by the client.
+   *
+   * @return GuzzleClient
+   */
+  protected function getHttpClient(array $responses = []) {
+    if (empty($responses)) {
+      $responses = [new GuzzleResponse(200, [], '{}')];
+    }
+
+    $this->mock = new MockHandler($responses);
+    $handlerStack = HandlerStack::create($this->mock);
+    $guzzle = new GuzzleClient(array('handler' => $handlerStack));
+
+    return $guzzle;
+  }
+
+}
