diff --git a/contextual_links_example/contextual_links_example.info.yml b/contextual_links_example/contextual_links_example.info.yml
new file mode 100644
index 0000000..14304ed
--- /dev/null
+++ b/contextual_links_example/contextual_links_example.info.yml
@@ -0,0 +1,9 @@
+name: Contextual Link Example
+type: module
+description: 'Example of implementing contextual links.'
+package: Example modules
+core: 8.x
+dependencies:
+  - drupal:examples
+  - drupal:contextual
+  - drupal:block
diff --git a/contextual_links_example/contextual_links_example.links.contextual.yml b/contextual_links_example/contextual_links_example.links.contextual.yml
new file mode 100644
index 0000000..81b4041
--- /dev/null
+++ b/contextual_links_example/contextual_links_example.links.contextual.yml
@@ -0,0 +1,9 @@
+contextual_links_example.configure:
+  route_name: contextual_links_example.contextual_links_object_edit
+  title: 'Edit object'
+  group: 'contextual_links_example'
+
+contextual_links_example.example_action:
+  route_name: contextual_links_example.contextual_node_action_form
+  title: 'Example Action'
+  group: 'node'
diff --git a/contextual_links_example/contextual_links_example.links.menu.yml b/contextual_links_example/contextual_links_example.links.menu.yml
new file mode 100644
index 0000000..bddbd52
--- /dev/null
+++ b/contextual_links_example/contextual_links_example.links.menu.yml
@@ -0,0 +1,3 @@
+contextual_links_example.description:
+  title: Contextual Links Example
+  route_name: contextual_links_example.contextual_links_object_list
diff --git a/contextual_links_example/contextual_links_example.links.task.yml b/contextual_links_example/contextual_links_example.links.task.yml
new file mode 100644
index 0000000..6ecb51b
--- /dev/null
+++ b/contextual_links_example/contextual_links_example.links.task.yml
@@ -0,0 +1,15 @@
+contextual_links_example.contextual_node_action_form:
+  route_name: contextual_links_example.contextual_node_action_form
+  base_route: entity.node.canonical
+  title: 'Example action'
+  weight: 30
+
+contextual_links_example.contextual_links_object_view:
+  route_name: contextual_links_example.contextual_links_object_view
+  base_route: contextual_links_example.contextual_links_object_view
+  title: 'View'
+
+contextual_links_example.contextual_links_object_edit:
+  route_name: contextual_links_example.contextual_links_object_edit
+  base_route: contextual_links_example.contextual_links_object_view
+  title: 'Edit object'
diff --git a/contextual_links_example/contextual_links_example.module b/contextual_links_example/contextual_links_example.module
new file mode 100644
index 0000000..c02cea8
--- /dev/null
+++ b/contextual_links_example/contextual_links_example.module
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Shows how to use Drupal's contextual links functionality.
+ *
+ * @see https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-contextual-links
+ */
+
+/**
+ * Implements hook_theme().
+ */
+function contextual_links_example_theme() {
+  return [
+    'contextual_links_example_entity' => [
+      'template' => 'contextual-links-example-entity',
+      'render element' => 'element',
+    ],
+  ];
+}
+
+/**
+ * Process variables for contextual-links-example-entity.html.twig.
+ */
+function template_preprocess_contextual_links_example_entity(&$variables) {
+  // Here we take the object that is being themed and define some useful
+  // variables that we will print in the template file.
+  $variables['title'] = $variables['element']['#object']->titleContent();
+  $variables['content'] = $variables['element']['#object']->bodyContent();
+}
diff --git a/contextual_links_example/contextual_links_example.routing.yml b/contextual_links_example/contextual_links_example.routing.yml
new file mode 100644
index 0000000..c2febee
--- /dev/null
+++ b/contextual_links_example/contextual_links_example.routing.yml
@@ -0,0 +1,34 @@
+contextual_links_example.contextual_node_action_form:
+  path: '/node/{node}/example-action'
+  defaults:
+    _title: 'Example action'
+    _form: '\Drupal\contextual_links_example\Form\ContextualLinksExampleNodeActionForm'
+  requirements:
+    node: \d+
+    _access: 'TRUE'
+    _entity_access: 'node.edit'
+
+contextual_links_example.contextual_links_object_list:
+  path: 'examples/contextual-links'
+  defaults:
+    _title: 'Contextual links example list objects'
+    _controller: '\Drupal\contextual_links_example\Controller\CLEController::clePage'
+  requirements:
+    _access: 'TRUE'
+
+contextual_links_example.contextual_links_object_view:
+  path: 'examples/contextual-links/{entity_id}'
+  defaults:
+    _title: 'Contextual links example object'
+    _controller: '\Drupal\contextual_links_example\Controller\CLEController::cleContent'
+  requirements:
+    entity_id: \d+
+    _access: 'TRUE'
+
+contextual_links_example.contextual_links_object_edit:
+  path: '/examples/contextual-links/{entity_id}/edit'
+  defaults:
+    _title: 'Contextual links example object'
+    _form: '\Drupal\contextual_links_example\Form\CLEEntityEditForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/contextual_links_example/src/Controller/CLEController.php b/contextual_links_example/src/Controller/CLEController.php
new file mode 100644
index 0000000..65f662b
--- /dev/null
+++ b/contextual_links_example/src/Controller/CLEController.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\contextual_links_example\Controller;
+
+use Drupal\contextual_links_example\Entity\CLEEntity;
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Controller routines for contextual example routes.
+ */
+class CLEController extends ControllerBase {
+
+  /**
+   * Returns a list of CLEEntity objects.
+   */
+  public function clePage() {
+    // For simplicity we hardcode an array of CLEEntity ids.
+    $entity_ids = [1, 2, 3, 4, 5];
+
+    // Create the renderable array for every CLEEntity.
+    foreach ($entity_ids as $id) {
+      // To add a contextual link we need to provide the key #contextual_links.
+      // See \Drupal\contextual\Element\ContextualLinks.
+      $build[$id] = [
+        '#theme' => 'contextual_links_example_entity',
+        '#object' => new CLEEntity(['id' => $id], 'contextual_links_example_entity'),
+        '#contextual_links' => [
+          'contextual_links_example' => [
+            'route_parameters' => ['entity_id' => $id],
+          ],
+        ],
+      ];
+    }
+
+    return $build;
+  }
+
+  /**
+   * Returns CLEEntity.
+   *
+   * @param int $entity_id
+   *   Entity ID.
+   *
+   * @return array
+   *   An array as expected by drupal_render().
+   */
+  public function cleContent($entity_id) {
+    $build = [
+      '#theme' => 'contextual_links_example_entity',
+      '#object' => new CLEEntity(['id' => $entity_id], 'contextual_links_example_entity'),
+    ];
+    return $build;
+  }
+
+}
diff --git a/contextual_links_example/src/Entity/CLEEntity.php b/contextual_links_example/src/Entity/CLEEntity.php
new file mode 100644
index 0000000..090b8a0
--- /dev/null
+++ b/contextual_links_example/src/Entity/CLEEntity.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\contextual_links_example\Entity;
+
+use Drupal\Core\Entity\Entity;
+
+/**
+ * A simple entity for usage of contextual links assigned to an object.
+ *
+ * @see https://www.drupal.org/node/2123523
+ *
+ * @EntityType(
+ *   id = "contextual_links_example_entity",
+ *   handlers = {
+ *     "storage" = "Drupal\contextual_links_example\Storage\CLEStorage"
+ *   },
+ *   entity_keys = {
+ *     "id" = "id"
+ *   }
+ * )
+ */
+class CLEEntity extends Entity {
+
+  /**
+   * Creates a renderable array containing the body of the entity.
+   *
+   * @return array
+   *   Renderable array of the entity's body.
+   */
+  public function bodyContent() {
+    return [
+      '#markup' => t('This is the content of example object @id.', ['@id' => $this->id()]),
+    ];
+  }
+
+  /**
+   * Creates a renderable array containing the title of the entity.
+   *
+   * @return array
+   *   Renderable array of the entity's title.
+   */
+  public function titleContent() {
+    return [
+      '#markup' => t('Title for example object @id', ['@id' => $this->id()]),
+    ];
+  }
+
+}
diff --git a/contextual_links_example/src/Form/CLEEntityEditForm.php b/contextual_links_example/src/Form/CLEEntityEditForm.php
new file mode 100644
index 0000000..cf4fca2
--- /dev/null
+++ b/contextual_links_example/src/Form/CLEEntityEditForm.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Drupal\contextual_links_example\Form;
+
+use Drupal\contextual_links_example\Entity\CLEEntity;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class CLEEntityEditForm.
+ *
+ * Builds the CLEEntity edit form.
+ *
+ * @package Drupal\contextual_links_example\Form
+ */
+class CLEEntityEditForm extends FormBase {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   * @param int $entity_id
+   *   Entity ID.
+   *
+   * @return array
+   *   An associative array containing the robot add/edit form.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $entity_id = NULL) {
+    $values = [
+      'id' => $entity_id ? $entity_id : 1,
+    ];
+
+    $entity = new CLEEntity($values, 'contextual_links_example_entity');
+
+    // Build the form.
+    $form['paragraph'] = [
+      '#type' => 'html_tag',
+      '#tag' => 'p',
+      '#value' => $this->t('This is the page that would allow you to edit object @id', ['@id' => $entity->id()]),
+    ];
+
+    $form['save'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Update'),
+    ];
+
+    // Return the form.
+    return $form;
+  }
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    return 'contextual_links_example_entity_edit';
+  }
+
+  /**
+   * Form submission handler.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Do the actions upon submitting the form.
+    drupal_set_message($this->t('The entity was modified.'));
+  }
+
+}
diff --git a/contextual_links_example/src/Form/ContextualLinksExampleNodeActionForm.php b/contextual_links_example/src/Form/ContextualLinksExampleNodeActionForm.php
new file mode 100644
index 0000000..7592ca3
--- /dev/null
+++ b/contextual_links_example/src/Form/ContextualLinksExampleNodeActionForm.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Drupal\contextual_links_example\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\node\NodeInterface;
+
+/**
+ * Class ContextualLinksExampleNodeActionForm.
+ *
+ * Creates a form action for the given node in context. Contains the needed
+ * functions for the form and some helper functions.
+ *
+ * @package Drupal\contextual_links_example\Form
+ */
+class ContextualLinksExampleNodeActionForm extends FormBase {
+
+  /**
+   * The form ID. Unique string identifying the form.
+   *
+   * @var string
+   *
+   * @see getFormID()
+   */
+  protected $formId = "contextual_links_example_node_action";
+
+  /**
+   * The node to which the form is attached.
+   *
+   * We add it as a property of the form, to keep a reference to it once the
+   * form is built.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $node;
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    return $this->formId;
+  }
+
+  /**
+   * Form constructor.
+   *
+   * Creates an action form to transform the node's title in all lower, all
+   * uppercase or first character of each word to upper.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param \Drupal\node\NodeInterface $node
+   *   The node from the URL context.
+   *
+   * @return array
+   *   The form structure.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $node = NULL) {
+    // Keep the reference to the node.
+    $this->setNode($node);
+
+    $form['text'] = [
+      '#type' => 'html_tag',
+      '#tag' => 'p',
+      '#value' => $this->t(
+        'This is the page that would allow you to perform an example action on node @nid, named "@title".',
+        ['@nid' => $this->getNid(), '@title' => $this->getNodeTitle()]
+      ),
+    ];
+
+    $form['save'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Save changes'),
+    ];
+
+    return $form;
+  }
+
+  /**
+   * Form submission handler.
+   *
+   * Transform the node's title based on user's choice.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    drupal_set_message($this->t('Updated node %title.', ['%title' => $this->getNodeTitle()]));
+  }
+
+  /**
+   * Set the current node.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node object to be set.
+   */
+  protected function setNode(NodeInterface $node) {
+    $this->node = $node;
+  }
+
+  /**
+   * Get the id of the current node.
+   *
+   * @return int|null|string
+   *   The id of the node.
+   */
+  protected function getNid() {
+    return $this->node->id();
+  }
+
+  /**
+   * Returns the title of the current node.
+   *
+   * @return string
+   *   Title of the node.
+   */
+  protected function getNodeTitle() {
+    return $this->node->getTitle();
+  }
+
+}
diff --git a/contextual_links_example/src/Plugin/Block/CLEBlock.php b/contextual_links_example/src/Plugin/Block/CLEBlock.php
new file mode 100644
index 0000000..f7d0d12
--- /dev/null
+++ b/contextual_links_example/src/Plugin/Block/CLEBlock.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\contextual_links_example\Plugin\Block;
+
+use Drupal\contextual_links_example\Entity\CLEEntity;
+use Drupal\Core\Block\BlockBase;
+
+/**
+ * Provides a 'CLEBlock' block.
+ *
+ * Contains the body of a CLEEntity instance and sets a contextual link to the
+ * entity edit form.
+ *
+ * @Block(
+ *  id = "cleblock",
+ *  label = @Translation("Example: contextual links block"),
+ *  admin_label = @Translation("Example: contextual links block")
+ * )
+ */
+class CLEBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    /** @var \Drupal\contextual_links_example\Entity\CLEEntity $entity */
+    $entity = new CLEEntity(['id' => 42], 'contextual_links_example_entity');
+
+    // Create the content of the block and contextual links.
+    return [
+      'content' => $entity->bodyContent(),
+      '#contextual_links' => [
+        'contextual_links_example' => [
+          'route_parameters' => ['entity_id' => $entity->id()],
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/contextual_links_example/src/Storage/CLEStorage.php b/contextual_links_example/src/Storage/CLEStorage.php
new file mode 100644
index 0000000..403ed1f
--- /dev/null
+++ b/contextual_links_example/src/Storage/CLEStorage.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\contextual_links_example\Storage;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+
+/**
+ * Class CLEStorage.
+ *
+ * Defines a simple storage used by module's entity type.
+ *
+ * @see \Drupal\node\NodeStorage
+ *
+ * @package Drupal\contextual_links_example\Storage
+ */
+class CLEStorage implements EntityStorageInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetCache(array $ids = NULL) {
+    // TODO: Implement resetCache() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $ids = NULL) {
+    // TODO: Implement loadMultiple() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    // TODO: Implement load() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadUnchanged($id) {
+    // TODO: Implement loadUnchanged() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadRevision($revision_id) {
+    // TODO: Implement loadRevision() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteRevision($revision_id) {
+    // TODO: Implement deleteRevision() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadByProperties(array $values = []) {
+    // TODO: Implement loadByProperties() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function create(array $values = []) {
+    // TODO: Implement create() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete(array $entities) {
+    // TODO: Implement delete() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(EntityInterface $entity) {
+    // TODO: Implement save() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuery($conjunction = 'AND') {
+    // TODO: Implement getQuery() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAggregateQuery($conjunction = 'AND') {
+    // TODO: Implement getAggregateQuery() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityTypeId() {
+    // TODO: Implement getEntityTypeId() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityType() {
+    // TODO: Implement getEntityType() method.
+  }
+
+}
diff --git a/contextual_links_example/templates/contextual-links-example-entity.html.twig b/contextual_links_example/templates/contextual-links-example-entity.html.twig
new file mode 100644
index 0000000..5e23413
--- /dev/null
+++ b/contextual_links_example/templates/contextual-links-example-entity.html.twig
@@ -0,0 +1,6 @@
+<div{{ attributes.addClass(classes)}}>
+    {{ title_prefix }}
+    <h2>{{ title }}</h2>
+    {{ title_suffix }}
+    {{ content }}
+</div>
diff --git a/contextual_links_example/tests/src/FunctionalJavascript/ContextualLinksExampleTest.php b/contextual_links_example/tests/src/FunctionalJavascript/ContextualLinksExampleTest.php
new file mode 100644
index 0000000..aee1794
--- /dev/null
+++ b/contextual_links_example/tests/src/FunctionalJavascript/ContextualLinksExampleTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\Tests\contextual_links_example\FunctionalJavascript;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\user\Entity\Role;
+
+/**
+ * Tests the behavior of the contextual links provided by the module.
+ *
+ * @ingroup contextual_links_example
+ *
+ * @group contextual_links_example
+ * @group examples
+ */
+class ContextualLinksExampleTest extends JavascriptTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'node',
+    'contextual_links_example'
+  ];
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * This test class requires the "Tools" block.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Place our custom block in sidebar first.
+    $this->placeBlock('cleblock');
+
+    // Create a node and promote it to the front page.
+    $this->drupalCreateNode(['promote' => 1]);
+  }
+
+  /**
+   * Tests contextual_links_example functionality.
+   */
+  public function testContextualLinksExampleBasic() {
+    $assert = $this->assertSession();
+
+    // Create user.
+    $web_user = $this->drupalCreateUser([
+      'access contextual links',
+    ]);
+    // Login the admin user.
+    $this->drupalLogin($web_user);
+
+    // Grant permissions to use contextual links on blocks.
+    $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), [
+      'access contextual links',
+      'administer blocks',
+    ]);
+
+    // We check for a link to the contextual example in the Tools menu.
+    $this->drupalGet('node');
+    $this->assertSession()->linkByHrefExists('examples/contextual-links');
+
+    $this->drupalGet('examples/contextual-links');
+    $this->assertSession()->statusCodeEquals(200);
+
+    $this->drupalGet('node');
+    // Check the presence of contextual link "Edit object" on our block.
+    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual-links-exampleconfigure');
+    $this->assertNotEmpty($contextualLinks);
+
+    // Check the presence of contextual link "Example Action" on our node.
+    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual-links-exampleexample-action');
+    $this->assertNotEmpty($contextualLinks);
+
+    // Visit our example overview page and check for presence of contextual links.
+    $this->$this->drupalGet('examples/contextual-links');
+    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual-links-exampleconfigure');
+    $this->assertNotEmpty($contextualLinks);
+  }
+
+}
diff --git a/examples.module b/examples.module
index 1333b6e..18bc317 100644
--- a/examples.module
+++ b/examples.module
@@ -39,6 +39,7 @@ function examples_toolbar() {
     'cache_example' => 'cache_example.description',
     'config_entity_example' => 'entity.robot.list',
     'content_entity_example' => 'entity.content_entity_example_contact.collection',
+    'contextual_links_example' => 'contextual_links_example.contextual_links_object_list',
     'cron_example' => 'cron_example',
     'dbtng_example' => 'dbtng_example',
     'email_example' => 'email_example.description',
