diff --git a/config/schema/sdk.schema.yml b/config/schema/sdk.schema.yml
index f5d3abd..56b2295 100644
--- a/config/schema/sdk.schema.yml
+++ b/config/schema/sdk.schema.yml
@@ -4,7 +4,7 @@ sdk.sdk.*:
   type: config_object
   label: SDK
   mapping:
-    type:
+    id:
       type: string
       label: Machine name of integration
     label:
@@ -13,6 +13,3 @@ sdk.sdk.*:
     settings:
       type: sequence
       label: Integration settings
-    callbackUri:
-      type: string
-      label: Callback URI
diff --git a/modules/sdk_facebook/sdk_facebook.module b/modules/sdk_facebook/sdk_facebook.module
deleted file mode 100644
index 3179794..0000000
--- a/modules/sdk_facebook/sdk_facebook.module
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * Facebook SDK.
- */
-
-use Drupal\sdk_facebook\Sdk\FacebookForm;
-use Drupal\sdk_facebook\Sdk\FacebookDeriver;
-
-/**
- * Implements hook_sdk_types().
- */
-function sdk_facebook_sdk_types() {
-  $types = [];
-
-  $types['facebook'] = [
-    'label' => t('Facebook'),
-    'classes' => [
-      'form' => FacebookForm::class,
-      'deriver' => FacebookDeriver::class,
-    ],
-  ];
-
-  return $types;
-}
diff --git a/modules/sdk_facebook/src/Sdk/FacebookDeriver.php b/modules/sdk_facebook/src/Plugin/Sdk/Facebook.php
similarity index 50%
rename from modules/sdk_facebook/src/Sdk/FacebookDeriver.php
rename to modules/sdk_facebook/src/Plugin/Sdk/Facebook.php
index 22362b7..2c0c615 100644
--- a/modules/sdk_facebook/src/Sdk/FacebookDeriver.php
+++ b/modules/sdk_facebook/src/Plugin/Sdk/Facebook.php
@@ -1,21 +1,24 @@
 <?php
 
-namespace Drupal\sdk_facebook\Sdk;
+namespace Drupal\sdk_facebook\Plugin\Sdk;
 
-// SDK API components.
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-// Facebook SDK.
-use Facebook\Facebook;
+use Drupal\sdk\SdkPluginBase;
+use Facebook\Facebook as FacebookSdk;
 
 /**
- * Class FacebookDeriver.
+ * SDK definition.
+ *
+ * @Sdk(
+ *   id = "facebook",
+ *   label = @Translation("Facebook"),
+ * )
  */
-class FacebookDeriver extends BaseDeriver {
+class Facebook extends SdkPluginBase {
 
   /**
    * SDK instance.
    *
-   * @var Facebook
+   * @var FacebookSdk
    */
   private $instance;
 
@@ -24,10 +27,12 @@ class FacebookDeriver extends BaseDeriver {
    */
   protected function getInstance() {
     if (NULL === $this->instance) {
-      $this->instance = new Facebook([
-        'app_id' => $this->entity->settings['app_id'],
-        'app_secret' => $this->entity->settings['app_secret'],
-        'default_graph_version' => 'v' . (float) $this->entity->settings['api_version'],
+      $config = $this->getConfig();
+
+      $this->instance = new FacebookSdk([
+        'app_id' => $config->settings['app_id'],
+        'app_secret' => $config->settings['app_secret'],
+        'default_graph_version' => 'v' . (float) $config->settings['api_version'],
       ]);
     }
 
@@ -52,9 +57,11 @@ class FacebookDeriver extends BaseDeriver {
    * {@inheritdoc}
    */
   public function loginUrl() {
+    $config = $this->getConfig();
+
     return $this->getInstance()->getRedirectLoginHelper()->getLoginUrl(
-      $this->entity->getCallbackUrl(),
-      $this->entity->settings['scope']
+      $config->getCallbackUrl(),
+      $config->settings['scope']
     );
   }
 
@@ -75,7 +82,19 @@ class FacebookDeriver extends BaseDeriver {
   public function getTokenExpiration() {
     $token = $this->getToken();
 
-    return NULL === $token ? NULL : $token->getExpiresAt();
+    // Token not set or its life has ended.
+    if (NULL === $token) {
+      return NULL;
+    }
+
+    $expires = $token->getExpiresAt();
+
+    // If token has "NULL" as expiration this means it has no limitation.
+    if (NULL === $expires) {
+      return self::TOKEN_LIFE_UNLIMITED;
+    }
+
+    return $expires;
   }
 
 }
diff --git a/modules/sdk_facebook/src/Sdk/FacebookForm.php b/modules/sdk_facebook/src/Plugin/Sdk/FacebookConfigurationForm.php
similarity index 93%
rename from modules/sdk_facebook/src/Sdk/FacebookForm.php
rename to modules/sdk_facebook/src/Plugin/Sdk/FacebookConfigurationForm.php
index c01983b..68f9d87 100644
--- a/modules/sdk_facebook/src/Sdk/FacebookForm.php
+++ b/modules/sdk_facebook/src/Plugin/Sdk/FacebookConfigurationForm.php
@@ -1,21 +1,19 @@
 <?php
 
-namespace Drupal\sdk_facebook\Sdk;
+namespace Drupal\sdk_facebook\Plugin\Sdk;
 
-// Core components.
 use Drupal\Core\Form\FormStateInterface;
-// SDK API components.
-use Drupal\sdk\Api\Form\BaseForm;
+use Drupal\sdk\SdkPluginConfigurationFormBase;
 
 /**
- * Class FacebookForm.
+ * Form for SDK configuration.
  */
-class FacebookForm extends BaseForm {
+class FacebookConfigurationForm extends SdkPluginConfigurationFormBase {
 
   /**
    * {@inheritdoc}
    */
-  public function form(array $form, FormStateInterface $form_state) {
+  public function form(array &$form, FormStateInterface $form_state) {
     $form['information'] = [
       '#markup' => $this->t('You can manage applications: @link', [
         '@link' => static::externalLink('https://developers.facebook.com/apps'),
@@ -94,8 +92,6 @@ class FacebookForm extends BaseForm {
         'pages_messaging_phone_number' => $this->t('Messaging phone number pages'),
       ],
     ];
-
-    return $form;
   }
 
 }
diff --git a/modules/sdk_github/sdk_github.module b/modules/sdk_github/sdk_github.module
deleted file mode 100755
index c9cc0a5..0000000
--- a/modules/sdk_github/sdk_github.module
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * GitHub SDK.
- */
-
-use Drupal\sdk_github\Sdk\GitHubForm;
-use Drupal\sdk_github\Sdk\GitHubDeriver;
-
-/**
- * Implements hook_sdk_types().
- */
-function sdk_github_sdk_types() {
-  $types = [];
-
-  $types['github'] = [
-    'label' => t('GitHub'),
-    'classes' => [
-      'form' => GitHubForm::class,
-      'deriver' => GitHubDeriver::class,
-    ],
-  ];
-
-  return $types;
-}
diff --git a/modules/sdk_github/src/Sdk/GitHubDeriver.php b/modules/sdk_github/src/Plugin/Sdk/GitHub.php
old mode 100755
new mode 100644
similarity index 72%
rename from modules/sdk_github/src/Sdk/GitHubDeriver.php
rename to modules/sdk_github/src/Plugin/Sdk/GitHub.php
index 8424c37..50ce4d0
--- a/modules/sdk_github/src/Sdk/GitHubDeriver.php
+++ b/modules/sdk_github/src/Plugin/Sdk/GitHub.php
@@ -1,25 +1,25 @@
 <?php
 
-namespace Drupal\sdk_github\Sdk;
+namespace Drupal\sdk_github\Plugin\Sdk;
 
-// SDK API components.
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-// GitHub SDK.
-use Github\Client;
+use Drupal\sdk\SdkPluginBase;
+use Github\Client as GithubSdk;
 use League\OAuth2\Client\Provider\Github as OAuth;
-use League\OAuth2\Client\Token\AccessToken;
 
 /**
- * Class GitHubDeriver.
+ * SDK definition.
  *
- * @method AccessToken getToken()
+ * @Sdk(
+ *   id = "github",
+ *   label = @Translation("GitHub"),
+ * )
  */
-class GitHubDeriver extends BaseDeriver {
+class GitHub extends SdkPluginBase {
 
   /**
    * SDK instance.
    *
-   * @var Client
+   * @var GithubSdk
    */
   private $instance;
   /**
@@ -34,7 +34,7 @@ class GitHubDeriver extends BaseDeriver {
    */
   protected function getInstance() {
     if (NULL === $this->instance) {
-      $this->instance = new Client();
+      $this->instance = new GithubSdk();
     }
 
     return $this->instance;
@@ -48,10 +48,12 @@ class GitHubDeriver extends BaseDeriver {
    */
   protected function getOauth() {
     if (NULL === $this->oauth) {
+      $config = $this->getConfig();
+
       $this->oauth = new OAuth([
-        'clientId' => $this->entity->settings['client_id'],
-        'clientSecret' => $this->entity->settings['client_secret'],
-        'redirectUri' => $this->entity->getCallbackUrl(),
+        'clientId' => $config->settings['client_id'],
+        'clientSecret' => $config->settings['client_secret'],
+        'redirectUri' => $config->getCallbackUrl(),
       ]);
     }
 
@@ -76,11 +78,12 @@ class GitHubDeriver extends BaseDeriver {
    * {@inheritdoc}
    */
   public function loginUrl() {
-    $_SESSION[static::class] = (string) \Drupal::time()->getRequestTime();
+    // @todo Replace by "datetime.time" service once support of core 8.2.x will be dropped off.
+    $_SESSION[static::class] = REQUEST_TIME;
 
     return $this->getOauth()->getAuthorizationUrl([
       'state' => $_SESSION[static::class],
-      'scope' => $this->entity->settings['scope'],
+      'scope' => $this->getConfig()->settings['scope'],
     ]);
   }
 
diff --git a/modules/sdk_github/src/Sdk/GitHubForm.php b/modules/sdk_github/src/Plugin/Sdk/GitHubConfigurationForm.php
old mode 100755
new mode 100644
similarity index 84%
rename from modules/sdk_github/src/Sdk/GitHubForm.php
rename to modules/sdk_github/src/Plugin/Sdk/GitHubConfigurationForm.php
index c6d84e8..765e8db
--- a/modules/sdk_github/src/Sdk/GitHubForm.php
+++ b/modules/sdk_github/src/Plugin/Sdk/GitHubConfigurationForm.php
@@ -1,16 +1,14 @@
 <?php
 
-namespace Drupal\sdk_github\Sdk;
+namespace Drupal\sdk_github\Plugin\Sdk;
 
-// Core components.
 use Drupal\Core\Form\FormStateInterface;
-// SDK API components.
-use Drupal\sdk\Api\Form\BaseForm;
+use Drupal\sdk\SdkPluginConfigurationFormBase;
 
 /**
- * Class GitHubForm.
+ * Form for SDK configuration.
  */
-class GitHubForm extends BaseForm {
+class GitHubConfigurationForm extends SdkPluginConfigurationFormBase {
 
   /**
    * Access scopes.
@@ -44,7 +42,7 @@ class GitHubForm extends BaseForm {
   /**
    * {@inheritdoc}
    */
-  public function form(array $form, FormStateInterface $form_state) {
+  public function form(array &$form, FormStateInterface $form_state) {
     $form['information'] = [
       '#markup' => $this->t('You can manage applications here: @link', [
         '@link' => static::externalLink('https://github.com/settings/developers'),
@@ -73,8 +71,6 @@ class GitHubForm extends BaseForm {
         '@link' => static::externalLink('https://developer.github.com/v3/oauth/#scopes'),
       ]),
     ];
-
-    return $form;
   }
 
 }
diff --git a/modules/sdk_instagram/sdk_instagram.module b/modules/sdk_instagram/sdk_instagram.module
deleted file mode 100644
index d59f2a3..0000000
--- a/modules/sdk_instagram/sdk_instagram.module
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * Instagram SDK.
- */
-
-use Drupal\sdk_instagram\Sdk\InstagramForm;
-use Drupal\sdk_instagram\Sdk\InstagramDeriver;
-
-/**
- * Implements hook_sdk_types().
- */
-function sdk_instagram_sdk_types() {
-  $types = [];
-
-  $types['instagram'] = [
-    'label' => t('Instagram'),
-    'classes' => [
-      'form' => InstagramForm::class,
-      'deriver' => InstagramDeriver::class,
-    ],
-  ];
-
-  return $types;
-}
diff --git a/modules/sdk_instagram/src/Sdk/InstagramDeriver.php b/modules/sdk_instagram/src/Plugin/Sdk/Instagram.php
similarity index 57%
rename from modules/sdk_instagram/src/Sdk/InstagramDeriver.php
rename to modules/sdk_instagram/src/Plugin/Sdk/Instagram.php
index c3bd335..213bdf5 100644
--- a/modules/sdk_instagram/src/Sdk/InstagramDeriver.php
+++ b/modules/sdk_instagram/src/Plugin/Sdk/Instagram.php
@@ -1,21 +1,24 @@
 <?php
 
-namespace Drupal\sdk_instagram\Sdk;
+namespace Drupal\sdk_instagram\Plugin\Sdk;
 
-// SDK API components.
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-// Instagram SDK.
-use MetzWeb\Instagram\Instagram;
+use Drupal\sdk\SdkPluginBase;
+use MetzWeb\Instagram\Instagram as InstagramSdk;
 
 /**
- * Class InstagramDeriver.
+ * SDK definition.
+ *
+ * @Sdk(
+ *   id = "instagram",
+ *   label = @Translation("Instagram"),
+ * )
  */
-class InstagramDeriver extends BaseDeriver {
+class Instagram extends SdkPluginBase {
 
   /**
    * SDK instance.
    *
-   * @var Instagram
+   * @var InstagramSdk
    */
   private $instance;
 
@@ -24,10 +27,12 @@ class InstagramDeriver extends BaseDeriver {
    */
   protected function getInstance() {
     if (NULL === $this->instance) {
-      $this->instance = new Instagram([
-        'apiKey' => $this->entity->settings['client_id'],
-        'apiSecret' => $this->entity->settings['client_secret'],
-        'apiCallback' => $this->entity->getCallbackUrl(),
+      $config = $this->getConfig();
+
+      $this->instance = new InstagramSdk([
+        'apiKey' => $config->settings['client_id'],
+        'apiSecret' => $config->settings['client_secret'],
+        'apiCallback' => $config->getCallbackUrl(),
       ]);
     }
 
@@ -52,7 +57,7 @@ class InstagramDeriver extends BaseDeriver {
    * {@inheritdoc}
    */
   public function loginUrl() {
-    return $this->getInstance()->getLoginUrl($this->entity->settings['scope']);
+    return $this->getInstance()->getLoginUrl($this->getConfig()->settings['scope']);
   }
 
   /**
diff --git a/modules/sdk_instagram/src/Sdk/InstagramForm.php b/modules/sdk_instagram/src/Plugin/Sdk/InstagramConfigurationForm.php
similarity index 85%
rename from modules/sdk_instagram/src/Sdk/InstagramForm.php
rename to modules/sdk_instagram/src/Plugin/Sdk/InstagramConfigurationForm.php
index c747bcb..5e00344 100644
--- a/modules/sdk_instagram/src/Sdk/InstagramForm.php
+++ b/modules/sdk_instagram/src/Plugin/Sdk/InstagramConfigurationForm.php
@@ -1,21 +1,19 @@
 <?php
 
-namespace Drupal\sdk_instagram\Sdk;
+namespace Drupal\sdk_instagram\Plugin\Sdk;
 
-// Core components.
 use Drupal\Core\Form\FormStateInterface;
-// SDK API components.
-use Drupal\sdk\Api\Form\BaseForm;
+use Drupal\sdk\SdkPluginConfigurationFormBase;
 
 /**
- * Class InstagramForm.
+ * Form for SDK configuration.
  */
-class InstagramForm extends BaseForm {
+class InstagramConfigurationForm extends SdkPluginConfigurationFormBase {
 
   /**
    * {@inheritdoc}
    */
-  public function form(array $form, FormStateInterface $form_state) {
+  public function form(array &$form, FormStateInterface $form_state) {
     $form['information'] = [
       '#markup' => $this->t('You can manage API clients here: @link', [
         '@link' => static::externalLink('https://www.instagram.com/developer/clients/manage'),
@@ -52,8 +50,6 @@ class InstagramForm extends BaseForm {
         'public_content' => $this->t('Read any public profile info and media on a user behalf'),
       ],
     ];
-
-    return $form;
   }
 
 }
diff --git a/modules/sdk_linkedin/sdk_linkedin.module b/modules/sdk_linkedin/sdk_linkedin.module
deleted file mode 100644
index dc23682..0000000
--- a/modules/sdk_linkedin/sdk_linkedin.module
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * LinkedIn SDK.
- */
-
-use Drupal\sdk_linkedin\Sdk\LinkedInForm;
-use Drupal\sdk_linkedin\Sdk\LinkedInDeriver;
-
-/**
- * Implements hook_sdk_types().
- */
-function sdk_linkedin_sdk_types() {
-  $types = [];
-
-  $types['linkedin'] = [
-    'label' => t('LinkedIn'),
-    'classes' => [
-      'form' => LinkedInForm::class,
-      'deriver' => LinkedInDeriver::class,
-    ],
-  ];
-
-  return $types;
-}
diff --git a/modules/sdk_linkedin/src/Sdk/LinkedInDeriver.php b/modules/sdk_linkedin/src/Plugin/Sdk/LinkedIn.php
similarity index 51%
rename from modules/sdk_linkedin/src/Sdk/LinkedInDeriver.php
rename to modules/sdk_linkedin/src/Plugin/Sdk/LinkedIn.php
index 14d321e..518f2d1 100644
--- a/modules/sdk_linkedin/src/Sdk/LinkedInDeriver.php
+++ b/modules/sdk_linkedin/src/Plugin/Sdk/LinkedIn.php
@@ -1,21 +1,24 @@
 <?php
 
-namespace Drupal\sdk_linkedin\Sdk;
+namespace Drupal\sdk_linkedin\Plugin\Sdk;
 
-// SDK API components.
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-// LinkedIn SDK.
-use Happyr\LinkedIn\LinkedIn;
+use Drupal\sdk\SdkPluginBase;
+use Happyr\LinkedIn\LinkedIn as LinkedInSdk;
 
 /**
- * Class LinkedInDeriver.
+ * SDK definition.
+ *
+ * @Sdk(
+ *   id = "linkedin",
+ *   label = @Translation("LinkedIn"),
+ * )
  */
-class LinkedInDeriver extends BaseDeriver {
+class LinkedIn extends SdkPluginBase {
 
   /**
    * SDK instance.
    *
-   * @var LinkedIn
+   * @var LinkedInSdk
    */
   private $instance;
 
@@ -24,9 +27,11 @@ class LinkedInDeriver extends BaseDeriver {
    */
   protected function getInstance() {
     if (NULL === $this->instance) {
-      $this->instance = new LinkedIn(
-        $this->entity->settings['client_id'],
-        $this->entity->settings['client_secret']
+      $config = $this->getConfig();
+
+      $this->instance = new LinkedInSdk(
+        $config->settings['client_id'],
+        $config->settings['client_secret']
       );
     }
 
@@ -51,9 +56,11 @@ class LinkedInDeriver extends BaseDeriver {
    * {@inheritdoc}
    */
   public function loginUrl() {
+    $config = $this->getConfig();
+
     return $this->getInstance()->getLoginUrl([
-      'scope' => $this->entity->settings['scope'],
-      'redirect_uri' => $this->entity->getCallbackUrl(),
+      'scope' => $config->settings['scope'],
+      'redirect_uri' => $config->getCallbackUrl(),
     ]);
   }
 
@@ -75,7 +82,19 @@ class LinkedInDeriver extends BaseDeriver {
   public function getTokenExpiration() {
     $token = $this->getToken();
 
-    return NULL === $token ? NULL : $token->getExpiresAt();
+    // Token not set or its life has ended.
+    if (NULL === $token) {
+      return NULL;
+    }
+
+    $expires = $token->getExpiresAt();
+
+    // If token has "NULL" as expiration this means it has no limitation.
+    if (NULL === $expires) {
+      return self::TOKEN_LIFE_UNLIMITED;
+    }
+
+    return $expires;
   }
 
 }
diff --git a/modules/sdk_linkedin/src/Sdk/LinkedInForm.php b/modules/sdk_linkedin/src/Plugin/Sdk/LinkedInConfigurationForm.php
similarity index 81%
rename from modules/sdk_linkedin/src/Sdk/LinkedInForm.php
rename to modules/sdk_linkedin/src/Plugin/Sdk/LinkedInConfigurationForm.php
index 02faec1..acc4386 100644
--- a/modules/sdk_linkedin/src/Sdk/LinkedInForm.php
+++ b/modules/sdk_linkedin/src/Plugin/Sdk/LinkedInConfigurationForm.php
@@ -1,21 +1,19 @@
 <?php
 
-namespace Drupal\sdk_linkedin\Sdk;
+namespace Drupal\sdk_linkedin\Plugin\Sdk;
 
-// Core components.
 use Drupal\Core\Form\FormStateInterface;
-// SDK API components.
-use Drupal\sdk\Api\Form\BaseForm;
+use Drupal\sdk\SdkPluginConfigurationFormBase;
 
 /**
- * Class LinkedInForm.
+ * Form for SDK configuration.
  */
-class LinkedInForm extends BaseForm {
+class LinkedInConfigurationForm extends SdkPluginConfigurationFormBase {
 
   /**
    * {@inheritdoc}
    */
-  public function form(array $form, FormStateInterface $form_state) {
+  public function form(array &$form, FormStateInterface $form_state) {
     $form['information'] = [
       '#markup' => $this->t('You can manage applications: @link', [
         '@link' => static::externalLink('https://www.linkedin.com/developer/apps'),
@@ -48,8 +46,6 @@ class LinkedInForm extends BaseForm {
         'rw_company_admin' => $this->t('Company admin [RW]'),
       ],
     ];
-
-    return $form;
   }
 
 }
diff --git a/modules/sdk_twitter/sdk_twitter.module b/modules/sdk_twitter/sdk_twitter.module
deleted file mode 100644
index 19d52d5..0000000
--- a/modules/sdk_twitter/sdk_twitter.module
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * @file
- * Twitter SDK.
- */
-
-use Drupal\sdk_twitter\Sdk\TwitterForm;
-use Drupal\sdk_twitter\Sdk\TwitterDeriver;
-
-/**
- * Implements hook_sdk_types().
- */
-function sdk_twitter_sdk_types() {
-  $types = [];
-
-  $types['twitter'] = [
-    'label' => t('Twitter'),
-    'classes' => [
-      'form' => TwitterForm::class,
-      'deriver' => TwitterDeriver::class,
-    ],
-  ];
-
-  return $types;
-}
diff --git a/modules/sdk_twitter/src/Plugin/Sdk/Twitter.php b/modules/sdk_twitter/src/Plugin/Sdk/Twitter.php
new file mode 100644
index 0000000..5b118d1
--- /dev/null
+++ b/modules/sdk_twitter/src/Plugin/Sdk/Twitter.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\sdk_twitter\Plugin\Sdk;
+
+use Drupal\sdk\SdkPluginBase;
+use Abraham\TwitterOAuth\TwitterOAuth as TwitterSdk;
+
+/**
+ * SDK definition.
+ *
+ * @Sdk(
+ *   id = "twitter",
+ *   label = @Translation("Twitter"),
+ * )
+ */
+class Twitter extends SdkPluginBase {
+
+  /**
+   * SDK instance.
+   *
+   * @var TwitterSdk
+   */
+  private $instance;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getInstance() {
+    if (NULL === $this->instance) {
+      $config = $this->getConfig();
+
+      $this->instance = new TwitterSdk(
+        $config->settings['consumer_key'],
+        $config->settings['consumer_secret'],
+        $config->settings['access_key'],
+        $config->settings['access_secret']
+      );
+    }
+
+    return $this->instance;
+  }
+
+}
diff --git a/modules/sdk_twitter/src/Sdk/TwitterForm.php b/modules/sdk_twitter/src/Plugin/Sdk/TwitterConfigurationForm.php
similarity index 76%
rename from modules/sdk_twitter/src/Sdk/TwitterForm.php
rename to modules/sdk_twitter/src/Plugin/Sdk/TwitterConfigurationForm.php
index a509bb8..9b21c5d 100644
--- a/modules/sdk_twitter/src/Sdk/TwitterForm.php
+++ b/modules/sdk_twitter/src/Plugin/Sdk/TwitterConfigurationForm.php
@@ -1,21 +1,19 @@
 <?php
 
-namespace Drupal\sdk_twitter\Sdk;
+namespace Drupal\sdk_twitter\Plugin\Sdk;
 
-// Core components.
 use Drupal\Core\Form\FormStateInterface;
-// SDK API components.
-use Drupal\sdk\Api\Form\BaseForm;
+use Drupal\sdk\SdkPluginConfigurationFormBase;
 
 /**
- * Class TwitterForm.
+ * Form for SDK configuration.
  */
-class TwitterForm extends BaseForm {
+class TwitterConfigurationForm extends SdkPluginConfigurationFormBase {
 
   /**
    * {@inheritdoc}
    */
-  public function form(array $form, FormStateInterface $form_state) {
+  public function form(array &$form, FormStateInterface $form_state) {
     $form['information'] = [
       '#markup' => $this->t('You can manage applications: @link', [
         '@link' => static::externalLink('https://apps.twitter.com'),
@@ -45,8 +43,6 @@ class TwitterForm extends BaseForm {
       '#title' => $this->t('Access secret'),
       '#required' => TRUE,
     ];
-
-    return $form;
   }
 
 }
diff --git a/modules/sdk_twitter/src/Sdk/TwitterDeriver.php b/modules/sdk_twitter/src/Sdk/TwitterDeriver.php
deleted file mode 100644
index 2b3df6d..0000000
--- a/modules/sdk_twitter/src/Sdk/TwitterDeriver.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-
-namespace Drupal\sdk_twitter\Sdk;
-
-// SDK API components.
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-// Twitter SDK.
-use Abraham\TwitterOAuth\TwitterOAuth;
-
-/**
- * Class TwitterDeriver.
- */
-class TwitterDeriver extends BaseDeriver {
-
-  /**
-   * SDK instance.
-   *
-   * @var TwitterOAuth
-   */
-  private $instance;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getInstance() {
-    if (NULL === $this->instance) {
-      $this->instance = new TwitterOAuth(
-        $this->entity->settings['consumer_key'],
-        $this->entity->settings['consumer_secret'],
-        $this->entity->settings['access_key'],
-        $this->entity->settings['access_secret']
-      );
-    }
-
-    return $this->instance;
-  }
-
-}
diff --git a/sdk.api.php b/sdk.api.php
deleted file mode 100644
index 1d3ab3e..0000000
--- a/sdk.api.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-
-/**
- * @file
- * SDK API.
- */
-
-/**
- * Define types of software development kits.
- *
- * @return array[]
- *   An associative array, keyed by machine name of SDK type and containing
- *   a list of the following values:
- *   - label<string>: human-readable label of SDK;
- *   - classes<string[]>:
- *     - form<string>: must be extended from "BaseForm".
- *     - deriver<string>: must be extended from "BaseDeriver".
- *
- * @see \Drupal\sdk\Api\Form\BaseForm
- * @see \Drupal\sdk\Api\Deriver\BaseDeriver
- */
-function hook_sdk_types() {
-  $types = [];
-
-  $types['paypal'] = [
-    'label' => t('PayPal'),
-    'classes' => [
-      'form' => PayPalForm::class,
-      'deriver' => PayPalDeriver::class,
-    ],
-  ];
-
-  return $types;
-}
diff --git a/sdk.info.yml b/sdk.info.yml
index 888510d..a2b81a9 100644
--- a/sdk.info.yml
+++ b/sdk.info.yml
@@ -4,6 +4,3 @@ package: SDK API
 type: module
 core: 8.x
 php: 5.6
-
-dependencies:
-  - composer_manager
diff --git a/sdk.links.menu.yml b/sdk.links.menu.yml
index 4e6e864..5d8d97e 100644
--- a/sdk.links.menu.yml
+++ b/sdk.links.menu.yml
@@ -2,4 +2,4 @@ entity.sdk.collection:
   title: SDK
   parent: system.admin_config_development
   route_name: entity.sdk.collection
-  description: Manage SDK configurations
+  description: Manage SDK configurations.
diff --git a/sdk.module b/sdk.module
index daea864..6d14a7a 100644
--- a/sdk.module
+++ b/sdk.module
@@ -2,13 +2,9 @@
 
 /**
  * @file
- * Social Network API.
+ * SDK API.
  */
 
-use Drupal\sdk\Api\Form\BaseForm;
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-use Drupal\sdk\Entity\SdkInterface;
-
 /**
  * Derive an instance of SDK.
  *
@@ -19,71 +15,17 @@ use Drupal\sdk\Entity\SdkInterface;
  *   An instance of SDK.
  */
 function sdk($type) {
-  return sdk_deriver($type)->derive();
-}
-
-/**
- * Instantiate SDK deriver.
- *
- * @param string $type
- *   One of SDK types.
- *
- * @return BaseDeriver
- *   An instance of SDK deriver.
- */
-function sdk_deriver($type) {
-  if (empty($type)) {
-    throw new InvalidArgumentException('You must specify the type of SDK.');
-  }
-
-  $types = sdk_types();
-
-  if (empty($types[$type])) {
-    throw new InvalidArgumentException(sprintf('SDK of "%s" type is undefined.', $type));
-  }
-
-  $entity = Drupal::entityTypeManager()
-    ->getStorage(SdkInterface::ENTITY_TYPE)
-    ->load($type);
-
-  if (NULL === $entity) {
-    throw new RuntimeException(sprintf('SDK of "%s" type is not configured.', $type));
-  }
-
-  return new $types[$type]['classes']['deriver']($entity);
+  return sdk_plugin_manager()->createInstance($type)->derive();
 }
 
 /**
- * Get list of defined SDK types.
+ * Returns an instance of the "plugin.manager.sdk" service.
  *
- * @return array[]
- *   SDK type definitions.
- *
- * @see hook_sdk_types()
+ * @return \Drupal\sdk\SdkPluginManager
+ *   Service instance.
  */
-function sdk_types() {
-  $cache_bin = Drupal::cache();
-  $cache_item = $cache_bin->get(__FUNCTION__);
-  $sdk_types = [];
-
-  if (FALSE === $cache_item) {
-    foreach (Drupal::moduleHandler()->invokeAll(__FUNCTION__) as $sdk => $info) {
-      if (
-        isset($info['label'], $info['classes']['form'], $info['classes']['deriver']) &&
-        is_subclass_of($info['classes']['form'], BaseForm::class) &&
-        is_subclass_of($info['classes']['deriver'], BaseDeriver::class)
-      ) {
-        $sdk_types[$sdk] = $info;
-      }
-    }
-
-    $cache_bin->set(__FUNCTION__, $sdk_types);
-  }
-  else {
-    $sdk_types = $cache_item->data;
-  }
-
-  return $sdk_types;
+function sdk_plugin_manager() {
+  return Drupal::service('plugin.manager.sdk');
 }
 
 /**
@@ -91,38 +33,37 @@ function sdk_types() {
  */
 function sdk_preprocess_page(array &$variables) {
   if (Drupal::currentUser()->isAuthenticated() && Drupal::service('router.admin_context')->isAdminRoute()) {
-    foreach (sdk_types() as $type => $info) {
-      try {
-        $deriver = sdk_deriver($type);
-      }
-      catch (Exception $e) {
+    $sdk_manager = sdk_plugin_manager();
+
+    foreach ($sdk_manager->getDefinitions() as $type => $plugin) {
+      $plugin = $sdk_manager->createInstance($type);
+      $config = $plugin->getConfig();
+
+      if (NULL === $config || !$plugin->isLoginCallbackOverridden()) {
         continue;
       }
 
-      if ($deriver->isLoginCallbackOverridden()) {
-        $expired_at = $deriver->getTokenExpiration();
-        $entity = $deriver->getEntity();
+      $expiration = $plugin->getTokenExpiration();
 
-        if ($expired_at instanceof DateTime) {
-          $date_diff = date_diff($expired_at, date_create(NULL, $expired_at->getTimezone()));
+      if ($expiration instanceof DateTime) {
+        $date_diff = date_diff($expiration, date_create(NULL, $expiration->getTimezone()));
 
-          if ($date_diff->days < 7) {
-            drupal_set_message(t('Access token for @label will be expired in @days days. Click on "@button" button <a href=":url">here</a> to renew it!', [
-              ':url' => $entity->url('edit-form'),
-              '@days' => $date_diff->days,
-              '@label' => $entity->label(),
-              '@button' => t('Save'),
-            ]), 'warning');
-          }
-        }
-        elseif (NULL === $expired_at) {
-          drupal_set_message(t('Access token is not set or expired for @label. Click on "@button" button <a href=":url">here</a> to create it!', [
-            ':url' => $entity->url('edit-form'),
-            '@label' => $entity->label(),
+        if ($date_diff->days < 7) {
+          drupal_set_message(t('Access token for @label will be expired in @days days. Click on "@button" button <a href=":url">here</a> to renew it!', [
+            ':url' => $config->url('edit-form'),
+            '@days' => $date_diff->days,
+            '@label' => $config->label(),
             '@button' => t('Save'),
           ]), 'warning');
         }
       }
+      elseif (NULL === $expiration) {
+        drupal_set_message(t('Access token is not set or expired for @label. Click on "@button" button <a href=":url">here</a> to create it!', [
+          ':url' => $config->url('edit-form'),
+          '@label' => $config->label(),
+          '@button' => t('Save'),
+        ]), 'warning');
+      }
     }
   }
 }
diff --git a/sdk.routing.yml b/sdk.routing.yml
index ce85152..bce5a7c 100644
--- a/sdk.routing.yml
+++ b/sdk.routing.yml
@@ -9,31 +9,31 @@ sdk.callback:
 entity.sdk.collection:
   path: /admin/config/development/sdk
   defaults:
-    _entity_list: sdk
     _title: SDK
+    _entity_list: sdk
   requirements:
     _permission: administer sdk configurations
 
 entity.sdk.add_form:
   path: /admin/config/development/sdk/add
   defaults:
-    _entity_form: sdk.default
     _title: Add configuration
+    _entity_form: sdk.default
   requirements:
     _permission: administer sdk configurations
 
 entity.sdk.edit_form:
   path: /admin/config/development/sdk/{sdk}
   defaults:
-    _entity_form: sdk.default
     _title: Edit
+    _entity_form: sdk.default
   requirements:
     _permission: administer sdk configurations
 
 entity.sdk.delete_form:
   path: /admin/config/development/sdk/{sdk}/delete
   defaults:
-    _entity_form: sdk.delete
     _title: Delete
+    _entity_form: sdk.delete
   requirements:
     _permission: administer sdk configurations
diff --git a/sdk.services.yml b/sdk.services.yml
new file mode 100644
index 0000000..4ea7934
--- /dev/null
+++ b/sdk.services.yml
@@ -0,0 +1,7 @@
+services:
+  plugin.manager.sdk:
+    class: Drupal\sdk\SdkPluginManager
+    arguments:
+      - '@container.namespaces'
+      - '@cache.discovery'
+      - '@module_handler'
diff --git a/src/Annotation/Sdk.php b/src/Annotation/Sdk.php
new file mode 100644
index 0000000..c86c108
--- /dev/null
+++ b/src/Annotation/Sdk.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\sdk\Annotation;
+
+use Drupal\sdk\SdkPluginDefinition;
+use Drupal\Component\Annotation\AnnotationBase;
+
+/**
+ * Annotation for defining SDK.
+ *
+ * @Annotation
+ */
+class Sdk extends AnnotationBase {
+
+  /**
+   * Human-readable name of SDK.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $label;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get() {
+    return (new SdkPluginDefinition())
+      ->setId($this->id)
+      ->setLabel($this->label->get())
+      ->setProvider($this->provider)
+      ->setClass($this->class)
+      ->setFormClass($this->class . 'ConfigurationForm');
+  }
+
+}
diff --git a/src/Api/Api.php b/src/Api/Api.php
deleted file mode 100644
index c709789..0000000
--- a/src/Api/Api.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-namespace Drupal\sdk\Api;
-
-use Drupal\sdk\Entity\Sdk;
-
-/**
- * Class Api.
- */
-abstract class Api {
-
-  /**
-   * SDK configuration.
-   *
-   * @var Sdk
-   */
-  protected $entity;
-
-  /**
-   * Api constructor.
-   *
-   * @param Sdk $entity
-   *   SDK configuration.
-   */
-  public function __construct(Sdk $entity) {
-    $this->entity = $entity;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getEntity() {
-    return $this->entity;
-  }
-
-}
diff --git a/src/Api/Deriver/BaseDeriver.php b/src/Api/Deriver/BaseDeriver.php
deleted file mode 100644
index 9c1666d..0000000
--- a/src/Api/Deriver/BaseDeriver.php
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-
-namespace Drupal\sdk\Api\Deriver;
-
-// Core components.
-use Drupal\Core\Url;
-use Drupal\Core\KeyValueStore\DatabaseStorageExpirable;
-use Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory;
-use Drupal\Core\Routing\TrustedRedirectResponse;
-// SDK API components.
-use Drupal\sdk\Api\Api;
-
-/**
- * Class BaseDeriver.
- */
-abstract class BaseDeriver extends Api {
-
-  const TOKEN_LIFE_UNLIMITED = -1;
-
-  /**
-   * Returns an instance of SDK.
-   *
-   * @return object
-   *   SDK instance.
-   */
-  abstract protected function getInstance();
-
-  /**
-   * Derive an instance of SDK.
-   *
-   * @return object
-   *   Derived instance of SDK.
-   */
-  public function derive() {
-    return $this->getInstance();
-  }
-
-  /**
-   * Return URL to redirect to for login.
-   *
-   * @return string
-   *   URL to redirect to for login and token obtaining.
-   */
-  public function loginUrl() {
-    if (!$this->isLoginCallbackOverridden()) {
-      throw new \RuntimeException(sprintf('The "%s" method must be overridden by "%s" class', 'loginCallback', static::class));
-    }
-
-    return '';
-  }
-
-  /**
-   * Process result of visiting the login URL.
-   */
-  public function loginCallback() {
-  }
-
-  /**
-   * Check whether "loginCallback" method has been overridden.
-   *
-   * @return bool
-   *   A state of check.
-   */
-  final public function isLoginCallbackOverridden() {
-    return (new \ReflectionMethod($this, 'loginCallback'))->getDeclaringClass()->getName() !== self::class;
-  }
-
-  /**
-   * Returns storage.
-   *
-   * @return DatabaseStorageExpirable
-   *   Database storage for SDK purposes.
-   */
-  public static function storage() {
-    $service = \Drupal::service('keyvalue.expirable.database');
-
-    if ($service instanceof KeyValueDatabaseExpirableFactory) {
-      $service->garbageCollection();
-    }
-
-    return $service->get('sdk_storage');
-  }
-
-  /**
-   * Returns token.
-   *
-   * @return mixed|null
-   *   Representation of a token or NULL if it was not set.
-   */
-  public function getToken() {
-    return static::storage()->get($this->entity->id());
-  }
-
-  /**
-   * Set token.
-   *
-   * @param object|string $value
-   *   Representation of a token.
-   * @param int|null $expire
-   *   Expiration timestamp.
-   */
-  public function setToken($value, $expire = NULL) {
-    if (!empty($value)) {
-      $storage = static::storage();
-
-      if (NULL === $expire) {
-        $storage->set($this->entity->id(), $value);
-      }
-      else {
-        $storage->setWithExpire($this->entity->id(), $value, $expire - REQUEST_TIME);
-      }
-    }
-  }
-
-  /**
-   * Returns a date when token will no longer be valid.
-   *
-   * @return \DateTime|null|int
-   *   DateTime object of expiration, NULL if token expired or
-   *   "self::TOKEN_LIFE_UNLIMITED" if token has no limitation.
-   */
-  public function getTokenExpiration() {
-    return NULL;
-  }
-
-  /**
-   * Trigger "sdk.callback" which must implement token requesting/receiving.
-   *
-   * @param string|Url|null $destination
-   *   Destination path where user should be after processing.
-   *
-   * @return TrustedRedirectResponse
-   *   An instance of response.
-   */
-  public function requestToken($destination = NULL) {
-    if (empty($destination)) {
-      $destination = \Drupal::request()->getUri();
-    }
-    elseif ($destination instanceof Url) {
-      $destination = $destination->toString();
-    }
-
-    $_SESSION['destination'] = $destination;
-
-    return new TrustedRedirectResponse($this->loginUrl());
-  }
-
-}
diff --git a/src/Api/Form/BaseForm.php b/src/Api/Form/BaseForm.php
deleted file mode 100644
index baf83fa..0000000
--- a/src/Api/Form/BaseForm.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-namespace Drupal\sdk\Api\Form;
-
-// Core components.
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-// SDK API components.
-use Drupal\sdk\Api\Api;
-use Drupal\sdk\Api\ExternalLink;
-
-/**
- * Class BaseForm.
- */
-abstract class BaseForm extends Api {
-
-  use ExternalLink;
-  use StringTranslationTrait;
-
-  /**
-   * {@inheritdoc}
-   */
-  abstract public function form(array $form, FormStateInterface $form_state);
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array $form, FormStateInterface $form_state) {
-  }
-
-}
diff --git a/src/Controller/SdkController.php b/src/Controller/SdkController.php
index 047e29f..bedb458 100644
--- a/src/Controller/SdkController.php
+++ b/src/Controller/SdkController.php
@@ -2,10 +2,10 @@
 
 namespace Drupal\sdk\Controller;
 
-// Core components.
 use Drupal\Core\Controller\ControllerBase;
-// Symfony components.
 use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\sdk\SdkPluginManager;
 
 /**
  * Class SdkController.
@@ -13,11 +13,35 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
 class SdkController extends ControllerBase {
 
   /**
+   * Instance of the "plugin.manager.sdk" service.
+   *
+   * @var SdkPluginManager
+   */
+  private $pluginManager;
+
+  /**
+   * SdkController constructor.
+   *
+   * @param \Drupal\sdk\SdkPluginManager $plugin_manager
+   *   Instance of the "plugin.manager.sdk" service.
+   */
+  public function __construct(SdkPluginManager $plugin_manager) {
+    $this->pluginManager = $plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('plugin.manager.sdk'));
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function callback($sdk) {
     try {
-      $redirect = sdk_deriver($sdk)->loginCallback();
+      $redirect = $this->pluginManager->createInstance($sdk)->loginCallback();
 
       if ($redirect instanceof RedirectResponse) {
         return $redirect;
diff --git a/src/Entity/Form/Sdk/DefaultForm.php b/src/Entity/Form/Sdk/DefaultForm.php
index 7646c36..f7c4076 100644
--- a/src/Entity/Form/Sdk/DefaultForm.php
+++ b/src/Entity/Form/Sdk/DefaultForm.php
@@ -2,35 +2,57 @@
 
 namespace Drupal\sdk\Entity\Form\Sdk;
 
-// Core components.
 use Drupal\Core\Render\Element;
 use Drupal\Core\Entity\EntityForm;
 use Drupal\Core\Form\FormStateInterface;
-// SDK API components.
-use Drupal\sdk\Entity\Sdk;
-use Drupal\sdk\Api\Deriver\BaseDeriver;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\sdk\SdkPluginManager;
 
 /**
- * Class DefaultForm.
+ * Default entity form for every SDK.
  *
- * @method Sdk getEntity()
+ * @method \Drupal\sdk\Entity\Sdk getEntity()
  *
- * @property Sdk $entity
+ * @property \Drupal\sdk\Entity\Sdk $entity
  */
 class DefaultForm extends EntityForm {
 
   /**
+   * Instance of the "plugin.manager.sdk" service.
+   *
+   * @var SdkPluginManager
+   */
+  protected $pluginManager;
+
+  /**
+   * DefaultForm constructor.
+   *
+   * @param \Drupal\sdk\SdkPluginManager $plugin_manager
+   *   Instance of the "plugin.manager.sdk" service.
+   */
+  public function __construct(SdkPluginManager $plugin_manager) {
+    $this->pluginManager = $plugin_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('plugin.manager.sdk'));
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function form(array $form, FormStateInterface $form_state) {
+    $this->entity->set('id', $form_state->getValue('type', $this->entity->id()));
+
+    $entity_id = $this->entity->id();
     $form_id = strtolower(strtr(static::class, '\\', '_'));
-    $types = sdk_types();
     $options = [];
 
-    $this->entity->type = $form_state->getValue('type', $this->entity->type);
-
-    foreach ($types as $option => $info) {
-      $options[$option] = $info['label'];
+    foreach ($this->pluginManager->getDefinitions() as $option => $plugin) {
+      $options[$option] = $plugin->getLabel();
     }
 
     $form['type'] = [
@@ -40,29 +62,24 @@ class DefaultForm extends EntityForm {
       '#required' => TRUE,
       '#disabled' => !$this->entity->isNew(),
       '#empty_option' => $this->t('- None -'),
-      '#default_value' => $this->entity->type,
+      '#default_value' => $entity_id,
       '#ajax' => [
         'callback' => '::reloadForm',
         'wrapper' => $form_id,
       ],
     ];
 
-    if (!empty($this->entity->type)) {
-      static::populateControllers($this->entity, $form_state, $types);
-
+    if (!empty($entity_id)) {
       $form['label'] = [
         '#type' => 'hidden',
-        '#value' => $types[$this->entity->type]['label'],
+        '#value' => $options[$entity_id],
       ];
 
-      $form['callbackUri'] = [
-        '#type' => 'hidden',
-        '#value' => $this->entity->getCallbackUrl(FALSE),
-      ];
+      $form['settings'] = [];
 
-      $form['settings'] = $this->invoke(__FUNCTION__, $form_state);
+      $this->invoke(__FUNCTION__, $form_state, $form['settings']);
 
-      static::addWrapper($form['settings'], $form_id . '_' . $this->entity->type);
+      static::addWrapper($form['settings'], $form_id . '_' . $entity_id);
       static::processSettings($form['settings'], $this->entity->settings);
     }
 
@@ -103,14 +120,11 @@ class DefaultForm extends EntityForm {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     parent::submitForm($form, $form_state);
 
-    $this->invoke(__FUNCTION__, $form_state, $form['settings']);
-
-    /* @var BaseDeriver $deriver */
-    $deriver = $form_state->getTemporaryValue(['controller', 'deriver']);
+    $plugin = $this->invoke(__FUNCTION__, $form_state, $form['settings']);
     $redirect = $this->entity->toUrl('collection');
 
-    if ($deriver->isLoginCallbackOverridden()) {
-      $form_state->setResponse($deriver->requestToken($redirect));
+    if ($plugin->isLoginCallbackOverridden()) {
+      $form_state->setResponse($plugin->requestToken($redirect));
     }
     else {
       $form_state->setRedirectUrl($redirect);
@@ -127,41 +141,20 @@ class DefaultForm extends EntityForm {
    * @param array[] $form
    *   Form element definitions.
    *
-   * @return array|null
-   *   Form element definitions or NULL in case of validation or submission.
+   * @return \Drupal\sdk\SdkPluginBase
+   *   SDK plugin.
    */
   protected function invoke($method, FormStateInterface $form_state, array &$form = []) {
-    $controller_form = $form_state->getTemporaryValue(['controller', 'form']);
+    $plugin =& $form_state->getTemporaryValue('sdk');
 
-    if (NULL === $controller_form) {
-      static::populateControllers($this->entity, $form_state);
+    if (NULL === $plugin) {
+      $plugin = $this->pluginManager->createInstance($this->entity->id());
+      $plugin->setConfig($this->entity);
     }
 
-    $controller_form = $form_state->getTemporaryValue(['controller', 'form']);
-
-    // @codingStandardsIgnoreStart
-    return empty($controller_form) ? [] : call_user_func_array([$controller_form, $method], [&$form, $form_state]);
-    // @codingStandardsIgnoreEnd
-  }
+    $plugin->getConfigurationForm()->{$method}($form, $form_state);
 
-  /**
-   * Populate SDK controllers into a state of form.
-   *
-   * @param Sdk $entity
-   *   An instance of entity object.
-   * @param FormStateInterface $form_state
-   *   A state of form.
-   * @param array|null $types
-   *   List of SDK type definitions or NULL.
-   */
-  protected static function populateControllers(Sdk $entity, FormStateInterface $form_state, array $types = NULL) {
-    if (NULL === $types) {
-      $types = sdk_types();
-    }
-
-    foreach ($types[$entity->type]['classes'] as $controller => $class) {
-      $form_state->setTemporaryValue(['controller', $controller], new $class($entity));
-    }
+    return $plugin;
   }
 
   /**
@@ -182,9 +175,7 @@ class DefaultForm extends EntityForm {
       }
 
       if (is_array($form[$child])) {
-        // @codingStandardsIgnoreStart
         call_user_func_array(__METHOD__, [&$form[$child], &$settings[$child]]);
-        // @codingStandardsIgnoreEnd
       }
     }
   }
diff --git a/src/Entity/ListBuilder/Sdk/DefaultListBuilder.php b/src/Entity/ListBuilder/Sdk/DefaultListBuilder.php
index 95ce0b0..2b582d8 100644
--- a/src/Entity/ListBuilder/Sdk/DefaultListBuilder.php
+++ b/src/Entity/ListBuilder/Sdk/DefaultListBuilder.php
@@ -2,12 +2,11 @@
 
 namespace Drupal\sdk\Entity\ListBuilder\Sdk;
 
-// Core components.
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
 
 /**
- * Class DefaultListBuilder.
+ * Default list builder for overview page.
  */
 class DefaultListBuilder extends ConfigEntityListBuilder {
 
diff --git a/src/Entity/Sdk.php b/src/Entity/Sdk.php
index 44e5f0a..c3c567d 100644
--- a/src/Entity/Sdk.php
+++ b/src/Entity/Sdk.php
@@ -2,12 +2,11 @@
 
 namespace Drupal\sdk\Entity;
 
-// Core components.
 use Drupal\Core\Url;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 
 /**
- * Class Sdk.
+ * SDK entity.
  *
  * @ConfigEntityType(
  *   id = "sdk",
@@ -22,7 +21,7 @@ use Drupal\Core\Config\Entity\ConfigEntityBase;
  *     },
  *   },
  *   entity_keys = {
- *     "id" = "type",
+ *     "id" = "id",
  *     "label" = "label",
  *   },
  *   links = {
@@ -39,7 +38,7 @@ class Sdk extends ConfigEntityBase implements SdkInterface {
    *
    * @var string
    */
-  public $type = '';
+  public $id = '';
   /**
    * Human-readable label.
    *
@@ -52,12 +51,6 @@ class Sdk extends ConfigEntityBase implements SdkInterface {
    * @var array
    */
   public $settings = [];
-  /**
-   * Available callback URL for authentications.
-   *
-   * @var string
-   */
-  public $callbackUri = '';
 
   /**
    * {@inheritdoc}
@@ -83,11 +76,15 @@ class Sdk extends ConfigEntityBase implements SdkInterface {
    *   An absolute callback URL.
    */
   public function getCallbackUrl($absolute = TRUE) {
-    if (empty($this->type)) {
+    $id = $this->id();
+
+    if (empty($id)) {
       throw new \RuntimeException('You must set the type of SDK before continue.');
     }
 
-    return (new Url('sdk.callback', ['sdk' => $this->type], ['absolute' => $absolute]))->toString(TRUE)->getGeneratedUrl();
+    return (new Url('sdk.callback', ['sdk' => $id], ['absolute' => $absolute]))
+      ->toString(TRUE)
+      ->getGeneratedUrl();
   }
 
 }
diff --git a/src/Api/ExternalLink.php b/src/ExternalLink.php
similarity index 83%
rename from src/Api/ExternalLink.php
rename to src/ExternalLink.php
index 51a3aaf..f9fc78d 100644
--- a/src/Api/ExternalLink.php
+++ b/src/ExternalLink.php
@@ -1,11 +1,9 @@
 <?php
 
-namespace Drupal\sdk\Api;
+namespace Drupal\sdk;
 
-// Core components.
 use Drupal\Core\Url;
 use Drupal\Core\Link;
-use Drupal\Core\GeneratedLink;
 
 /**
  * Trait ExternalLink.
@@ -20,7 +18,7 @@ trait ExternalLink {
    * @param string|null $text
    *   Link text. URL will be used if not specified.
    *
-   * @return GeneratedLink
+   * @return \Drupal\Core\GeneratedLink
    *   Generated link.
    */
   public static function externalLink($url, $text = NULL) {
diff --git a/src/SdkPluginBase.php b/src/SdkPluginBase.php
new file mode 100644
index 0000000..e3dc4c9
--- /dev/null
+++ b/src/SdkPluginBase.php
@@ -0,0 +1,249 @@
+<?php
+
+namespace Drupal\sdk;
+
+use Drupal\Core\Url;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Routing\TrustedRedirectResponse;
+use Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory;
+use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
+use Drupal\Component\Datetime\TimeInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\sdk\Entity\Sdk;
+use Drupal\sdk\Entity\SdkInterface;
+
+/**
+ * Base SDK plugin.
+ *
+ * @property SdkPluginDefinition $pluginDefinition
+ */
+abstract class SdkPluginBase extends PluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Marker for unexpirable tokens.
+   */
+  const TOKEN_LIFE_UNLIMITED = -1;
+
+  /**
+   * SDK configuration.
+   *
+   * @var \Drupal\sdk\Entity\Sdk
+   */
+  private $config;
+  /**
+   * Instance of the "keyvalue.expirable.database" service.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $storage;
+  /**
+   * Instance of the "request_stack" service.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+  /**
+   * Storage of configuration entities.
+   *
+   * @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface
+   */
+  protected $configEntityStorage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(
+    array $configuration,
+    $plugin_id,
+    SdkPluginDefinition $plugin_definition,
+    KeyValueExpirableFactoryInterface $key_value_storage,
+    RequestStack $request_stack,
+    EntityTypeManagerInterface $entity_type_manager
+  ) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->storage = $key_value_storage;
+    $this->requestStack = $request_stack;
+    $this->configEntityStorage = $entity_type_manager->getStorage(SdkInterface::ENTITY_TYPE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('keyvalue.expirable.database'),
+      $container->get('request_stack'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfig() {
+    if (NULL === $this->config) {
+      $this->config = $this->configEntityStorage->load($this->pluginId);
+    }
+
+    return $this->config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfig(Sdk $config) {
+    $this->config = $config;
+  }
+
+  /**
+   * Returns an instance of SDK.
+   *
+   * @return object
+   *   SDK instance.
+   */
+  abstract protected function getInstance();
+
+  /**
+   * Derive an instance of SDK.
+   *
+   * @return object
+   *   Derived instance of SDK.
+   */
+  public function derive() {
+    return $this->getInstance();
+  }
+
+  /**
+   * Return URL to redirect to for login.
+   *
+   * @return string
+   *   URL to redirect to for login and token obtaining.
+   */
+  public function loginUrl() {
+    if (!$this->isLoginCallbackOverridden()) {
+      throw new \RuntimeException(sprintf('The "%s" method must be overridden by "%s" class', 'loginCallback', static::class));
+    }
+
+    return '';
+  }
+
+  /**
+   * Process result of visiting the login URL.
+   */
+  public function loginCallback() {
+  }
+
+  /**
+   * Check whether "loginCallback" method has been overridden.
+   *
+   * @return bool
+   *   A state of check.
+   */
+  final public function isLoginCallbackOverridden() {
+    return (new \ReflectionMethod($this, 'loginCallback'))->getDeclaringClass()->getName() !== self::class;
+  }
+
+  /**
+   * Get instance of configuration form.
+   *
+   * @return \Drupal\sdk\SdkPluginConfigurationFormBase
+   *   SDK configuration form.
+   *
+   * @see \Drupal\sdk\Entity\Form\Sdk\DefaultForm::invoke()
+   */
+  final public function getConfigurationForm() {
+    static $forms = [];
+
+    $class = $this->pluginDefinition->getFormClass();
+
+    if (empty($forms[$class])) {
+      $forms[$class] = new $class($this->getConfig());
+    }
+
+    return $forms[$class];
+  }
+
+  /**
+   * Returns token.
+   *
+   * @return mixed|null
+   *   Representation of a token or NULL if it was not set.
+   */
+  public function getToken() {
+    return $this->getStorage()->get($this->pluginId);
+  }
+
+  /**
+   * Set token.
+   *
+   * @param object|string $value
+   *   Representation of a token.
+   * @param int|null $expire
+   *   Expiration timestamp.
+   */
+  public function setToken($value, $expire = NULL) {
+    if (!empty($value)) {
+      if (NULL === $expire) {
+        $this->getStorage()->set($this->pluginId, $value);
+      }
+      else {
+        $this->getStorage()->setWithExpire($this->pluginId, $value, $expire - REQUEST_TIME);
+      }
+    }
+  }
+
+  /**
+   * Returns a date when token will no longer be valid.
+   *
+   * @return \DateTime|null|int
+   *   DateTime object of expiration, NULL if token expired or
+   *   "self::TOKEN_LIFE_UNLIMITED" if token has no limitation.
+   */
+  public function getTokenExpiration() {
+    return NULL;
+  }
+
+  /**
+   * Trigger "sdk.callback" which must implement token requesting/receiving.
+   *
+   * @param string|Url|null $destination
+   *   Destination path where user should be after processing.
+   *
+   * @return TrustedRedirectResponse
+   *   An instance of response.
+   */
+  public function requestToken($destination = NULL) {
+    if (empty($destination)) {
+      $destination = $this->requestStack->getCurrentRequest()->getUri();
+    }
+    elseif ($destination instanceof Url) {
+      $destination = $destination->toString();
+    }
+
+    $_SESSION['destination'] = $destination;
+
+    return new TrustedRedirectResponse($this->loginUrl());
+  }
+
+  /**
+   * Returns storage for SDK tokens.
+   *
+   * @return \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   *   Storage for SDK tokens.
+   */
+  private function getStorage() {
+    if ($this->storage instanceof KeyValueDatabaseExpirableFactory) {
+      $this->storage->garbageCollection();
+    }
+
+    return $this->storage->get('sdk_tokens_storage');
+  }
+
+}
diff --git a/src/SdkPluginConfigurationFormBase.php b/src/SdkPluginConfigurationFormBase.php
new file mode 100644
index 0000000..4f7a48e
--- /dev/null
+++ b/src/SdkPluginConfigurationFormBase.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\sdk;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\sdk\Entity\Sdk;
+
+/**
+ * Base configuration form of SDK.
+ */
+abstract class SdkPluginConfigurationFormBase {
+
+  use ExternalLink;
+  use StringTranslationTrait;
+
+  /**
+   * SDK configuration.
+   *
+   * @var \Drupal\sdk\Entity\Sdk
+   */
+  protected $config;
+
+  /**
+   * SdkPluginConfigurationFormBase constructor.
+   *
+   * @param \Drupal\sdk\Entity\Sdk $config
+   *   SDK configuration.
+   *
+   * @see \Drupal\sdk\SdkPluginBase::getConfigurationForm()
+   */
+  public function __construct(Sdk $config) {
+    $this->config = $config;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function form(array &$form, FormStateInterface $form_state);
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array $form, FormStateInterface $form_state) {
+  }
+
+}
diff --git a/src/SdkPluginDefinition.php b/src/SdkPluginDefinition.php
new file mode 100644
index 0000000..dbbbb94
--- /dev/null
+++ b/src/SdkPluginDefinition.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Drupal\sdk;
+
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
+
+/**
+ * SDK plugin definition.
+ */
+final class SdkPluginDefinition implements PluginDefinitionInterface {
+
+  /**
+   * SDK type.
+   *
+   * @var string
+   */
+  protected $id;
+  /**
+   * Human-readable name of SDK.
+   *
+   * @var string
+   */
+  protected $label;
+  /**
+   * The plugin provider.
+   *
+   * @var string
+   */
+  protected $provider;
+  /**
+   * A fully qualified class name of deriver.
+   *
+   * @var string
+   */
+  protected $class;
+  /**
+   * A fully qualified class name of configuration form.
+   *
+   * @var string
+   */
+  protected $formClass;
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return static
+   */
+  public function setId($id) {
+    $this->id = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function id() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return static
+   */
+  public function setLabel($label) {
+    $this->label = $label;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return $this->label;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return static
+   */
+  public function setProvider($provider) {
+    $this->provider = $provider;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return $this->provider;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return static
+   */
+  public function setClass($class) {
+    if (!is_subclass_of($class, SdkPluginBase::class)) {
+      throw new \InvalidArgumentException(sprintf(
+        'SDK plugin "%s", provided by "%s", must extends "%s"',
+        $this->id,
+        $this->provider,
+        SdkPluginBase::class
+      ));
+    }
+
+    $this->class = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return $this->class;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return static
+   */
+  public function setFormClass($class) {
+    if (!is_subclass_of($class, SdkPluginConfigurationFormBase::class)) {
+      throw new \InvalidArgumentException(sprintf(
+        'SDK configuration form of "%s" plugin, provided by "%s", must extends "%s"',
+        $this->id,
+        $this->provider,
+        SdkPluginConfigurationFormBase::class
+      ));
+    }
+
+    $this->formClass = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormClass() {
+    return $this->formClass;
+  }
+
+}
diff --git a/src/SdkPluginManager.php b/src/SdkPluginManager.php
new file mode 100644
index 0000000..faa7b80
--- /dev/null
+++ b/src/SdkPluginManager.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\sdk;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\sdk\Annotation\Sdk;
+
+/**
+ * Manager of SDK plugins.
+ *
+ * @method SdkPluginBase createInstance($plugin_id, array $configuration = [])
+ * @method SdkPluginDefinition getDefinition($plugin_id, $exception_on_invalid = TRUE)
+ * @method SdkPluginDefinition[] getDefinitions()
+ */
+class SdkPluginManager extends DefaultPluginManager {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/Sdk', $namespaces, $module_handler, SdkPluginBase::class, Sdk::class);
+
+    $this->setCacheBackend($cache_backend, 'sdk_plugins');
+  }
+
+}
diff --git a/tests/src/Unit/Api/ApiTest.php b/tests/src/Unit/Api/ApiTest.php
deleted file mode 100644
index 48de747..0000000
--- a/tests/src/Unit/Api/ApiTest.php
+++ /dev/null
@@ -1,213 +0,0 @@
-<?php
-
-namespace Drupal\Tests\sdk\Unit\Api;
-
-// Testing dependencies.
-use Drupal\Tests\UnitTestCase;
-// Core components.
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\KeyValueStore\DatabaseStorageExpirable;
-use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
-// SDK API components.
-use Drupal\sdk\Api\Api;
-use Drupal\sdk\Api\Form\BaseForm;
-use Drupal\sdk\Api\Deriver\BaseDeriver;
-use Drupal\sdk\Entity\Sdk;
-
-/**
- * Tests SDK API abstractions.
- *
- * @covers \Drupal\sdk\Api\Api
- * @covers \Drupal\sdk\Api\Form\BaseForm
- * @covers \Drupal\sdk\Api\Deriver\BaseDeriver
- *
- * @group sdk-api
- */
-class ApiTest extends UnitTestCase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-
-    $container = new ContainerBuilder();
-    $container->set('keyvalue.expirable.database', $this->getMock(KeyValueExpirableFactoryInterface::class));
-
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * Returns constructed mock.
-   *
-   * @param string $class
-   *   Class to mock.
-   * @param mixed $argument
-   *   An argument for class constructor.
-   *
-   * @return \PHPUnit_Framework_MockObject_MockObject
-   *   Mock instance.
-   */
-  public function mock($class, $argument = NULL) {
-    return $this->getMockBuilder($class)
-      ->setConstructorArgs([$argument ?: $this->getSdk()])
-      ->getMock();
-  }
-
-  /**
-   * Returns mock instance of Sdk.
-   *
-   * @return Sdk
-   *   Mock instance of Sdk.
-   */
-  protected function getSdk() {
-    static $sdk;
-
-    if (NULL === $sdk) {
-      $sdk = $this->mock(Sdk::class, [
-        'type' => 'test_sdk',
-        'label' => 'SDK',
-      ]);
-    }
-
-    return $sdk;
-  }
-
-  /**
-   * Return form element definitions.
-   *
-   * @return array[]
-   *   Form element definitions.
-   */
-  protected function getForm() {
-    $form = [];
-
-    $form['option'] = [
-      '#type' => 'textfield',
-      '#title' => 'Option',
-    ];
-
-    return $form;
-  }
-
-  /**
-   * Returns mock instance of \Drupal\Core\Form\FormState.
-   *
-   * @return FormStateInterface|\PHPUnit_Framework_MockObject_MockObject
-   *   Mock instance of Drupal\Core\Form\FormState.
-   */
-  protected function getFormState() {
-    return $this->getMock(FormStateInterface::class);
-  }
-
-  /**
-   * Returns values for API testing.
-   *
-   * @return array[]
-   *   An array of arrays with arguments for API testing.
-   */
-  public function provider() {
-    $items = [];
-
-    foreach ([Api::class, BaseForm::class, BaseDeriver::class] as $class) {
-      $items[][] = $this->mock($class);
-    }
-
-    return $items;
-  }
-
-  /**
-   * Tests all SDK API abstractions.
-   *
-   * @dataProvider provider
-   *
-   * @param Api|\PHPUnit_Framework_MockObject_MockObject $api
-   *   Mock instance of API abstraction.
-   */
-  public function testApi(\PHPUnit_Framework_MockObject_MockObject $api) {
-    $sdk = $this->getSdk();
-    $api->expects(static::once())
-      ->method('getEntity')
-      ->willReturn($sdk);
-
-    $this->assertSame($sdk, $api->getEntity());
-    $this->assertInstanceOf(Api::class, $api);
-    $this->assertAttributeEquals($sdk, 'entity', $api);
-  }
-
-  /**
-   * Tests for specific methods of base form builder.
-   *
-   * @covers \Drupal\sdk\Api\Form\BaseForm::form
-   * @covers \Drupal\sdk\Api\Form\BaseForm::submitForm
-   * @covers \Drupal\sdk\Api\Form\BaseForm::validateForm
-   */
-  public function testBaseForm() {
-    $form = $this->getForm();
-    $form_state = $this->getFormState();
-    $base_form = $this->mock(BaseForm::class);
-
-    foreach ([
-      // Implementation of form must return form element definitions.
-      'form' => $form,
-      // Form submission handler must return nothing.
-      'submitForm' => NULL,
-      // Form validation handler must return nothing as well.
-      'validateForm' => NULL,
-    ] as $method => $return) {
-      $base_form
-        // Tell, that we will trigger method once.
-        ->expects(static::once())
-        // Set name of method which must be executed.
-        ->method($method)
-        // - First argument must be an array with form elements or empty.
-        // - Second argument - is always an object representing state of form.
-        ->with(static::isType('array'), static::isInstanceOf($form_state))
-        // Tell, that we expecting a specific result to return by method.
-        ->willReturn($return);
-
-      // Call method with with set of arguments and check returned result.
-      $this->assertSame($return, $base_form->{$method}($form, $form_state));
-    }
-  }
-
-  /**
-   * Test base deriver implementation.
-   */
-  public function testBaseDeriver() {
-    $instance = new \stdClass();
-    $storage = $this->getMockBuilder(DatabaseStorageExpirable::class)
-      ->disableOriginalConstructor()
-      ->getMock();
-
-    $base_deriver = $this->getMockBuilder(BaseDeriver::class)
-      ->setConstructorArgs([$this->getSdk()])
-      ->getMockForAbstractClass();
-
-    $base_deriver->expects(static::once())
-      ->method('getInstance')
-      ->willReturn($instance);
-
-    // The "derive" method must call "getInstance".
-    $this->assertSame($instance, $base_deriver->derive());
-    // Ensure that login callback has not been overridden.
-    $this->assertFalse($base_deriver->isLoginCallbackOverridden());
-    // The "loginCallback" method not overridden and must return void (null).
-    $this->assertNull($base_deriver->loginCallback());
-
-    \Drupal::service('keyvalue.expirable.database')->expects(static::exactly(2))
-      ->method('get')
-      ->with('sdk_storage')
-      ->willReturn($storage);
-
-    $this->assertSame($storage, $base_deriver::storage());
-    // Currently token is not set.
-    $this->assertNull($base_deriver->getToken());
-
-    $this->setExpectedExceptionRegExp(\RuntimeException::class, '/^The "loginCallback" method must be overridden by ".*" class$/');
-    // Must throw an exception because "loginCallback" method not overridden.
-    $base_deriver->loginUrl();
-  }
-
-}
diff --git a/tests/src/Unit/Api/ExternalLinkTest.php b/tests/src/Unit/ExternalLinkTest.php
similarity index 88%
rename from tests/src/Unit/Api/ExternalLinkTest.php
rename to tests/src/Unit/ExternalLinkTest.php
index 9f46a5b..1691e92 100644
--- a/tests/src/Unit/Api/ExternalLinkTest.php
+++ b/tests/src/Unit/ExternalLinkTest.php
@@ -1,23 +1,20 @@
 <?php
 
-namespace Drupal\Tests\sdk\Unit\Api;
+namespace Drupal\Tests\sdk\Unit;
 
-// Testing dependencies.
 use Drupal\Tests\UnitTestCase;
-// Core components.
 use Drupal\Core\Link;
 use Drupal\Core\GeneratedLink;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Utility\LinkGeneratorInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
-// SDK API components.
-use Drupal\sdk\Api\ExternalLink;
+use Drupal\sdk\ExternalLink;
 
 /**
  * Test generating external links.
  *
- * @covers \Drupal\sdk\Api\ExternalLink
+ * @covers \Drupal\sdk\ExternalLink
  *
  * @group sdk-api
  */
@@ -40,14 +37,13 @@ class ExternalLinkTest extends UnitTestCase {
   /**
    * Checks that external link properly generated.
    *
-   * @dataProvider provider
-   *
-   * @covers \Drupal\sdk\Api\ExternalLink::externalLink
-   *
    * @param string $url
    *   Link URL.
    * @param string|null $text
    *   Link text. URL will be used if not specified.
+   *
+   * @dataProvider provider
+   * @covers \Drupal\sdk\ExternalLink::externalLink
    */
   public function testExternalLink($url, $text = NULL) {
     $external_link = $this->getMockForTrait(ExternalLink::class);
