diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
new file mode 100644
index 0000000..2b81b14
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\Derivative\EntityDerivative.
+ */
+
+namespace Drupal\rest\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+/**
+ * Provides a resource plugin definition for every entity type.
+ */
+class EntityDerivative implements DerivativeInterface {
+
+  /**
+   * List of derivative definitions.
+   *
+   * @var array
+   */
+  protected $derivatives;
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    if (!isset($this->derivatives)) {
+      $this->getDerivativeDefinitions($base_plugin_definition);
+    }
+    if (isset($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    if (!isset($this->derivatives)) {
+      // Add in the default plugin configuration and the resource type.
+      foreach (entity_get_info() as $entity_type => $entity_info) {
+        $this->derivatives[$entity_type] = array(
+          'id' => 'entity:' . $entity_type,
+          'entity_type' => $entity_type,
+          'label' => $entity_info['label'],
+        );
+        $this->derivatives[$entity_type] += $base_plugin_definition;
+      }
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
new file mode 100644
index 0000000..1973877
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\ResourceBase.
+ */
+
+namespace Drupal\rest\Plugin;
+
+use Drupal\Component\Plugin\PluginBase;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Common base class for resource plugins.
+ */
+abstract class ResourceBase extends PluginBase {
+
+  /**
+   * Temporary helper for building permissions.
+   */
+  public function permissions() {
+    $permissions = array();
+    $definition = $this->getDefinition();
+    foreach ($this->requestMethods() as $method) {
+      $lowered_method = strtolower($method);
+      // Only expose permissions where the HTTP request method exists on the
+      // plugin.
+      if (method_exists($this, $lowered_method)) {
+        $permissions["restful $lowered_method $this->plugin_id"] = array(
+          'title' => t('Access @method on %label resource', array('@method' => $method, '%label' => $definition['label'])),
+        );
+      }
+    }
+    return $permissions;
+  }
+
+  /**
+   * Temporary helper for building routes.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A collection of routes that should be registered for this resource.
+   */
+  public function routes() {
+    $collection = new RouteCollection();
+
+    $methods = $this->requestMethods();
+    foreach ($methods as $method) {
+      // Only expose routes where the HTTP request method exists on the plugin.
+      if (method_exists($this, strtolower($method))) {
+        $prefix = strtr($this->plugin_id, ':', '/');
+        $route = new Route("/$prefix/{id}", array(
+          '_controller' => 'Drupal\rest\RequestHandler::handle',
+          'plugin' => $this->plugin_id,
+        ), array(
+          '_method' => $method,
+        ));
+
+        $name = strtr($this->plugin_id, ':', '.');
+        $collection->add("$name.$method", $route);
+      }
+    }
+
+    return $collection;
+  }
+
+  /**
+   * Provides predefined HTTP request methods.
+   *
+   * Plugins can override this method to provide additional custom request
+   * methods.
+   *
+   * @return array
+   *   The list of allowed HTTP request method strings.
+   */
+  protected function requestMethods() {
+    return drupal_map_assoc(array(
+      'HEAD',
+      'GET',
+      'POST',
+      'PUT',
+      'DELETE',
+      'TRACE',
+      'OPTIONS',
+      'CONNECT',
+      'PATCH',
+    ));
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Type/ResourcePluginManager.php b/core/modules/rest/lib/Drupal/rest/Plugin/Type/ResourcePluginManager.php
new file mode 100644
index 0000000..b496d2c
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Type/ResourcePluginManager.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\Type\ResourcePluginManager.
+ */
+
+namespace Drupal\rest\Plugin\Type;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Component\Plugin\Factory\ReflectionFactory;
+
+/**
+ * Manages discovery and instantiation of resource plugins.
+ */
+class ResourcePluginManager extends PluginManagerBase {
+
+  /**
+   * Overrides Drupal\Component\Plugin\PluginManagerBase::__construct().
+   */
+  public function __construct() {
+    // Create resource plugin derivatives from declaratively defined resources.
+    $this->discovery = new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('rest', 'resource'));
+    $this->factory = new ReflectionFactory($this);
+  }
+
+  /**
+   * Overrides Drupal\Component\Plugin\PluginManagerBase::getInstance().
+   */
+  public function getInstance(array $options){
+    if (isset($options['id'])) {
+      return $this->createInstance($options['id']);
+    }
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
new file mode 100644
index 0000000..afc8d50
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\rest\resource\DBLogResource.
+ */
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\Core\Annotation\Plugin;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Provides a resource for database watchdog log entries.
+ *
+ * @Plugin(
+ *  id = "dblog",
+ *  label = "Watchdog database log"
+ * )
+ */
+class DBLogResource extends ResourceBase {
+
+  /**
+   * Overrides \Drupal\rest\Plugin\ResourceBase::routes().
+   */
+  public function routes() {
+    // Only expose routes if the dblog module is enabled.
+    if (module_exists('dblog')) {
+      return parent::routes();
+    }
+    return new RouteCollection();
+  }
+
+  /**
+   * Responds to GET requests.
+   *
+   * Returns a watchdog log entry for the specified ID.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The response object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function get($id = NULL) {
+    if ($id) {
+      $result = db_select('watchdog', 'w')
+        ->condition('wid', $id)
+        ->fields('w')
+        ->execute()
+        ->fetchAll();
+      if (empty($result)) {
+        throw new NotFoundHttpException('Not Found');
+      }
+      // @todo remove hard coded format here.
+      return new Response(drupal_json_encode($result[0]), 200, array('Content-Type' => 'application/json'));
+    }
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
new file mode 100644
index 0000000..3524389
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\rest\resource\EntityResource.
+ */
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\rest\Plugin\ResourceBase;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Represents entities as resources.
+ *
+ * @Plugin(
+ *  id = "entity",
+ *  label = "Entity",
+ *  derivative = "Drupal\rest\Plugin\Derivative\EntityDerivative"
+ * )
+ */
+class EntityResource extends ResourceBase {
+
+  /**
+   * Responds to entity DELETE requests.
+   *
+   * @param mixed $id
+   *   The entity ID.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The response object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function delete($id) {
+    $definition = $this->getDefinition();
+    $entity = entity_load($definition['entity_type'], $id);
+    if ($entity) {
+      try {
+        $entity->delete();
+        // Delete responses have an empty body.
+        return new Response('', 204);
+      }
+      catch (EntityStorageException $e) {
+        throw new HttpException(500, 'Internal Server Error', $e);
+      }
+    }
+    throw new NotFoundHttpException(t('Entity with ID @id not found', array('@id' => $id)));
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/RequestHandler.php b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
new file mode 100644
index 0000000..7ed7b7f
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\RequestHandler.
+ */
+
+namespace Drupal\rest;
+
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+/**
+ * Acts as intermediate request forwarder for resource plugins.
+ */
+class RequestHandler extends ContainerAware {
+
+  /**
+   * Handles a web API request.
+   *
+   * @param string $plugin
+   *   The resource type plugin.
+   * @param Symfony\Component\HttpFoundation\Request $request
+   *   The HTTP request object.
+   * @param mixed $id
+   *   The resource ID.
+   */
+  public function handle($plugin, Request $request, $id = NULL) {
+    $method = strtolower($request->getMethod());
+    if (user_access("restful $method $plugin")) {
+      $resource = $this->container
+        ->get('plugin.manager.rest')
+        ->getInstance(array('id' => $plugin));
+      try {
+        return $resource->{$method}($id);
+      }
+      catch (HttpException $e) {
+        return new Response($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
+      }
+    }
+    return new Response('Access Denied', 403);
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/RestBundle.php b/core/modules/rest/lib/Drupal/rest/RestBundle.php
new file mode 100644
index 0000000..7810c30
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/RestBundle.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\RestBundle.
+ */
+
+namespace Drupal\rest;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Rest dependency injection container.
+ */
+class RestBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    // Register the resource manager class with the dependency injection
+    // container.
+    $container->register('plugin.manager.rest', 'Drupal\rest\Plugin\Type\ResourcePluginManager');
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
new file mode 100644
index 0000000..6bd8e65
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\DBLogTest.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\rest\Tests\RESTTestBase;
+
+/**
+ * Tests the Watchdog resource to retrieve log messages.
+ */
+class DBLogTest extends RESTTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('rest', 'dblog');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'DB Log resource',
+      'description' => 'Tests the watchdog database log resource.',
+      'group' => 'REST',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    // Enable web API for the watchdog resource.
+    $config = config('rest');
+    $config->set('resources', array(
+      'dblog' => 'dblog',
+    ));
+    $config->save();
+
+    // Rebuild routing cache, so that the web API paths are available.
+    drupal_container()->get('router.builder')->rebuild();
+    // Reset the Simpletest permission cache, so that the new resource
+    // permissions get picked up.
+    drupal_static_reset('checkPermissions');
+  }
+
+  /**
+   * Writes a log messages and retrieves it via the web API.
+   */
+  public function testWatchdog() {
+    // Write a log message to the DB.
+    watchdog('rest_test', 'Test message');
+    // Get ID of the written message.
+    $result = db_select('watchdog', 'w')
+      ->condition('type', 'rest_test')
+      ->fields('w', array('wid'))
+      ->execute()
+      ->fetchCol();
+    $id = $result[0];
+
+    // Create a user account that has the required permissions to read
+    // the watchdog resource via the web API.
+    $account = $this->drupalCreateUser(array('restful get dblog'));
+    $this->drupalLogin($account);
+
+    $response = $this->httpRequest("dblog/$id", 'GET');
+    $this->assertResponse(200);
+    $log = drupal_json_decode($response);
+    $this->assertEqual($log['wid'], $id, 'Log ID is correct.');
+    $this->assertEqual($log['type'], 'rest_test', 'Type of log message is correct.');
+    $this->assertEqual($log['message'], 'Test message', 'Log message text is correct.');
+
+    // Request an unknown log entry.
+    $response = $this->httpRequest("dblog/9999", 'GET');
+    $this->assertResponse(404);
+    $this->assertEqual($response, 'Not Found', 'Response message is correct.');
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
new file mode 100644
index 0000000..187c386
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\DeleteTest.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\rest\Tests\RESTTestBase;
+
+/**
+ * Tests resource deletion on user, node and test entities.
+ */
+class DeleteTest extends RESTTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('rest', 'entity_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Delete resource',
+      'description' => 'Tests the deletion of resources.',
+      'group' => 'REST',
+    );
+  }
+
+  /**
+   * Tests several valid and invalid delete requests on all entity types.
+   */
+  public function testDelete() {
+    foreach (entity_get_info() as $entity_type => $info) {
+      // Enable web API for this entity type.
+      $config = config('rest');
+      $config->set('resources', array(
+        'entity:' . $entity_type => 'entity:' . $entity_type,
+      ));
+      $config->save();
+
+      // Rebuild routing cache, so that the web API paths are available.
+      drupal_container()->get('router.builder')->rebuild();
+      // Reset the Simpletest permission cache, so that the new resource
+      // permissions get picked up.
+      drupal_static_reset('checkPermissions');
+      // Create a user account that has the required permissions to delete
+      // resources via the web API.
+      $account = $this->drupalCreateUser(array('restful delete entity:' . $entity_type));
+      // Reset cURL here because it is confused from our previously used cURL
+      // options.
+      unset($this->curlHandle);
+      $this->drupalLogin($account);
+
+      // Create an entity programmatically.
+      $entity = $this->entityCreate($entity_type);
+      $entity->save();
+      // Delete it over the web API.
+      $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'DELETE');
+      // Clear the static cache with entity_load(), otherwise we won't see the
+      // update.
+      $entity = entity_load($entity_type, $entity->id(), TRUE);
+      $this->assertFalse($entity, $entity_type . ' entity is not in the DB anymore.');
+      $this->assertResponse('204', 'HTTP response code is correct.');
+      $this->assertEqual($response, '', 'Response body is empty.');
+
+      // Try to delete an entity that does not exist.
+      $response = $this->httpRequest('entity/' . $entity_type . '/9999', 'DELETE');
+      $this->assertResponse(404);
+      $this->assertEqual($response, 'Entity with ID 9999 not found', 'Response message is correct.');
+
+      // Try to delete an entity without proper permissions.
+      $this->drupalLogout();
+      // Re-save entity to the database.
+      $entity = $this->entityCreate($entity_type);
+      $entity->save();
+      $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'DELETE');
+      $this->assertResponse(403);
+      $this->assertNotIdentical(FALSE, entity_load($entity_type, $entity->id(), TRUE), 'The ' . $entity_type . ' entity is still in the database.');
+    }
+    // Try to delete a resource which is not web API enabled.
+    $account = $this->drupalCreateUser();
+    // Reset cURL here because it is confused from our previously used cURL
+    // options.
+    unset($this->curlHandle);
+    $this->drupalLogin($account);
+    $this->httpRequest('entity/user/' . $account->id(), 'DELETE');
+    $user = entity_load('user', $account->id(), TRUE);
+    $this->assertEqual($account->id(), $user->id());
+    $this->assertResponse(404);
+  }
+
+  /**
+   * Creates entity objects based on their types.
+   *
+   * Required properties differ from entity type to entity type, so we keep a
+   * minimum mapping here.
+   *
+   * @param string $entity_type
+   *   The type of the entity that should be created..
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The new entity object.
+   */
+  protected function entityCreate($entity_type) {
+    switch ($entity_type) {
+      case 'entity_test':
+        return entity_create('entity_test', array('name' => 'test', 'user_id' => 1));
+      case 'node':
+        return entity_create('node', array('title' => $this->randomString()));
+      case 'user':
+        return entity_create('user', array('name' => $this->randomName()));
+      default:
+        return entity_create($entity_type, array());
+    }
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
new file mode 100644
index 0000000..3bc8756
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\RESTTestBase.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test helper class that provides a REST client method to send HTTP requests.
+ */
+abstract class RESTTestBase extends WebTestBase {
+
+  /**
+   * Helper function to issue a HTTP request with simpletest's cURL.
+   *
+   * @param string $url
+   *   The relative URL, e.g. "entity/node/1"
+   * @param string $method
+   *   HTTP method, one of GET, POST, PUT or DELETE.
+   * @param array $body
+   *   Either the body for POST and PUT or additional URL parameters for GET.
+   * @param string $format
+   *   The MIME type of the transmitted content.
+   */
+  protected function httpRequest($url, $method, $body = NULL, $format = 'application/ld+json') {
+    switch ($method) {
+      case 'GET':
+        // Set query if there are additional GET parameters.
+        $options = isset($body) ? array('absolute' => TRUE, 'query' => $body) : array('absolute' => TRUE);
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => TRUE,
+          CURLOPT_URL => url($url, $options),
+          CURLOPT_NOBODY => FALSE)
+        );
+      case 'POST':
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => FALSE,
+          CURLOPT_POST => TRUE,
+          CURLOPT_POSTFIELDS => $body,
+          CURLOPT_URL => url($url, array('absolute' => TRUE)),
+          CURLOPT_NOBODY => FALSE,
+          CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
+        ));
+      case 'PUT':
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => FALSE,
+          CURLOPT_CUSTOMREQUEST => 'PUT',
+          CURLOPT_POSTFIELDS => $body,
+          CURLOPT_URL => url($url, array('absolute' => TRUE)),
+          CURLOPT_NOBODY => FALSE,
+          CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
+        ));
+      case 'DELETE':
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => FALSE,
+          CURLOPT_CUSTOMREQUEST => 'DELETE',
+          CURLOPT_URL => url($url, array('absolute' => TRUE)),
+          CURLOPT_NOBODY => FALSE,
+        ));
+    }
+  }
+}
diff --git a/core/modules/rest/rest.admin.inc b/core/modules/rest/rest.admin.inc
new file mode 100644
index 0000000..266a04e
--- /dev/null
+++ b/core/modules/rest/rest.admin.inc
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Admin pages for REST module.
+ */
+
+/**
+ * Form constructor for the REST admin form.
+ *
+ * @ingroup forms
+ */
+function rest_admin_form($form, &$form_state) {
+  $resources = drupal_container()
+    ->get('plugin.manager.rest')
+    ->getDefinitions();
+  $entity_resources = array();
+  $other_resources = array();
+  foreach ($resources as $plugin_name => $definition) {
+    if (strpos($plugin_name, 'entity:') === FALSE) {
+      $other_resources[$plugin_name] = $definition['label'];
+    }
+    else {
+      $entity_resources[$plugin_name] = $definition['label'];
+    }
+  }
+  asort($entity_resources);
+  asort($other_resources);
+  $enabled_resources = config('rest')->get('resources') ?: array();
+
+  $form['entity_resources'] = array(
+    '#type' => 'checkboxes',
+    '#options' => $entity_resources,
+    '#default_value' => $enabled_resources,
+    '#title' => t('Entity resource types that should be exposed as web services:'),
+  );
+  if (!empty($other_resources)) {
+    $form['other_resources'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $other_resources,
+      '#default_value' => $enabled_resources,
+      '#title' => t('Other available resource types that should be exposed as web services:'),
+    );
+  }
+
+  return system_config_form($form, $form_state);
+}
+
+/**
+ * Form submission handler for rest_admin_form().
+ */
+function rest_admin_form_submit($form, &$form_state) {
+  $resources = array_filter($form_state['values']['entity_resources']);
+  if (!empty($form_state['values']['other_resources'])) {
+    $resources += array_filter($form_state['values']['other_resources']);
+  }
+
+  $config = config('rest');
+  $config->set('resources', $resources);
+  $config->save();
+
+  // Rebuild routing cache.
+  drupal_container()->get('router.builder')->rebuild();
+}
diff --git a/core/modules/rest/rest.info b/core/modules/rest/rest.info
new file mode 100644
index 0000000..56c650c
--- /dev/null
+++ b/core/modules/rest/rest.info
@@ -0,0 +1,6 @@
+name = RESTful web services
+description = Exposes entities and other resources as RESTful web API
+package = Core
+version = VERSION
+core = 8.x
+configure = admin/config/services/rest
diff --git a/core/modules/rest/rest.module b/core/modules/rest/rest.module
new file mode 100644
index 0000000..a020845
--- /dev/null
+++ b/core/modules/rest/rest.module
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * RESTful web services module.
+ */
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Implements hook_menu().
+ *
+ * @todo for now we use this hook for our admin page until
+ * http://drupal.org/node/1801570 is sorted out.
+ */
+function rest_menu() {
+  $items['admin/config/services/rest'] = array(
+    'title' => 'RESTful web services',
+    'description' => 'Configure resources and entities that are exposed as web API.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rest_admin_form'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'rest.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_route_info().
+ */
+function rest_route_info() {
+  $collection = new RouteCollection();
+
+  // This hook is called during module enabling where the manager has not been
+  // registered as service yet.
+  if (drupal_container()->has('plugin.manager.rest')) {
+    $manager = drupal_container()->get('plugin.manager.rest');
+    $resources = config('rest')->get('resources');
+    if ($resources && $enabled = array_intersect_key($manager->getDefinitions(), $resources)) {
+      foreach ($enabled as $key => $resource) {
+        $plugin = $manager->getInstance(array('id' => $key));
+
+        // @todo Switch to ->addCollection() once http://drupal.org/node/1819018 is resolved.
+        foreach ($plugin->routes() as $name => $route) {
+          $collection->add("rest.$name", $route);
+        }
+      }
+    }
+  }
+
+  return $collection;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function rest_permission() {
+  $permissions = array();
+  if (drupal_container()->has('plugin.manager.rest')) {
+    $manager = drupal_container()->get('plugin.manager.rest');
+    $resources = config('rest')->get('resources');
+    if ($resources && $enabled = array_intersect_key($manager->getDefinitions(), $resources)) {
+      foreach ($enabled as $key => $resource) {
+        $plugin = $manager->getInstance(array('id' => $key));
+        $permissions = array_merge($permissions, $plugin->permissions());
+      }
+    }
+  }
+  return $permissions;
+}
+
+/**
+ * Implements hook_help().
+ */
+function rest_help($path, $arg) {
+  switch ($path) {
+    // Main module help for the REST module
+    case 'admin/help#rest':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The REST module provides a framework for exposing Drupal\'s data structures as RESTful web services. It can be used to read and write resources remotely, such as entity types like nodes or users. For more information, see the online handbook entry for the <a href="@rest">RESTful web services module</a>.', array('@rest' => 'http://drupal.org/documentation/modules/rest')) . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<dl>';
+      $output .= '<dt>' . t('Exposing resources') . '</dt>';
+      $output .= '<dd>' . t('Visit the <a href="@admin-rest">configuration page</a> to display a list of available resources and to enable them individually.', array('@admin-rest' => url('admin/config/services/rest'))) . '</dd>';
+      $output .= '</dl>';
+      $output .= '<dl>';
+      $output .= '<dt>' . t('Granting permissions') . '</dt>';
+      $output .= '<dd>' . t('The <a href="@permission-rest">permissions page</a> allows you to determine what user roles may access a web service operation.', array('@admin-rest' => url('admin/people/permissions', array('fragment' => 'module-rest')))) . '</dd>';
+      $output .= '</dl>';
+      return $output;
+
+    case 'admin/config/services/rest':
+      global $base_url;
+      return '<p>' . t('This page allows you to expose resources as RESTful web API. That enables external third parties to interact with your Drupal installation via a machine readable interface. All entity types are available for such operations while contributed modules can provide more.') . '</p>' .
+        '<p>' . t('Example: when enabled, the node resource is avialable at the path <code>/entity/node/{id}</code> and a HTTP DELETE request can be used to delete a node. Testing on the command line with cURL (deletes node 5, make sure to replace the node ID accordingly):') . '</p>' .
+        '<p><code>curl --include --request DELETE --cookie ' . session_name() . '=' . session_id() . ' ' . $base_url . base_path() . 'entity/node/5</code>' .
+        '<p>' . t('Note: Session information of the current user is shown here, make sure that you have the <a href="@permissions">permission to use the web API</a> to delete nodes.', array('@permissions' => url('admin/people/permissions', array('fragment' => 'module-rest')))) . '</p>';
+  }
+}
