diff --git a/ctools.install b/ctools.install
index ca77e96b8505f90dd6e442da7db8aeba47aea896..938ba8504c07e188ecf8e2a87804cc6c82950d96 100644
--- a/ctools.install
+++ b/ctools.install
@@ -38,11 +38,43 @@ function ctools_schema_2() {
   // update the 'name' field to be 128 bytes long:
   $schema['ctools_object_cache']['fields']['name']['length'] = 128;
 
+  // DO NOT MODIFY THIS TABLE -- this definition is used to create the table.
+  // Changes to this table must be made in schema_3 or higher.
+  $schema['ctools_css_cache'] = array(
+    'description' => t('A special cache used to store CSS that must be non-volatile.'),
+    'fields' => array(
+      'cid' => array(
+        'type' => 'varchar',
+        'length' => '128',
+        'description' => t('The CSS ID this cache object belongs to.'),
+      ),
+      'filename' => array(
+        'type' => 'varchar',
+        'length' => '255',
+        'description' => t('The filename this CSS is stored in.'),
+      ),
+      'css' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'description' => t('CSS being stored.'),
+        'serialize' => TRUE,
+      ),
+      'filter' => array(
+         'type' => 'int',
+         'size' => 'tiny',
+         'description' => t('Whether or not this CSS needs to be filtered.'),
+       ),
+    ),
+    'primary key' => array('cid'),
+  );
+
   return $schema;
 }
 
 /**
- * ctools' initial schema; separated for the purposes of updates.
+ * CTools' initial schema; separated for the purposes of updates.
+ *
+ * DO NOT MAKE CHANGES HERE. This schema version is locked.
  */
 function ctools_schema_1() {
   $schema['ctools_object_cache'] = array(
@@ -71,7 +103,7 @@ function ctools_schema_1() {
         'description' => t('The time this cache was created or updated.'),
       ),
       'data' => array(
-        'type' => 'blob',
+        'type' => 'text',
         'size' => 'big',
         'description' => t('Serialized data being stored.'),
         'serialize' => TRUE,
@@ -99,3 +131,16 @@ function ctools_update_6001() {
 
   return $ret;
 }
+
+/**
+ * Add the new css cache table.
+ */
+function ctools_update_6002() {
+  $ret = array();
+
+  // Schema 2 is locked and should not be changed.
+  $schema = ctools_schema_2();
+
+  db_create_table($ret, 'ctools_css_cache', $schema['ctools_css_cache']);
+  return $ret;
+}
diff --git a/includes/css.inc b/includes/css.inc
index 25830176cd980ac7c7b2bb4085dc11fc7b8345e9..e1a4fccd6ea3884ac570d14ad56f4c4249618ed5 100644
--- a/includes/css.inc
+++ b/includes/css.inc
@@ -31,8 +31,85 @@
  * a backup method of re-generating the CSS cache in case it is removed, so
  * that it is easy to force a re-cache by simply deleting the contents of the
  * directory.
+ *
+ * Finally, if for some reason your application cannot store the filename
+ * (which is true of Panels where the style can't force the display to
+ * resave unconditionally) you can use the ctools storage mechanism. You
+ * simply have to come up with a unique Id:
+ *
+ * @code
+ *   $filename = ctools_css_store($id, $css, TRUE);
+ * @endcode
+ *
+ * Then later on:
+ * @code
+ *   $filename = ctools_css_retrieve($id);
+ *   drupal_add_css($filename);
+ * @endcode
+ *
+ * The CSS that was generated will be stored in the database, so even if the
+ * file was removed the cached CSS will be used. It is your module's
+ * responsibility to know when this CSS is outdated and needs to be
+ * changed. This storage is non-volatile and will never be removed unless
+ * you remove it:
+ *
+ * @code
+ *   ctools_css_clear($id);
+ * @endcode
  */
 
+/**
+ * Store CSS with a given id and return the filename to use.
+ *
+ * This function associates a piece of CSS with an id, and stores the
+ * cached filename and the actual CSS for later use with
+ * ctools_css_retrieve.
+ */
+function ctools_css_store($id, $css, $filter = TRUE) {
+  $filename = db_result(db_query("SELECT filename FROM {ctools_css_cache} WHERE cid = '%s'", $id));
+  if ($filename) {
+    file_delete($filename);
+  }
+  // Remove any previous records.
+  db_query("DELETE FROM {ctools_css_cache} WHERE cid = '%s'", $id);
+
+  $filename = ctools_css_cache($css, $filter);
+
+  db_query("INSERT INTO {ctools_css_cache} (cid, filename, css, filter) VALUES ('%s', '%s', '%s', %d)", $id, $filename, $css, $filter);
+
+  return $filename;
+}
+
+/**
+ * Retrieve a filename associated with an id of previously cached CSS.
+ *
+ * This will ensure the file still exists and, if not, create it.
+ */
+function ctools_css_retrieve($id) {
+  $cache = db_fetch_object(db_query("SELECT * FROM {ctools_css_cache} WHERE cid = '%s'", $id));
+  if (!$cache) {
+    return;
+  }
+
+  if (!file_exists($cache->filename)) {
+    $filename = ctools_css_cache($cache->css, $cache->filter);
+    if ($filename != $cache->filename) {
+      db_query("UPDATE {ctools_css_cache} SET filename = '%s' WHERE cid = '%s'", $filename, $id);
+      $cache->filename = $filename;
+    }
+  }
+
+  return $cache->filename;
+}
+
+/**
+ * Remove stored CSS and any associated file.
+ */
+function ctools_css_clear($id) {
+  db_query("DELETE FROM {ctools_css_cache} WHERE cid = '%s'", $id);
+}
+
+
 /**
  * Write a chunk of CSS to a temporary cache file and return the file name.
  *
@@ -50,6 +127,9 @@
  * @param $filter
  *   If TRUE the css will be filtered. If FALSE the text will be cached
  *   as-is.
+ *
+ * @return $filename
+ *   The filename the CSS will be cached in.
  */
 function ctools_css_cache($css, $filter = TRUE) {
   if ($filter) {