diff --git a/jsonapi.services.yml b/jsonapi.services.yml
index 5b4c023..23325e1 100644
--- a/jsonapi.services.yml
+++ b/jsonapi.services.yml
@@ -85,3 +85,6 @@ services:
     arguments: ['@entity.manager']
   jsonapi.error_handler:
     class: Drupal\jsonapi\Error\ErrorHandler
+  plugin.manager.resource.processor:
+    class: Drupal\jsonapi\Plugin\JsonApiResourceManager
+    parent: default_plugin_manager
diff --git a/src/Annotation/JsonApiResource.php b/src/Annotation/JsonApiResource.php
new file mode 100644
index 0000000..3fe8353
--- /dev/null
+++ b/src/Annotation/JsonApiResource.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\jsonapi\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a JSON API Resource item annotation object.
+ *
+ * @see \Drupal\jsonapi\Plugin\JsonApiResourceManager
+ * @see plugin_api
+ *
+ * @Annotation
+ */
+class JsonApiResource extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The label of the plugin.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $label;
+
+  /**
+   * The entity type ID.
+   *
+   * @var string
+   */
+  public $entityType;
+
+  /**
+   * The bundle ID.
+   *
+   * @var string
+   */
+  public $bundle;
+
+  /**
+   * Information about the data resources.
+   *
+   * @var array
+   */
+  public $data;
+
+  /**
+   * Information about the data resources.
+   *
+   * @var array
+   */
+  public $schema;
+
+}
diff --git a/src/Configuration/ResourceManager.php b/src/Configuration/ResourceManager.php
index 3999f06..87d8d22 100644
--- a/src/Configuration/ResourceManager.php
+++ b/src/Configuration/ResourceManager.php
@@ -1,12 +1,12 @@
 <?php
 
 namespace Drupal\jsonapi\Configuration;
+
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
 
-
 /**
  * Class ResourceManager.
  *
diff --git a/src/Context/FieldResolver.php b/src/Context/FieldResolver.php
index 83d3db5..a7b3172 100644
--- a/src/Context/FieldResolver.php
+++ b/src/Context/FieldResolver.php
@@ -62,18 +62,16 @@ class FieldResolver implements FieldResolverInterface {
     $parts = explode('.', $external_field_name);
     // The last part of the chain is the referenced field, not a relationship.
     $leave_field = array_pop($parts);
-    // Prepare the exception only once.
-    $exception = new BadRequestHttpException('Invalid nested filtering.');
     $entity_type_id = $this->currentContext->getResourceConfig()->getEntityTypeId();
     foreach ($parts as $field_name) {
       if (!$definitions = $this->fieldManager->getFieldStorageDefinitions($entity_type_id)) {
-        throw $exception;
+        throw new BadRequestHttpException(sprintf('Invalid nested filtering. There is no entity type "%s".', $entity_type_id));
       }
       if (
         empty($definitions[$field_name]) ||
         $definitions[$field_name]->getType() != 'entity_reference'
       ) {
-        throw $exception;
+        throw new BadRequestHttpException(sprintf('Invalid nested filtering. Invalid entity reference "%s".', $field_name));
       }
       // Update the entity type with the referenced type.
       $entity_type_id = $definitions[$field_name]->getSetting('target_type');
diff --git a/src/Plugin/Deriver/ResourceDeriver.php b/src/Plugin/Deriver/ResourceDeriver.php
new file mode 100644
index 0000000..a8fd8d9
--- /dev/null
+++ b/src/Plugin/Deriver/ResourceDeriver.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Drupal\jsonapi\Plugin\Deriver;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\jsonapi\Configuration\ResourceManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a resource plugin definition for every entity type's bundle.
+ *
+ * @see \Drupal\jsonapi\Plugin\jsonapi\resource\BundleResource
+ */
+class ResourceDeriver extends DeriverBase implements ContainerDeriverInterface {
+
+  /**
+   * List of derivative definitions.
+   *
+   * @var array
+   */
+  protected $derivatives;
+
+  /**
+   * The resource manager.
+   *
+   * @var \Drupal\jsonapi\Configuration\ResourceManagerInterface
+   */
+  protected $resourceManager;
+
+  /**
+   * Constructs an ResourceDeriver object.
+   *
+   * @param \Drupal\jsonapi\Configuration\ResourceManagerInterface $resource_manager
+   *   The entity manager.
+   */
+  public function __construct(ResourceManagerInterface $resource_manager) {
+    $this->resourceManager = $resource_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    /* @var \Drupal\jsonapi\Configuration\ResourceManagerInterface $resource_manager */
+    $resource_manager = $container->get('jsonapi.resource.manager');
+    return new static($resource_manager);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_definition) {
+    if (isset($this->derivatives)) {
+      return $this->derivatives;
+    }
+    $this->derivatives = [];
+    // Add in the default plugin configuration and the resource type.
+    /* @var \Drupal\jsonapi\Configuration\ResourceConfigInterface[] $resource_configs */
+    $resource_configs = $this->resourceManager->all();
+    foreach ($resource_configs as $resource) {
+      $global_config = $resource->getGlobalConfig();
+      $prefix = $global_config->get('prefix') ?: 'api';
+      $schema_prefix = $global_config->get('schema_prefix') ?: 'schema';
+      $id = sprintf('%s.dynamic.%s', $prefix, $resource->getTypeName());
+      $this->derivatives[$id] = [
+        'id' => $id,
+        'entityType' => $resource->getEntityTypeId(),
+        'bundle' => $resource->getBundleId(),
+        'hasBundle' => $this->resourceManager->hasBundle($resource->getEntityTypeId()),
+        'type' => $resource->getTypeName(),
+        'data' => [
+          'prefix' => $prefix,
+          'partialPath' => '/' . $prefix . $resource->getPath()
+        ],
+        'schema' => [
+          'prefix' => $schema_prefix,
+          'partialPath' => '/' . $schema_prefix . $resource->getPath()
+        ]
+      ];
+
+      $this->derivatives[$id] += $base_definition;
+    }
+    return $this->derivatives;
+  }
+
+}
diff --git a/src/Plugin/JsonApiResourceInterface.php b/src/Plugin/JsonApiResourceInterface.php
new file mode 100644
index 0000000..560b703
--- /dev/null
+++ b/src/Plugin/JsonApiResourceInterface.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\jsonapi\Plugin;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * Defines an interface for JSON API Resource plugins.
+ */
+interface JsonApiResourceInterface extends PluginInspectionInterface {
+
+  // Add get/set methods for your plugin type here.
+
+}
diff --git a/src/Plugin/JsonApiResourceManager.php b/src/Plugin/JsonApiResourceManager.php
new file mode 100644
index 0000000..a465ab5
--- /dev/null
+++ b/src/Plugin/JsonApiResourceManager.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Drupal\jsonapi\Plugin;
+
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\jsonapi\Resource\DocumentWrapperInterface;
+use Drupal\jsonapi\Routing\Routes;
+
+/**
+ * Provides the JSON API Resource plugin manager.
+ */
+class JsonApiResourceManager extends DefaultPluginManager {
+
+  /**
+   * Default values for the plugin definition.
+   *
+   * @var array
+   */
+  protected $defaults = [
+    'permission' => 'access content',
+    'controller' => Routes::FRONT_CONTROLLER,
+  ];
+
+  /**
+   * Constructor for JsonApiResourceManager objects.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/jsonapi', $namespaces, $module_handler, 'Drupal\jsonapi\Plugin\JsonApiResourceInterface', 'Drupal\jsonapi\Annotation\JsonApiResource');
+
+    $this->alterInfo('jsonapi_resource_info');
+    $this->setCacheBackend($cache_backend, 'jsonapi_resource_plugins');
+  }
+
+}
diff --git a/src/Plugin/jsonapi/BundleJsonApiResource.php b/src/Plugin/jsonapi/BundleJsonApiResource.php
new file mode 100644
index 0000000..6885391
--- /dev/null
+++ b/src/Plugin/jsonapi/BundleJsonApiResource.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\jsonapi\Plugin\jsonapi;
+
+use Drupal\Core\Plugin\ContextAwarePluginBase;
+use Drupal\jsonapi\Plugin\JsonApiResourceBase;
+use Drupal\jsonapi\Plugin\JsonApiResourceInterface;
+
+/**
+ * Class BundleJsonApiResource
+ *
+ * Represents bundles as JSON API resources.
+ *
+ * @see \Drupal\jsonapi\Plugin\Deriver\ResourceDeriver
+ *
+ * @JsonApiResource(
+ *   id = "bundle",
+ *   label = @Translation("Bundle"),
+ *   deriver = "Drupal\jsonapi\Plugin\Deriver\ResourceDeriver",
+ * )
+ *
+ * @package Drupal\jsonapi\Plugin\jsonapi
+ */
+class BundleJsonApiResource extends ContextAwarePluginBase implements JsonApiResourceInterface {
+
+  // TODO: The plugin will contain a factory to a field mapper class.
+
+}
diff --git a/src/Routing/Routes.php b/src/Routing/Routes.php
index 6740742..6de3a0c 100644
--- a/src/Routing/Routes.php
+++ b/src/Routing/Routes.php
@@ -5,7 +5,7 @@ namespace Drupal\jsonapi\Routing;
 use Drupal\Core\Authentication\AuthenticationCollectorInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Field\EntityReferenceFieldItemList;
-use Drupal\jsonapi\Configuration\ResourceManagerInterface;
+use Drupal\jsonapi\Plugin\JsonApiResourceManager;
 use Drupal\jsonapi\Resource\DocumentWrapperInterface;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -29,9 +29,9 @@ class Routes implements ContainerInjectionInterface {
   /**
    * The resource manager interface.
    *
-   * @var \Drupal\jsonapi\Configuration\ResourceManagerInterface
+   * @var \Drupal\jsonapi\Plugin\JsonApiResourceManager
    */
-  protected $resourceManager;
+  protected $resourcePluginManager;
 
   /**
    * The authentication collector.
@@ -50,13 +50,13 @@ class Routes implements ContainerInjectionInterface {
   /**
    * Instantiates a Routes object.
    *
-   * @param \Drupal\jsonapi\Configuration\ResourceManagerInterface $resource_manager
+   * @param \Drupal\jsonapi\Plugin\JsonApiResourceManager $resource_plugin_manager
    *   The resource manager.
    * @param \Drupal\Core\Authentication\AuthenticationCollectorInterface $auth_collector
    *   The resource manager.
    */
-  public function __construct(ResourceManagerInterface $resource_manager, AuthenticationCollectorInterface $auth_collector) {
-    $this->resourceManager = $resource_manager;
+  public function __construct(JsonApiResourceManager $resource_plugin_manager, AuthenticationCollectorInterface $auth_collector) {
+    $this->resourcePluginManager = $resource_plugin_manager;
     $this->authCollector = $auth_collector;
   }
 
@@ -64,11 +64,11 @@ class Routes implements ContainerInjectionInterface {
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    /* @var \Drupal\jsonapi\Configuration\ResourceManagerInterface $resource_manager */
-    $resource_manager = $container->get('jsonapi.resource.manager');
+    /* @var \Drupal\jsonapi\Plugin\JsonApiResourceManager $resource_plugin_manager */
+    $resource_plugin_manager = $container->get('plugin.manager.resource.processor');
     /* @var \Drupal\Core\Authentication\AuthenticationCollectorInterface $auth_collector */
     $auth_collector = $container->get('authentication_collector');
-    return new static($resource_manager, $auth_collector);
+    return new static($resource_plugin_manager, $auth_collector);
   }
 
   /**
@@ -76,20 +76,18 @@ class Routes implements ContainerInjectionInterface {
    */
   public function routes() {
     $collection = new RouteCollection();
-    foreach ($this->resourceManager->all() as $resource) {
-      $global_config = $resource->getGlobalConfig();
-      $prefix = $global_config->get('prefix') ?: 'api';
-      $schema_prefix = $global_config->get('schema_prefix') ?: 'schema';
-      $entity_type = $resource->getEntityTypeId();
+    foreach ($this->resourcePluginManager->getDefinitions() as $plugin_id => $plugin_definition) {
+      $entity_type = $plugin_definition['entityType'];
       // For the entity type resources the bundle is NULL.
-      $bundle = $resource->getBundleId();
-      $entity_type_has_bundle = $this->resourceManager->hasBundle($entity_type);
-      $partial_path = '/' . $prefix . $resource->getPath();
-      $schema_partial_path = '/' . $schema_prefix . $resource->getPath();
-      $route_key = sprintf('%s.dynamic.%s.', $prefix, $resource->getTypeName());
+      $bundle = $plugin_definition['bundle'];
+      $entity_type_has_bundle = $plugin_definition['hasBundle'];
+      $partial_path = $plugin_definition['data']['partialPath'];
+      $schema_partial_path = $plugin_definition['schema']['partialPath'];
+      $route_keys = explode(':', $plugin_id);
+      $route_key = end($route_keys) . '.';
       // Add the collection route.
       $defaults = [
-        RouteObjectInterface::CONTROLLER_NAME => static::FRONT_CONTROLLER,
+        RouteObjectInterface::CONTROLLER_NAME => $plugin_definition['controller'],
       ];
       // Options that apply to all routes.
       $options = [
@@ -101,7 +99,7 @@ class Routes implements ContainerInjectionInterface {
       $route_collection = (new Route($partial_path))
         ->addDefaults($defaults)
         ->setRequirement('_entity_type', $entity_type)
-        ->setRequirement('_permission', 'access content')
+        ->setRequirement('_permission', $plugin_definition['permission'])
         ->setRequirement('_format', 'api_json')
         ->setRequirement('_custom_parameter_names', 'TRUE')
         ->setOption('serialization_class', DocumentWrapperInterface::class)
@@ -117,7 +115,7 @@ class Routes implements ContainerInjectionInterface {
       $route_individual = (new Route(sprintf('%s/{%s}', $partial_path, $entity_type)))
         ->addDefaults($defaults)
         ->setRequirement('_entity_type', $entity_type)
-        ->setRequirement('_permission', 'access content')
+        ->setRequirement('_permission', $plugin_definition['permission'])
         ->setRequirement('_format', 'api_json')
         ->setRequirement('_custom_parameter_names', 'TRUE')
         ->setOption('parameters', $parameters)
@@ -134,7 +132,7 @@ class Routes implements ContainerInjectionInterface {
       $route_related = (new Route(sprintf('%s/{%s}/{related}', $partial_path, $entity_type)))
         ->addDefaults($defaults)
         ->setRequirement('_entity_type', $entity_type)
-        ->setRequirement('_permission', 'access content')
+        ->setRequirement('_permission', $plugin_definition['permission'])
         ->setRequirement('_format', 'api_json')
         ->setRequirement('_custom_parameter_names', 'TRUE')
         ->setOption('parameters', $parameters)
@@ -150,7 +148,7 @@ class Routes implements ContainerInjectionInterface {
       $route_relationship = (new Route(sprintf('%s/{%s}/relationships/{related}', $partial_path, $entity_type)))
         ->addDefaults($defaults + ['_on_relationship' => TRUE])
         ->setRequirement('_entity_type', $entity_type)
-        ->setRequirement('_permission', 'access content')
+        ->setRequirement('_permission', $plugin_definition['permission'])
         ->setRequirement('_format', 'api_json')
         ->setRequirement('_custom_parameter_names', 'TRUE')
         ->setOption('parameters', $parameters)
@@ -169,7 +167,7 @@ class Routes implements ContainerInjectionInterface {
           '_controller' => '\Drupal\jsonapi\Controller\SchemaController::entityCollectionSchema',
           'typed_data_id' => 'entity:' . $entity_type . (($entity_type_has_bundle) ? ':' . $bundle : ''),
         ])
-        ->setRequirement('_permission', 'access content')
+        ->setRequirement('_permission', $plugin_definition['permission'])
         ->setOption('_auth', $this->authProviderList())
         ->setMethods(['GET']);
       $collection->add($route_key . 'schema', $route_collection_schema);
@@ -180,7 +178,7 @@ class Routes implements ContainerInjectionInterface {
           '_controller' => '\Drupal\jsonapi\Controller\SchemaController::entitySchema',
           'typed_data_id' => 'entity:' . $entity_type . (($entity_type_has_bundle) ? ':' . $bundle : ''),
         ])
-        ->setRequirement('_permission', 'access content')
+        ->setRequirement('_permission', $plugin_definition['permission'])
         ->setOption('_auth', $this->authProviderList())
         ->setMethods(['GET']);
       $collection->add($route_key . 'individual.schema', $route_individual_schema);
diff --git a/tests/src/Unit/Plugin/Deriver/ResourceDeriverTest.php b/tests/src/Unit/Plugin/Deriver/ResourceDeriverTest.php
new file mode 100644
index 0000000..88bc366
--- /dev/null
+++ b/tests/src/Unit/Plugin/Deriver/ResourceDeriverTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Drupal\Tests\jsonapi\Unit\Plugin\Deriver;
+
+use Drupal\jsonapi\Plugin\Deriver\ResourceDeriver;
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\jsonapi\Configuration\ResourceConfigInterface;
+use Drupal\jsonapi\Configuration\ResourceManagerInterface;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Class RoutesTest.
+ *
+ * @package Drupal\Tests\jsonapi\Unit\Plugin\Deriver
+ *
+ * @coversDefaultClass \Drupal\jsonapi\Plugin\Deriver\ResourceDeriver
+ *
+ * @group jsonapi
+ */
+class ResourceDeriverTest extends UnitTestCase {
+
+  /**
+   * The deriver under test.
+   *
+   * @var ResourceDeriver
+   */
+  protected $deriver;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    // Mock the resource manager to have some resources available.
+    $resource_manager = $this->prophesize(ResourceManagerInterface::class);
+
+    // Create some resource mocks for the manager.
+    $resource_config = $this->prophesize(ResourceConfigInterface::class);
+    $global_config = $this->prophesize(ImmutableConfig::class);
+    $global_config->get('prefix')->willReturn('api');
+    $global_config->get('schema_prefix')->willReturn('schema');
+    $resource_config->getGlobalConfig()->willReturn($global_config->reveal());
+    $resource_config->getEntityTypeId()->willReturn('entity_type_1');
+    $resource_config->getBundleId()->willReturn('bundle_1_1');
+    // Make sure that we're not coercing the bundle into the path, they can be
+    // different in the future.
+    $resource_config->getPath()->willReturn('/entity_type_1/bundle_path_1');
+    $resource_config->getTypeName()->willReturn('resource_type_1');
+    $resource_manager->all()->willReturn([$resource_config->reveal()]);
+    $resource_manager->hasBundle(Argument::type('string'))->willReturn(FALSE);
+
+    $container = $this->prophesize(ContainerInterface::class);
+    $container->get('jsonapi.resource.manager')->willReturn($resource_manager->reveal());
+    $this->deriver = ResourceDeriver::create($container->reveal(), 'bundle');
+  }
+
+
+  /**
+   * @covers ::getDerivativeDefinitions
+   */
+  public function testGetDerivativeDefinitions() {
+    $expected = ['api.dynamic.resource_type_1' => [
+      'id' => 'api.dynamic.resource_type_1',
+      'entityType' => 'entity_type_1',
+      'bundle' => 'bundle_1_1',
+      'hasBundle' => FALSE,
+      'type' => 'resource_type_1',
+      'data' => [
+        'prefix' => 'api',
+        'partialPath' => '/api/entity_type_1/bundle_path_1',
+      ],
+      'schema' => [
+        'prefix' => 'schema',
+        'partialPath' => '/schema/entity_type_1/bundle_path_1',
+      ],
+      'permission' => 'access content',
+      'controller' => '\\Drupal\\jsonapi\\RequestHandler::handle',
+    ]];
+    $actual = $this->deriver->getDerivativeDefinitions([
+      'permission' => 'access content',
+      'controller' => '\Drupal\jsonapi\RequestHandler::handle',
+    ]);
+    $this->assertArrayEquals($expected, $actual);
+  }
+
+}
diff --git a/tests/src/Unit/Routing/RoutesTest.php b/tests/src/Unit/Routing/RoutesTest.php
index 17d949b..bb5d1bb 100644
--- a/tests/src/Unit/Routing/RoutesTest.php
+++ b/tests/src/Unit/Routing/RoutesTest.php
@@ -3,12 +3,9 @@
 namespace Drupal\Tests\jsonapi\Unit\Routing;
 
 use Drupal\Core\Authentication\AuthenticationCollectorInterface;
-use Drupal\Core\Config\ImmutableConfig;
-use Drupal\jsonapi\Configuration\ResourceConfigInterface;
-use Drupal\jsonapi\Configuration\ResourceManagerInterface;
+use Drupal\jsonapi\Plugin\JsonApiResourceManager;
 use Drupal\jsonapi\Routing\Routes;
 use Drupal\Tests\UnitTestCase;
-use Prophecy\Argument;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -36,24 +33,28 @@ class RoutesTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
     // Mock the resource manager to have some resources available.
-    $resource_manager = $this->prophesize(ResourceManagerInterface::class);
-
-    // Create some resource mocks for the manager.
-    $resource_config = $this->prophesize(ResourceConfigInterface::class);
-    $global_config = $this->prophesize(ImmutableConfig::class);
-    $global_config->get('prefix')->willReturn('api');
-    $global_config->get('schema_prefix')->willReturn('schema');
-    $resource_config->getGlobalConfig()->willReturn($global_config->reveal());
-    $resource_config->getEntityTypeId()->willReturn('entity_type_1');
-    $resource_config->getBundleId()->willReturn('bundle_1_1');
-    // Make sure that we're not coercing the bundle into the path, they can be
-    // different in the future.
-    $resource_config->getPath()->willReturn('/entity_type_1/bundle_path_1');
-    $resource_config->getTypeName()->willReturn('resource_type_1');
-    $resource_manager->all()->willReturn([$resource_config->reveal()]);
-    $resource_manager->hasBundle(Argument::type('string'))->willReturn(FALSE);
+    $resource_plugin_manager = $this->prophesize(JsonApiResourceManager::class);
+    $resource_plugin_manager->getDefinitions()->willReturn([
+      'bundle:api.dynamic.resource_type_1' => [
+        'id' => 'bundle:api.dynamic.resource_type_1',
+        'entityType' => 'entity_type_1',
+        'bundle' => 'bundle_1_1',
+        'hasBundle' => TRUE,
+        'type' => 'resource_type_1',
+        'data' => [
+          'prefix' => 'api',
+          'partialPath' => '/api/entity_type_1/bundle_path_1',
+        ],
+        'schema' => [
+          'prefix' => 'schema',
+          'partialPath' => '/schema/entity_type_1/bundle_path_1',
+        ],
+        'controller' => 'MyCustomController',
+        'permission' => 'access content'
+      ],
+    ]);
     $container = $this->prophesize(ContainerInterface::class);
-    $container->get('jsonapi.resource.manager')->willReturn($resource_manager->reveal());
+    $container->get('plugin.manager.resource.processor')->willReturn($resource_plugin_manager->reveal());
     $auth_collector = $this->prophesize(AuthenticationCollectorInterface::class);
     $auth_collector->getSortedProviders()->willReturn([
       'lorem' => [],
@@ -84,7 +85,7 @@ class RoutesTest extends UnitTestCase {
     $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
     $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth'));
     $this->assertEquals(['GET', 'POST'], $route->getMethods());
-    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
+    $this->assertSame('MyCustomController', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
     $this->assertSame('Drupal\jsonapi\Resource\DocumentWrapperInterface', $route->getOption('serialization_class'));
   }
 
@@ -102,7 +103,7 @@ class RoutesTest extends UnitTestCase {
     $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
     $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
     $this->assertEquals(['GET', 'PATCH', 'DELETE'], $route->getMethods());
-    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
+    $this->assertSame('MyCustomController', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
     $this->assertSame('Drupal\jsonapi\Resource\DocumentWrapperInterface', $route->getOption('serialization_class'));
     $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth'));
     $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
@@ -122,7 +123,7 @@ class RoutesTest extends UnitTestCase {
     $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
     $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
     $this->assertEquals(['GET'], $route->getMethods());
-    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
+    $this->assertSame('MyCustomController', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
     $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth'));
     $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
   }
@@ -141,7 +142,7 @@ class RoutesTest extends UnitTestCase {
     $this->assertSame('entity_type_1', $route->getRequirement('_entity_type'));
     $this->assertSame('bundle_1_1', $route->getRequirement('_bundle'));
     $this->assertEquals(['GET', 'POST', 'PATCH', 'DELETE'], $route->getMethods());
-    $this->assertSame('\Drupal\jsonapi\RequestHandler::handle', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
+    $this->assertSame('MyCustomController', $route->getDefault(RouteObjectInterface::CONTROLLER_NAME));
     $this->assertSame(['lorem', 'ipsum'], $route->getOption('_auth'));
     $this->assertEquals(['entity_type_1' => ['type' => 'entity:entity_type_1']], $route->getOption('parameters'));
     $this->assertSame('Drupal\Core\Field\EntityReferenceFieldItemList', $route->getOption('serialization_class'));
