diff --git a/includes/media.browser.inc b/includes/media.browser.inc
index 35bb768..6884167 100644
--- a/includes/media.browser.inc
+++ b/includes/media.browser.inc
@@ -46,8 +46,7 @@ function media_browser($selected = NULL) {
     return $output;
   }
 
-  ctools_include('plugins');
-  $plugins = ctools_get_plugins('media', 'browser');
+  $plugins = media_get_browser_plugin_info();
 
   // Allow parameters to provide a list of enabled or disabled media browser plugins.
   if (!empty($params['enabledPlugins'])) {
@@ -60,21 +59,33 @@ function media_browser($selected = NULL) {
   // Render plugins.
   $plugin_output = array();
   foreach ($plugins as $key => &$plugin) {
-    if ($plugin_class = ctools_plugin_get_class($plugin, 'handler')) {
-      $access = TRUE;
-      if (isset($plugin['access callback']) && ($callback = $plugin['access callback']) && function_exists($callback)) {
-        $arguments = (isset($plugin['access arguments']) ? $plugin['access arguments'] : array());
-        $access = call_user_func_array($callback, $arguments);
+    // Support the old CTools style handler definition.
+    if (!isset($plugin['class']) && !empty($plugin['handler'])) {
+      if (is_string($plugin['handler'])) {
+        $plugin['class'] = $plugin['handler'];
       }
-      if ($access) {
-        $plugin_obj = new $plugin_class($plugin, $params);
-        $plugin_output[$key] = $plugin_obj->view() + array(
-          '#title' => $plugin['title'],
-          '#weight' => isset($plugin['weight']) ? $plugin['weight'] : 0,
-        );
+      elseif (isset($plugin['handler']['class'])) {
+        $plugin['class'] = $plugin['handler']['class'];
       }
     }
 
+    if (empty($plugin['class']) || !class_exists($plugin['class'])) {
+      continue;
+    }
+
+    $access = TRUE;
+    if (isset($plugin['access callback']) && ($callback = $plugin['access callback']) && function_exists($callback)) {
+      $arguments = (isset($plugin['access arguments']) ? $plugin['access arguments'] : array());
+      $access = call_user_func_array($callback, $arguments);
+    }
+    if ($access) {
+      $plugin_obj = new $plugin['class']($plugin, $params);
+      $plugin_output[$key] = $plugin_obj->view() + array(
+        '#title' => $plugin['title'],
+        '#weight' => isset($plugin['weight']) ? $plugin['weight'] : 0,
+      );
+    }
+
     // We need to get a submit and cancel button on each tab. If the plugin
     // is not returning a form element we need to add a submit button.
     // This is a fairly broad assumption.
diff --git a/includes/media.fields.inc b/includes/media.fields.inc
index 37bf72b..40a2708 100644
--- a/includes/media.fields.inc
+++ b/includes/media.fields.inc
@@ -110,8 +110,7 @@ function media_field_widget_settings_form($field, $instance) {
     '#weight' => 2,
   );
 
-  ctools_include('plugins');
-  $plugins = ctools_get_plugins('media', 'browser');
+  $plugins = media_get_browser_plugin_info();
   $form['browser_plugins'] = array(
     '#type' => 'checkboxes',
     '#title' => t('Enabled browser plugins'),
diff --git a/media.api.php b/media.api.php
index 4c06403..a27f10f 100644
--- a/media.api.php
+++ b/media.api.php
@@ -12,22 +12,38 @@
  * return a nested array of plugin information, keyed by plugin name. Each
  * plugin info array may have the following keys:
  * - title (required): A name for the tab in the media browser.
- * - handler (required): The class name of the handler. This class must
+ * - class (required): The class name of the handler. This class must
  *   implement a view() method, and may (should) extend the
  *   @link MediaBrowserPlugin MediaBrowserPlugin @endlink class.
  * - weight (optional): Integer to determine the tab order. Defaults to 0.
  * - access callback (optional): A callback for user access checks.
  * - access arguments (optional): An array of arguments for the user access
  *   check.
+ *
  * Additional custom keys may be provided for use by the handler.
+ *
+ * @see hook_media_browser_plugin_info_alter()
+ * @see media_get_browser_plugin_info()
  */
 function hook_media_browser_plugin_info() {
-  $plugins['media_upload'] = array(
+  $info['media_upload'] = array(
     'title' => t('Upload'),
-    'handler' => 'MediaBrowserUpload',
+    'class' => 'MediaBrowserUpload',
     'weight' => -10,
     'access callback' => 'user_access',
     'access arguments' => array('create files'),
   );
-  return $plugins;
+
+  return $info;
+}
+
+/**
+ * Alter the list of plugins for the media browser.
+ *
+ * @see hook_media_browser_plugin_info()
+ * @see media_get_browser_plugin_info()
+ */
+function hook_media_browser_plugin_info_alter() {
+  $info['media_upload']['title'] = t('Upload 2.0');
+  $info['media_upload']['class'] = 'MediaBrowserUploadImproved';
 }
diff --git a/media.module b/media.module
index baddb4f..e79e3d3 100644
--- a/media.module
+++ b/media.module
@@ -1073,24 +1073,6 @@ function media_flush_caches() {
 }
 
 /**
- * Implements hook_ctools_plugin_type().
- */
-function media_ctools_plugin_type() {
-  return array(
-    'browser' => array(
-      'version' => 1,
-      // Cache plugin information?
-      'cache' => TRUE,
-      // Which plugin info array keys contain classes?
-      'classes' => array('handler'),
-      // CTools will call hook_mymodule_myplugintype() to discover plugins.
-      'use hooks' => TRUE,
-      'hook' => 'media_browser_plugin_info',
-    ),
-  );
-}
-
-/**
  * Implements hook_ctools_plugin_api().
  *
  * Lets CTools know which plugin APIs are implemented by Media module.
@@ -1101,43 +1083,26 @@ function media_ctools_plugin_api($module, $api) {
       'version' => 1,
     );
   }
-
-  if ($module == 'media' && $api == 'browser') {
-    return array(
-      'version' => 1,
-      'path' => drupal_get_path('module', 'media') . '/includes',
-    );
-  }
 }
 
 /**
  * Implements hook_media_browser_plugin_info().
  */
 function media_media_browser_plugin_info() {
-  $plugins = array();
-
-  $plugins['upload'] = array(
+  $info['upload'] = array(
     'title' => t('Upload'),
     'weight' => -10,
-    'handler' => array(
-      'path' => drupal_get_path('module', 'media') . '/includes',
-      'file' => 'MediaBrowserUpload.inc',
-      'class' => 'MediaBrowserUpload',
-    ),
+    'class' => 'MediaBrowserUpload',
     // @todo Replace with appropriate file access function when that gets
     //   committed to File Entity project.
     'access callback' => 'media_access',
     'access arguments' => array('edit'),
   );
 
-  $plugins['library'] = array(
+  $info['library'] = array(
     'title' => t('Library'),
     'weight' => 10,
-    'handler' => array(
-      'path' => drupal_get_path('module', 'media') . '/includes',
-      'file' => 'MediaBrowserLibrary.inc',
-      'class' => 'MediaBrowserLibrary',
-    ),
+    'class' => 'MediaBrowserLibrary',
     // @todo Replace with appropriate file access function when that gets
     //   committed to File Entity project.
     'access callback' => 'media_access',
@@ -1152,14 +1117,10 @@ function media_media_browser_plugin_info() {
         if (!empty($display->display_options['title'])) {
           $title = $display->display_options['title'];
         }
-        $plugins["{$view->name}--{$display->id}"] = array(
+        $info["{$view->name}--{$display->id}"] = array(
           'title' => $title,
           'weight' => 11, // @TODO make this configurable.
-          'handler' => array(
-            'path' => drupal_get_path('module', 'media') . '/includes',
-            'file' => 'MediaBrowserView.inc',
-            'class' => 'MediaBrowserView',
-          ),
+          'class' => 'MediaBrowserView',
           'view_name' => $view->name,
           'view_display_id' => $display->id,
         );
@@ -1167,7 +1128,7 @@ function media_media_browser_plugin_info() {
     }
   }
 
-  return $plugins;
+  return $info;
 }
 
 /**
@@ -1284,3 +1245,27 @@ function media_load_all_exports($module, $directory, $extension, $name = NULL) {
 
   return $return;
 }
+
+/**
+ * Returns metadata describing Media browser plugins.
+ *
+ * @see hook_media_browser_plugin_info()
+ * @see hook_media_browser_plugin_info_alter()
+ */
+function media_get_browser_plugin_info() {
+  $info = &drupal_static(__FUNCTION__);
+
+  if (!isset($info)) {
+    $cid = 'media:browser:plugin:info:' . $GLOBALS['language']->language;
+    if ($cache = cache_get($cid)) {
+      $info = $cache->data;
+    }
+    else {
+      $info = module_invoke_all('media_browser_plugin_info');
+      drupal_alter('media_browser_plugin_info_alter', $info);
+      cache_set($cid, $info);
+    }
+  }
+
+  return $info;
+}
diff --git a/modules/media_internet/media_internet.module b/modules/media_internet/media_internet.module
index f88db5f..dedc464 100644
--- a/modules/media_internet/media_internet.module
+++ b/modules/media_internet/media_internet.module
@@ -17,31 +17,16 @@ function media_internet_menu() {
 }
 
 /**
- * Implements hook_ctools_plugin_api().
- */
-function media_internet_ctools_plugin_api($module, $api) {
-  if ($module == 'media' && $api == 'browser') {
-    return array(
-      'version' => 1,
-    );
-  }
-}
-
-/**
  * Implements hook_media_browser_plugin_info().
  */
 function media_internet_media_browser_plugin_info() {
-  $plugins = array();
-  $plugins['media_internet'] = array(
+  $info['media_internet'] = array(
     'title' => t('Web'),
-    'handler' => array(
-      'path' => drupal_get_path('module', 'media_internet') . '/includes',
-      'file' => 'MediaBrowserInternet.inc',
-      'class' => 'MediaBrowserInternet',
-    ),
+    'class' => 'MediaBrowserInternet',
     'access callback' => 'media_internet_access',
   );
-  return $plugins;
+
+  return $info;
 }
 
 /**
