diff --git export_ui/wordfilter.inc export_ui/wordfilter.inc
new file mode 100644
index 0000000..3bbd90d
--- /dev/null
+++ export_ui/wordfilter.inc
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * CTools Export UI Plugin Implementation.
+ */
+
+$plugin = array(
+  // The name of the table as found in the schema in hook_install. This
+  // must be an exportable type with the 'export' section defined.
+  'schema' => 'wordfilter',
+
+  // The access permission to use. If not provided it will default to
+  // 'administer site configuration'
+  'access' => 'administer words filtered',
+
+  // You can actually define large chunks of the menu system here. Nothing
+  // is required here. If you leave out the values, the prefix will default
+  // to admin/structure and the item will default to the plugin name.
+  'menu' => array(
+    'menu prefix' => 'admin/config/content',
+    'menu item' => 'wordfilter',
+    // Title of the top level menu. Note this should not be translated,
+    // as the menu system will translate it.
+    'menu title' => 'Word Filter', 
+    // Description of the top level menu, which is usually needed for
+    // menu items in an administration list. Will be translated
+    // by the menu system.
+    'menu description' => 'Replaces words or phrases inside posts with filtered versions.',
+  ),
+
+  // These are required to provide proper strings for referring to the
+  // actual type of exportable. "proper" means it will appear at the
+  // beginning of a sentence.
+  'title singular' => t('word'),
+  'title singular proper' => t('Words'),
+  'title plural' => t('words'),
+  'title plural proper' => t('Words'),
+
+  // This will provide you with a form for editing the properties on your
+  // exportable, with validate and submit handler.
+  //
+  // The item being edited will be in $form_state['item'].
+  //
+  // The submit handler is only responsible for moving data from
+  // $form_state['values'] to $form_state['item'].
+  //
+  // All callbacks will accept &$form and &$form_state as arguments.
+  'form' => array(
+    'settings' => 'wordfilter_admin_edit_form',
+    'submit' => 'wordfilter_admin_edit_form_submit',
+  ),
+);
+
+
+
+/**
+ * Edit word filter form.
+ */
+function wordfilter_admin_edit_form(&$form, &$form_state) {
+  $word = $form_state['item'];
+  // CTools tries to be nice by already giving us this field, but no thanks.
+  unset($form['info']['words']);
+
+  $form['id'] = array(
+    '#type' => 'hidden',
+    '#value' => isset($word->id) ? $word->id : '',
+  );
+
+  $form['words'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Word or phrase to filter'),
+    '#default_value' => !empty($word->words) ? $word->words : '',
+    '#description' => t('Enter the word or phrase you want to filter.'),
+    '#size' => 50,
+    '#maxlength' => 255,
+    '#required' => TRUE,
+  );
+  $form['replacement'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Replacement text'),
+    '#description' => t('Enter the filtered version of the word or phrase to replace the original word or phrase.'),
+    '#default_value' => !empty($word->replacement) ? $word->replacement : variable_get('wordfilter_default_replacement', '[filtered word]'),
+    '#size' => 50,
+    '#maxlength' => 255,
+  );
+  $form['standalone'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Stand-alone'),
+    '#default_value' => isset($word->standalone) ? $word->standalone : 0,
+    '#description' => t('When checked, the word or phrase will only be filtered when found as a separate word or phrase (i.e. prefixed and suffixed by spaces or "whitespace"). A period one character after a word or phrase will exclude the words or phrases from replacement in stand-alone mode. This is useful for preventing accidental word or phrase filtering with short or common words or phrases.'),
+  );
+
+  return $form;
+}
+
+/**
+ * Edit word filter form submit handler.
+ */
+function wordfilter_admin_edit_form_submit($form, &$form_state) {
+  $word = (object) $form_state['values'];
+  wordfilter_word_save($word);
+  watchdog('wordfilter', 'Updated filter for: %word', array('%word' => $form_state['values']['words']));
+  drupal_set_message(t('Updated filter for: %word', array('%word' => $form_state['values']['words'])));
+}
diff --git wordfilter.crud.inc wordfilter.crud.inc
new file mode 100644
index 0000000..73b3682
--- /dev/null
+++ wordfilter.crud.inc
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * CRUD API for WordFilter module
+ */
+
+/**
+ * Load a single word.
+ *
+ * @param string $word
+ *   The word to load.
+ */
+function wordfilter_word_load($word) {
+  ctools_include('export');
+
+  $result = ctools_export_load_object('wordfilter', 'names', array($words));
+  if (isset($result[$word])) {
+    return $result[$word];
+  }
+}
+
+/**
+ * Load all words.
+ *
+ * @param bool $reset
+ *   Whether to clear the cache.
+ * @return array
+ *   An array of word objects, keyed by word.
+ */
+function wordfilter_word_load_all($reset = FALSE) {
+  $list = &drupal_static(__FUNCTION__);
+
+  if ($reset || !isset($list) || !$cache = cache_get('wordfilter')) {
+    ctools_include('export');
+    $list = ctools_export_load_object('wordfilter');
+
+    cache_set('wordfilter', $list);
+  }
+
+  return $list;
+}
+
+/**
+ * Save a word.
+ *
+ * @param stdClass $word
+ *   The word object to save.
+ * @param bool $clear_cache
+ *   Whether to clear the cache or not.
+ */
+function wordfilter_word_save(stdClass $word, $clear_cache = TRUE) {
+  $update = (isset($word->id) && is_numeric($word->id)) ? array('id') : array();
+
+  $result = drupal_write_record('wordfilter', $word, $update);
+
+  if ($result && $clear_cache) {
+    _wordfilter_cache_clear();
+  }
+
+  return $result;
+}
+
+/**
+ * Clear static and Drupal caches.
+ */
+function _wordfilter_cache_clear() {
+  drupal_static_reset('wordfilter_word_load_all');
+  cache_clear_all('*', 'cache_filter', TRUE);
+  cache_clear_all('wordfilter', 'cache');
+}
diff --git wordfilter.info wordfilter.info
index a109b81..00e929a 100644
--- wordfilter.info
+++ wordfilter.info
@@ -1,8 +1,6 @@
 name = Word Filter
 description = Replaces words inside posts with filtered versions.
 core = 7.x
-files[] = wordfilter.module
-files[] = workfilter.install
 ; Information added by drupal.org packaging script on 2011-02-25
 version = "7.x-1.x-dev"
 core = "7.x"
diff --git wordfilter.install wordfilter.install
index 25b3488..ea01322 100755
--- wordfilter.install
+++ wordfilter.install
@@ -11,6 +11,20 @@
 function wordfilter_schema() {
   $schema['wordfilter'] = array(
     'description' => 'The {wordfilter} table stores user ids of users and other users that they have chosen to ignore.',
+    'export' => array(
+      'key' => 'words',
+      'key name' => 'Word',
+      'primary key' => 'id',
+      'identifier' => 'word', // Exports will be as $word
+      'default hook' => 'wordfilter_default_words',  // Function hook name.
+      'load all callback' => 'wordfilter_word_load_all',
+      'api' => array(
+        'owner' => 'wordfilter',
+        'api' => 'wordfilter_words',  // Base name for api include files.
+        'minimum_version' => 1,
+        'current_version' => 1,
+      ),
+    ),
     'fields' => array(
       'id' => array(
         'description' => 'The ID of the wordfilter word pair.',
diff --git wordfilter.module wordfilter.module
index c00eae6..18c5f05 100644
--- wordfilter.module
+++ wordfilter.module
@@ -5,6 +5,8 @@
  * Replaces words inside posts with filtered versions.
  */
 
+require_once 'wordfilter.crud.inc';
+
 /**
  * Implements hook_help().
  *
@@ -50,29 +52,13 @@ function wordfilter_permission() {
 function wordfilter_menu() {
   $items = array();
 
-  $items['admin/config/content/wordfilter'] = array(
-    'title' => 'Word filter',
-    'description' => 'Replaces words or phrases inside posts with filtered versions.',
-    'page callback' => 'wordfilter_admin_list',
-    'access callback' => 'user_access',
-    'access arguments' => array('administer words filtered'),
-  );
-
-  $items['admin/config/content/wordfilter/list'] = array(
-    'title' => 'List',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'access callback' => 'user_access',
-    'access arguments' => array('administer words filtered'),
-    'weight' => -10,
-  );
-
-  $items['admin/config/content/wordfilter/add'] = array(
-    'title' => 'Add',
+  $items['admin/config/content/wordfilter/bulk-add'] = array(
+    'title' => 'Bulk Add',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('wordfilter_admin_add_form'),
     'access callback' => 'user_access',
     'access arguments' => array('administer words filtered'),
-    'type' => MENU_LOCAL_TASK,
+    'type' => MENU_LOCAL_ACTION,
   );
 
   $items['admin/config/content/wordfilter/test'] = array(
@@ -84,24 +70,6 @@ function wordfilter_menu() {
     'type' => MENU_LOCAL_TASK,
   );
 
-  $items['admin/config/content/wordfilter/edit/%'] = array(
-    'title' => 'Edit',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('wordfilter_admin_edit_form', 5),
-    'access callback' => 'user_access',
-    'access arguments' => array('administer words filtered'),
-    'type' => MENU_CALLBACK,
-  );
-
-  $items['admin/config/content/wordfilter/delete/%'] = array(
-    'title' => 'Delete',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('wordfilter_admin_form_delete_confirm', 5),
-    'access callback' => 'user_access',
-    'access arguments' => array('administer words filtered'),
-    'type' => MENU_CALLBACK,
-  );
-
   return $items;
 }
 
@@ -153,6 +121,15 @@ function wordfilter_settings_form() {
 }
 
 /**
+ * Implements hook_ctools_plugin_directory().
+ */
+function wordfilter_ctools_plugin_directory($module, $plugin) {
+  if ($module == 'ctools' && $plugin == 'export_ui') {
+    return 'export_ui';
+  }
+}
+
+/**
  * Delete a word from the word filter list delete
  * confirmation callback
  */
@@ -231,7 +208,7 @@ function wordfilter_block_view($delta = '') {
 function _wordfilter_table() {
   $content .= '<div align="center">';
   $content .= '<table border="0" cellspacing="1" cellpadding="0">';
-  $list = _wordfilter_list();
+  $list = wordfilter_word_load_all();
   foreach ($list as $filtered_word) {
     $alt = implode(' &nbsp; ', explode(' ', check_plain($filtered_word->words)));
     $content .= '<tr><td align="left">' . check_plain($filtered_word->replacement) . '</td><td align="right">&nbsp;' . $alt . '</td></tr>';
@@ -287,11 +264,11 @@ function _wordfilter_filter_settings($form, $form_state, $filter, $format, $defa
  */
 function wordfilter_filter_process($text) {
   $text = ' ' . $text . ' ';
-  $list = _wordfilter_list();
+  $list = wordfilter_word_load_all();
   $utf8 = variable_get('wordfilter_use_utf8_flag', FALSE);
   foreach ($list as $word) {
     // Prevent mysterious empty value from blowing away the node title.
-    if (!empty($word->words)) {
+    if (!empty($word->words) && empty($word->disabled)) {
       $replacement = ($word->replacement) ? $word->replacement : variable_get('wordfilter_default_replacement', '[filtered word]');
 
       if ($word->standalone) {
@@ -329,141 +306,9 @@ function wordfilter_filter_process($text) {
 }
 
 /**
- * Query to get the list of words to filter in the
- * filter processing stage. Does not use a pager.
- */
-function _wordfilter_list($refresh = FALSE) {
-  static $list = NULL;
-  $cache = cache_get('wordfilter');
-  if (!$cache || $refresh) {
-    $list = db_select('wordfilter', 'w')
-      ->fields('w')
-      ->orderBy('words', 'DESC')
-      ->execute()
-      ->fetchAll();
-    cache_set('wordfilter', $list);
-  }
-  else {
-    $list = $cache->data;
-  }
-  return $list;
-}
-
-/**
- * Wordfilter admin settings page callback.
- */
-function wordfilter_admin_list() {
-  $build = array();
-  $header = array(
-    array('data' => t('Word to filter'), 'field' => 'words', 'sort' => 'asc'),
-    array('data' => t('Replacement text'), 'field' => 'replacement'),
-    array('data' => t('Standalone'), 'field' => 'standalone'),
-    array('data' => t('Operations'), 'colspan' => 2)
-  );
-  $rows = array();
-  
-  $list = db_select('wordfilter', 'w')
-    ->fields('w')
-    ->extend('PagerDefault')
-    ->extend('TableSort')
-    ->orderByHeader($header)
-    ->limit(10)
-    ->execute();
-  
-  foreach ($list as $word) {
-    $rows[] = array(
-      check_plain($word->words),
-      ($word->replacement) ? check_plain($word->replacement) : check_plain(variable_get('wordfilter_default_replacement', '[filtered word]')),
-      ($word->standalone) ? t('Yes') : t('No'),
-      l(t('Edit word'), 'admin/config/content/wordfilter/edit/' . $word->id),
-      l(t('Delete word'), 'admin/config/content/wordfilter/delete/' . $word->id),
-    );
-  }
-  $build['wordfilter_settings_form'] = drupal_get_form('wordfilter_settings_form');
-  $build['table'] = array(
-    '#markup' => theme('table', array('header' => $header, 'rows' => $rows)),
-  );
-  $build['pager'] = array(
-    '#markup' => theme('pager')
-  );
-
-  return $build;
-}
-
-/**
- * Edit word filter form.
- */
-function wordfilter_admin_edit_form($form, &$form_state, $word_id = NULL) {
-  if (!isset($word_id) || !is_numeric($word_id)) {
-    drupal_set_message(t('The wordfilter ID of the word or phrase you are trying to edit is missing or is not a number.'), 'error');
-    drupal_goto('admin/config/content/wordfilter');
-  }
-  
-  $word = db_select('wordfilter', 'w')
-    ->fields('w')
-    ->condition('w.id', $word_id)
-    ->execute()
-    ->fetch();
-
-
-  $form = array();
-  $form['id'] = array(
-    '#type' => 'hidden',
-    '#value' => $word->id,
-  );
-
-  $form['words'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Word or phrase to filter'),
-    '#default_value' => $word->words,
-    '#description' => t('Enter the word or phrase you want to filter.'),
-    '#size' => 50,
-    '#maxlength' => 255,
-    '#required' => TRUE,
-  );
-  $form['replacement'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Replacement text'),
-    '#description' => t('Enter the filtered version of the word or phrase to replace the original word or phrase.'),
-    '#default_value' => ($word->replacement) ? $word->replacement : variable_get('wordfilter_default_replacement', '[filtered word]'),
-    '#size' => 50,
-    '#maxlength' => 255,
-  );
-  $form['standalone'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Stand-alone'),
-    '#default_value' => $word->standalone,
-    '#description' => t('When checked, the word or phrase will only be filtered when found as a separate word or phrase (i.e. prefixed and suffixed by spaces or "whitespace"). A period one character after a word or phrase will exclude the words or phrases from replacement in stand-alone mode. This is useful for preventing accidental word or phrase filtering with short or common words or phrases.'),
-  );
-
-  $form['Save word filter'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save word filter'),
-  );
-  $form['cancel'] = array(
-    '#type' => 'submit',
-    '#value' => t('Cancel'),
-  );
-
-  return $form;
-}
-
-/**
- * Edit word filter form submit handler.
- */
-function wordfilter_admin_edit_form_submit($form, &$form_state) {
-  drupal_write_record('wordfilter', $form_state['values'], 'id');
-  watchdog('wordfilter', 'Updated filter for: %word', array('%word' => $form_state['values']['words']));
-  drupal_set_message(t('Updated filter for: %word', array('%word' => $form_state['values']['words'])));
-  $form_state['redirect'] = 'admin/config/content/wordfilter';
-  cache_clear_all('*', 'cache_filter', TRUE);
-  cache_clear_all('wordfilter', 'cache');
-}
-
-/**
  * Add word filter form.
  */
-function wordfilter_admin_add_form() {
+function wordfilter_admin_bulk_add_form() {
   $form = array();
 
   $form['help'] = array(
@@ -510,7 +355,7 @@ function wordfilter_admin_add_form() {
 /**
  * Add word filter form submit handler.
  */
-function wordfilter_admin_add_form_submit($form, &$form_state) {
+function wordfilter_admin_bulk_add_form_submit($form, &$form_state) {
   $pairs = explode("\n", $form_state['values']['words']);
   $pairs = array_map('trim', $pairs);
   $pairs = array_filter($pairs, 'strlen');
@@ -524,13 +369,13 @@ function wordfilter_admin_add_form_submit($form, &$form_state) {
     if (!$row->language) {
       $row->language = $form_state['values']['language'];
     }
-    drupal_write_record('wordfilter', $row);
+    // Don't clear the cache.
+    wordfilter_word_save($row, FALSE);
     watchdog('wordfilter', 'Added filter for: %word', array('%word' => $row->words));
     drupal_set_message(t('Added filter for: %word', array('%word' => $row->words)));
   }
+  _wordfilter_cache_clear();
   $form_state['redirect'] = 'admin/config/content/wordfilter';
-  cache_clear_all('*', 'cache_filter', TRUE);
-  cache_clear_all('wordfilter', 'cache');
 }
 
 /**
