diff --git a/cloudfiles-cdn.php b/cloudfiles-cdn.php
index b91e41afc5d96c8e1becae9abccd03d89dfc904d..f357f94222592cf8ae90fb6ded4000422b223560 100644
--- a/cloudfiles-cdn.php
+++ b/cloudfiles-cdn.php
@@ -10,9 +10,22 @@ Author URI: http://voceconnect.com/
 
 class CloudfilesCdn {
 
+	var $submenu_general;
+
+	var $option_group = 'cloudfiles_cdn';
+	static $option_general = 'cloudfiles_cdn_general';
+
 	function __construct() {
-		if (!defined('CF_USERNAME') || !defined('CF_API_KEY') || !defined('CF_CONTAINER') || !defined('CF_CONTAINER_URL')) {
-			// these should be defined in wp-config. if not, don't do anything here.
+
+		// relies on Voce_Settings
+		if (!class_exists('Voce_Settings')) {
+			return;
+		}
+
+		add_action('admin_menu', array($this, 'add_options_page'));
+
+		if (!self::get_setting('username') || !self::get_setting('api_key') || !self::get_setting('container') || !self::get_setting('root_url')) {
+			// these are the minimum required settings. should add an admin notice later...
 			return;
 		}
 
@@ -21,6 +34,57 @@ class CloudfilesCdn {
 		add_filter('wp_generate_attachment_metadata', array('CloudfilesCdn', 'catch_wp_generate_attachment_metadata'));
 		add_filter('bp_core_avatar_cropstore', array('CloudfilesCdn', 'catch_bp_core_avatar_cropstore'));
 		add_action('bp_core_avatar_save', array('CloudfilesCdn', 'catch_bp_core_avatar_save'), 10, 2);
+
+	}
+
+	/**
+	 * get general setting
+	 *
+	 * @param string $setting setting name
+	 * @return mixed setting value or false if not set
+	 */
+	public static function get_setting($setting) {
+		$settings = get_option(self::$option_general);
+		return (isset($settings[$setting])) ? $settings[$setting] : false;
+	}
+
+	/**
+	 * adds the options page
+	 *
+	 * @return void
+	 */
+	public function add_options_page() {
+		$this->submenu_general = add_options_page('Cloudfiles CDN', 'Cloudfiles CDN', 'manage_options', self::$option_general, array(&$this, 'submenu_general'));
+		$settings = new Voce_Settings(self::$option_general, self::$option_general);
+
+		$section = $settings->add_section('api', 'Cloudfiles API Settings', $this->submenu_general);
+		$section->add_field('username', 'Username', 'field_input');
+		$section->add_field('api_key', 'API Key', 'field_input');
+		$section->add_field('container', 'Container Name', 'field_input', array('description' => 'The container to store files in.'));
+		$section->add_field('root_url', 'Root URL', 'field_input', array('description' => 'The root URL to the container without a trailing slash.'));
+		$section->add_field('file_extensions', 'File Extensions', 'field_input');
+		$section->add_field('enable_debug', 'Enable Debugging?', 'field_checkbox', array('description' => 'Enable error_log() to log upload/delete actions.'));
+
+	}
+
+	/**
+	 * callback to display submenu_external
+	 *
+	 * @return void
+	 */
+	function submenu_general() {
+		?>
+		<div class="wrap">
+			<h2>Cloudfiles CDN Settings</h2>
+			<form method="post" action="options.php">
+				<?php settings_fields(self::$option_general); ?>
+				<?php do_settings_sections($this->submenu_general); ?>
+				<p class="submit">
+					<input name="Submit" type="submit" class="button-primary" value="<?php esc_attr_e('Save Changes'); ?>" />
+				</p>
+				</form>
+			</div>
+			<?php
 	}
 
 	/**
@@ -38,7 +102,7 @@ class CloudfilesCdn {
 		$files[] = str_replace('-avatar2', '-avatar1', $files[0]);
 
 		foreach ($files as $file) {
-			if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
+			if (self::get_setting('enable_debug'))
 				error_log("DELETING OLD BP AVATAR: $file");
 
 			self::delete_file($file);
@@ -56,7 +120,7 @@ class CloudfilesCdn {
 			$relative_file_path = str_replace(ABSPATH, '', $file);
 			$file_type = wp_check_filetype($file);
 
-			if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
+			if (self::get_setting('enable_debug'))
 				error_log("UPLOADING BP AVATAR: $relative_file_path");
 			self::upload_file($file, $file_type['type'], $relative_file_path);
 		}
@@ -80,7 +144,7 @@ class CloudfilesCdn {
 			$file = $size_data['file'];
 			$relative_file_path = self::get_blog_path() . 'files' . trailingslashit($upload_dir['subdir']) . $file;
 			$file_type = wp_check_filetype($file);
-			if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
+			if (self::get_setting('enable_debug'))
 				error_log("UPLOADING INTERMEDIATE SIZE: $relative_file_path");
 			self::upload_file($upload_path . $file, $file_type['type'], $relative_file_path);
 		}
@@ -95,7 +159,7 @@ class CloudfilesCdn {
 	 * @return string filename
 	 */
 	private static function get_local_filename($url) {
-		return ABSPATH . str_replace(trailingslashit(CF_CONTAINER_URL), '', $url);
+		return ABSPATH . str_replace(trailingslashit(self::get_setting('root_url')), '', $url);
 	}
 
 	/**
@@ -112,7 +176,7 @@ class CloudfilesCdn {
 
 		$blog_path = self::get_blog_path();
 		$relative_url = $blog_path . self::remove_site_url($upload['url']);
-		if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
+		if (self::get_setting('enable_debug'))
 			error_log("UPLOADING: $relative_url");
 
 		// upload file
@@ -142,7 +206,7 @@ class CloudfilesCdn {
 		$relative_path = self::get_blog_path() . 'files/' . $file;
 
 		// delete the file from the CDN if it is on there
-		if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
+		if (self::get_setting('enable_debug'))
 			error_log("DELETING FILE: $relative_path");
 		self::delete_file(str_replace(ABSPATH, '', $relative_path));
 
@@ -174,9 +238,9 @@ class CloudfilesCdn {
 	 * @return void
 	 */
 	private static function get_relative_file($file) {
-		$cont_url = CF_CONTAINER_URL;
+		$cont_url = self::get_setting('root_url');
 		if (strpos($file, $cont_url) !== false) {
-			if ($file_parts = explode(trailingslashit(CF_CONTAINER_URL), $file)) {
+			if ($file_parts = explode(trailingslashit(self::get_setting('root_url')), $file)) {
 				// prepended w/bad upload path (e.g. http://c0002127.cdn1.cloudfiles.rackspacecloud.com/wp-content/uploads/2010/07/http://c0002127.cdn1.cloudfiles.rackspacecloud.com/wp-content/uploads/2010/07/653106995_338e53fb1416-150x150.jpg)
 				if (isset($file_parts[2])) {
 					return $file_parts[2];
@@ -198,7 +262,7 @@ class CloudfilesCdn {
 	 */
 	private static function delete_file($file) {
 		require_once(trailingslashit(dirname(__FILE__)) . 'cloudfiles/cloudfiles.php');
-		$auth = new CF_Authentication(CF_USERNAME, CF_API_KEY);
+		$auth = new CF_Authentication(self::get_setting('username'), self::get_setting('api_key'));
 
 		try {
 			$auth->authenticate();
@@ -208,7 +272,7 @@ class CloudfilesCdn {
 		}
 
 		$conn = new CF_Connection($auth);
-		$container = $conn->get_container(CF_CONTAINER);
+		$container = $conn->get_container(self::get_setting('container'));
 
 		try {
 			$obj = $container->get_object($file);
@@ -231,7 +295,7 @@ class CloudfilesCdn {
 	 */
 	public static function cdn_attachment_url($url, $post_id) {
 		if ($file = get_post_meta($post_id, '_wp_attached_file', true)) {
-			if (strpos($file, CF_CONTAINER_URL) !== false) {
+			if (strpos($file, self::get_setting('root_url')) !== false) {
 				return $file;
 			}
 		}
@@ -249,7 +313,7 @@ class CloudfilesCdn {
 	 */
 	private static function upload_file($file, $file_type, $file_url) {
 		require_once(trailingslashit(dirname(__FILE__)) . 'cloudfiles/cloudfiles.php');
-		$auth = new CF_Authentication(CF_USERNAME, CF_API_KEY);
+		$auth = new CF_Authentication(self::get_setting('username'), self::get_setting('api_key'));
 
 		try {
 			$auth->authenticate();
@@ -259,7 +323,7 @@ class CloudfilesCdn {
 		}
 
 		$conn = new CF_Connection($auth);
-		$container = $conn->get_container(CF_CONTAINER);
+		$container = $conn->get_container(self::get_setting('container'));
 
 		$obj = $container->create_object($file_url);
 		$obj->content_type = $file_type;
@@ -271,7 +335,7 @@ class CloudfilesCdn {
 			return false;
 		}
 
-		if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
+		if (self::get_setting('enable_debug'))
 			error_log("UPLOADED: $file, $file_type, $file_url");
 
 		return true;
@@ -288,9 +352,10 @@ class CloudfilesCdn {
 	}
 
 	private static function remove_cdn_url($url) {
-		return str_replace(trailingslashit(CF_CONTAINER_URL), '', $url);
+		return str_replace(trailingslashit(self::get_setting('root_url')), '', $url);
 	}
 
 }
 
+require_once('voce-settings.php');
 $cdn = new CloudfilesCdn();
\ No newline at end of file
diff --git a/voce-settings.php b/voce-settings.php
new file mode 100644
index 0000000000000000000000000000000000000000..88f2de2d4ceaacb3d59097a961ae75ddd627f7e2
--- /dev/null
+++ b/voce-settings.php
@@ -0,0 +1,218 @@
+<?php
+/*  Copyright 2010 Chris Scott (cscott@voceconnect.com)
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License, version 2, as
+    published by the Free Software Foundation.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+if (!class_exists('Voce_Settings')) {
+	class Voce_Settings {
+
+		private $page_name;
+		private $option_name;
+		private $page;
+		private $section;
+
+		public function __construct($option_group, $option_name, $sanitize_callback = '') {
+			register_setting($option_group, $option_name, $sanitize_callback);
+			$this->option_name = $option_name;
+		}
+
+		public function add_section($name, $title, $page, $callback = null) {
+			// use blank output to allow not setting a callback if no output is desired between heading and fields
+			$callback = (null === $callback) ? create_function('', 'echo \'\';') : $callback;
+
+			add_settings_section($name, $title, $callback, $page);
+			$this->section = $name;
+			$this->page = $page;
+			return $this;
+		}
+
+		public function add_field($id, $label, $type, $args = null, $page = null, $section = null) {
+			$section = (null === $section) ? $this->section : $section;
+			$page = (null === $page) ? $this->page : $page;
+			$this->add_settings_field(
+				$id,
+				$label,
+				$type,
+				$page,
+				$section,
+				$args
+			);
+		}
+
+
+		/**
+		 * helper to use the settings API to add a field
+		 *
+		 * @param string $id the HTML ID for the field
+		 * @param string $label the label tag text
+		 * @param string $display_callback the function to use for the field display
+		 * @param string $extra_args extra args to pass to the callback
+		 * @return void
+		 */
+		function add_settings_field($id, $label, $display_callback, $page, $section, $extra_args = null) {
+
+			$args = array(
+				'id' => $id,
+				'label_for' => $id
+			);
+
+			if (is_array($extra_args)) {
+				$args += $extra_args;
+			}
+
+			add_settings_field(
+				$id,
+				$label,
+				array(&$this, $display_callback),
+				$page,
+				$section,
+				$args
+			);
+		}
+
+
+		/**
+		 * checkbox callback for settings API
+		 *
+		 * @param string $args
+		 * @return string checkbox
+		 */
+		function field_checkbox($args) {
+			$options = get_option($this->option_name);
+
+			$defaults = array('prepend_field' => '', 'append_field' => '', 'class' => '');
+			$args = wp_parse_args($args, $defaults);
+			extract($args);
+
+			$description = ($description) ? sprintf('<p><span class="description">%s</span></p>', $description) : '';
+
+			$checked = checked($options[$id], '1', false);
+
+			echo sprintf(
+				"%s<input id='%s' name='{$this->option_name}[%s]' type='checkbox' value='1' %s class='%s' />%s%s",
+				$prepend_field,
+				$id,
+				$id,
+				$checked,
+				$class,
+				$append_field,
+				$description
+			);
+		}
+
+			/**
+			 * input type text callback for settings API
+			 *
+			 * @param string $args
+			 * @return string input
+			 */
+			function field_input($args) {
+				$options = get_option($this->option_name);
+
+				$defaults = array('prepend_field' => '', 'append_field' => '', 'class' => 'regular-text', 'type' => 'text');
+				$args = wp_parse_args($args, $defaults);
+				extract($args);
+
+				$description = (isset($description) && $description) ? sprintf('<p><span class="description">%s</span></p>', $description) : '';
+
+				if (!isset($value)) {
+					if (isset($options[$id])) {
+						$value = $options[$id];
+					} else {
+						$value = '';
+					}
+				}
+
+				echo sprintf(
+					"%s<input id='%s' name='{$this->option_name}[%s]' type='%s' class='%s' value='%s' />%s%s",
+					$prepend_field,
+					$id,
+					$id,
+					$type,
+					$class,
+					esc_attr($value),
+					$append_field,
+					$description
+				);
+			}
+
+			/**
+			 * textarea callback for settings API
+			 *
+			 * @param string $args
+			 * @return string textarea
+			 */
+			function field_textarea($args) {
+				$options = get_option($this->option_name);
+				$id = $args['id'];
+
+				$defaults = array('prepend_field' => '', 'append_field' => '', 'columns' => 40, 'rows' => 8, 'class' => 'large-text');
+				$args = wp_parse_args($args, $defaults);
+				extract($args);
+
+				$description = (isset($description) && $description) ? sprintf('<p><span class="description">%s</span></p>', $description) : '';
+
+				echo sprintf(
+					"%s<textarea id='%s' name='{$this->option_name}[%s]' columns='%s' rows='%s' class='%s' />%s</textarea>%s%s",
+					$prepend_field,
+					$id,
+					$id,
+					$columns,
+					$rows,
+					$class,
+					esc_attr($options[$id]),
+					$append_field,
+					$description
+				);
+			}
+
+			function field_radio($args) {
+				$options = get_option($this->option_name);
+				$id = $args['id'];
+
+				$defaults = array('type' => 'radio', 'class' => '');
+				$args = wp_parse_args($args, $defaults);
+				extract($args);
+
+				if (!$items) {
+					return;
+				}
+
+				echo '<fieldset><p>';
+				foreach ($items as $item) {
+					if (!empty($options[$id])) {
+						$checked = checked($options[$id], $item['value'], false);
+					} else {
+						$checked = false;
+					}
+
+					echo sprintf(
+						"<label> <input id='%s' name='{$this->option_name}[%s]' type='%s', class='%s' value='%s' %s /> %s</label><br />",
+						$id,
+						$id,
+						$type,
+						$class,
+						esc_attr($item['value']),
+						$checked,
+						esc_html($item['text'])
+					);
+				}
+				echo '</fieldset></p>';
+
+				$description = (isset($description) && $description) ? sprintf('<p><span class="description">%s</span></p>', $description) : '';
+				echo $description;
+			}
+	}
+}
\ No newline at end of file