Index: src/bp-xprofile/bp-xprofile-admin.php
--- src/bp-xprofile/bp-xprofile-admin.php
+++ src/bp-xprofile/bp-xprofile-admin.php
@@ -449,16 +449,15 @@
 	// Check the nonce
 	check_admin_referer( 'bp_reorder_fields', '_wpnonce_reorder_fields' );
 
-	if ( empty( $_POST['field_order'] ) ) {
+	// Bail if missing POST data
+	if ( empty( $_POST['field_order'] ) || empty( $_POST['field_group_id'] ) ) {
 		return false;
 	}
 
 	parse_str( $_POST['field_order'], $order );
 
-	$field_group_id = $_POST['field_group_id'];
-
 	foreach ( (array) $order['field'] as $position => $field_id ) {
-		xprofile_update_field_position( (int) $field_id, (int) $position, (int) $field_group_id );
+		xprofile_update_field_position( (int) $field_id, (int) $position, (int) $_POST['field_group_id'] );
 	}
 }
 add_action( 'wp_ajax_xprofile_reorder_fields', 'xprofile_ajax_reorder_fields' );
Index: src/bp-xprofile/bp-xprofile-functions.php
--- src/bp-xprofile/bp-xprofile-functions.php
+++ src/bp-xprofile/bp-xprofile-functions.php
@@ -968,6 +968,36 @@
 }
 
 /**
+ * Get array of field IDs to show on member registration page
+ *
+ * @since BuddyPress (2.3.0)
+ *
+ * @global $wpdb $wpdb
+ * @return array
+ */
+function bp_xprofile_get_signup_field_ids() {
+	global $wpdb;
+
+	$field_ids = array();
+	$fields    = wp_cache_get( 'signup_fields', 'bp_xprofile' );
+	if ( false === $fields ) {
+		$table_key  = 'xprofile_fieldmeta';
+		$table_name = $wpdb->{$table_key};
+		$sql        = $wpdb->prepare( "SELECT * FROM {$table_name} WHERE object_type = %s AND meta_key = %s", 'field', 'signup_position' );
+		$fields     = $wpdb->get_results( $sql );
+
+		wp_cache_set( 'signup_fields', $fields, 'bp_xprofile' );
+	}
+
+	// Bail if no signup fields
+	if ( ! empty( $fields ) ) {
+		$field_ids = wp_list_pluck( $fields, 'object_id' );
+	}
+
+	return apply_filters( 'bp_xprofile_get_signup_field_ids', $field_ids, $fields );
+}
+
+/**
  * Return the field ID for the Full Name xprofile field.
  *
  * @since BuddyPress (2.0.0)
Index: src/bp-xprofile/bp-xprofile-loader.php
--- src/bp-xprofile/bp-xprofile-loader.php
+++ src/bp-xprofile/bp-xprofile-loader.php
@@ -367,6 +367,7 @@
 		wp_cache_add_global_groups( array(
 			'bp_xprofile',
 			'bp_xprofile_data',
+			'bp_xprofile_fields',
 			'bp_xprofile_groups',
 			'xprofile_meta'
 		) );
Index: src/bp-xprofile/classes/class-bp-xprofile-field.php
--- src/bp-xprofile/classes/class-bp-xprofile-field.php
+++ src/bp-xprofile/classes/class-bp-xprofile-field.php
@@ -110,6 +110,13 @@
 	public $allow_custom_visibility = 'allowed';
 
 	/**
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @var int Position of field on user registration page
+	 */
+	public $signup_position = null;
+
+	/**
 	 * @since BuddyPress (2.0.0)
 	 *
 	 * @var BP_XProfile_Field_Type Field type object used for validation
@@ -157,85 +164,79 @@
 	 * @param  bool   $get_data
 	 */
 	public function populate( $id, $user_id = null, $get_data = true ) {
-		global $wpdb, $userdata;
 
-		if ( empty( $user_id ) ) {
-			$user_id = isset( $userdata->ID ) ? $userdata->ID : 0;
-		}
+		// Check for cached field
+		$field = wp_cache_get( $id, 'bp_xprofile_fields' );
 
-		$bp    = buddypress();
-		$field = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE id = %d", $id ) );
+		// Field not cached, so query (and subsequently cache)
+		if ( false === $field ) {
 
-		if ( ! empty( $field ) ) {
-			$this->id                = $field->id;
-			$this->group_id          = $field->group_id;
-			$this->parent_id         = $field->parent_id;
-			$this->type              = $field->type;
-			$this->name              = stripslashes( $field->name );
-			$this->description       = stripslashes( $field->description );
-			$this->is_required       = $field->is_required;
-			$this->can_delete        = $field->can_delete;
-			$this->field_order       = $field->field_order;
-			$this->option_order      = $field->option_order;
-			$this->order_by          = $field->order_by;
-			$this->is_default_option = $field->is_default_option;
+			// Setup args
+			$args = array(
+				'include' => $id
+			);
 
-			// Create the field type and store a reference back to this object.
-			$this->type_obj            = bp_xprofile_create_field_type( $field->type );
-			$this->type_obj->field_obj = $this;
-
-			if ( ! empty( $get_data ) && ! empty( $user_id ) ) {
-				$this->data = $this->get_field_data( $user_id );
+			// Get data for the user ID
+			if ( ! empty( $user_id ) && ! empty( $get_data ) ) {
+				$args['fetch_data'] = $user_id;
 			}
 
-			// Get metadata for field
-			$default_visibility       = bp_xprofile_get_meta( $id, 'field', 'default_visibility'      );
-			$allow_custom_visibility  = bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' );
+			// Get the field and its data
+			$field = self::get( $args );
 
-			// Setup default visibility
-			$this->default_visibility = ! empty( $default_visibility )
-				? $default_visibility
-				: 'public';
+			// Use the first item if an array
+			if ( array( $field ) ) {
+				$field = reset( $field );
+			}
+		}
 
-			// Allow members to customize visibilty
-			$this->allow_custom_visibility = ( 'disabled' === $allow_custom_visibility )
-				? 'disabled'
-				: 'allowed';
+		// Bail if field could not be found
+		if ( empty( $field ) ) {
+			return;
 		}
-	}
 
-	/**
-	 * Delete a profile field
-	 *
-	 * @since BuddyPress (1.1.0)
-	 *
-	 * @global object  $wpdb
-	 * @param  boolean $delete_data
-	 * @return boolean
-	 */
-	public function delete( $delete_data = false ) {
-		global $wpdb;
+		// Setup this field
+		$this->id                = $field->id;
+		$this->group_id          = $field->group_id;
+		$this->parent_id         = $field->parent_id;
+		$this->type              = $field->type;
+		$this->name              = stripslashes( $field->name );
+		$this->description       = stripslashes( $field->description );
+		$this->is_required       = $field->is_required;
+		$this->is_default_option = $field->is_default_option;
+		$this->field_order       = $field->field_order;
+		$this->option_order      = $field->option_order;
+		$this->order_by          = $field->order_by;
+		$this->can_delete        = $field->can_delete;
 
-		// Prevent deletion if no ID is present
-		// Prevent deletion by url when can_delete is false.
-		// Prevent deletion of option 1 since this invalidates fields with options.
-		if ( empty( $this->id ) || empty( $this->can_delete ) || ( $this->parent_id && $this->option_order == 1 ) ) {
-			return false;
+		// Set field data if it was requested
+		if ( ! empty( $field->data ) ) {
+			$this->data = $field->data;
 		}
 
-		$bp  = buddypress();
-		$sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d OR parent_id = %d", $this->id, $this->id );
+		// Create the field type and store a reference back to this object.
+		$this->type_obj            = bp_xprofile_create_field_type( $field->type );
+		$this->type_obj->field_obj = $this;
 
-		if ( ! $wpdb->query( $sql ) ) {
-			return false;
-		}
+		// Get core metadata for field
+		$default_visibility      = bp_xprofile_get_meta( $id, 'field', 'default_visibility'      );
+		$allow_custom_visibility = bp_xprofile_get_meta( $id, 'field', 'allow_custom_visibility' );
+		$signup_position         = bp_xprofile_get_meta( $id, 'field', 'signup_position'         );
 
-		// delete the data in the DB for this field
-		if ( true === $delete_data ) {
-			BP_XProfile_ProfileData::delete_for_field( $this->id );
-		}
+		// Setup default visibility
+		$this->default_visibility = ! empty( $default_visibility )
+			? $default_visibility
+			: 'public';
 
-		return true;
+		// Allow members to customize visibilty
+		$this->allow_custom_visibility = ( 'disabled' === $allow_custom_visibility )
+			? 'disabled'
+			: 'allowed';
+
+		// Is this field used on the registration page
+		$this->signup_position = ( false !== $signup_position )
+			? $signup_position
+			: null;
 	}
 
 	/**
@@ -245,24 +246,28 @@
 	 *
 	 * @global object $wpdb
 	 *
+	 * @param  boolean $update_children Should child fields be updated?
+	 *
 	 * @return boolean
 	 */
-	public function save() {
+	public function save( $updating_position = false ) {
 		global $wpdb;
 
 		$bp = buddypress();
 
-		$this->group_id     = apply_filters( 'xprofile_field_group_id_before_save',     $this->group_id,     $this->id );
-		$this->parent_id    = apply_filters( 'xprofile_field_parent_id_before_save',    $this->parent_id,    $this->id );
-		$this->type         = apply_filters( 'xprofile_field_type_before_save',         $this->type,         $this->id );
-		$this->name         = apply_filters( 'xprofile_field_name_before_save',         $this->name,         $this->id );
-		$this->description  = apply_filters( 'xprofile_field_description_before_save',  $this->description,  $this->id );
-		$this->is_required  = apply_filters( 'xprofile_field_is_required_before_save',  $this->is_required,  $this->id );
-		$this->order_by	    = apply_filters( 'xprofile_field_order_by_before_save',     $this->order_by,     $this->id );
-		$this->field_order  = apply_filters( 'xprofile_field_field_order_before_save',  $this->field_order,  $this->id );
-		$this->option_order = apply_filters( 'xprofile_field_option_order_before_save', $this->option_order, $this->id );
-		$this->can_delete   = apply_filters( 'xprofile_field_can_delete_before_save',   $this->can_delete,   $this->id );
-		$this->type_obj     = bp_xprofile_create_field_type( $this->type );
+		$this->group_id            = apply_filters( 'xprofile_field_group_id_before_save',          $this->group_id,          $this->id );
+		$this->parent_id           = apply_filters( 'xprofile_field_parent_id_before_save',         $this->parent_id,         $this->id );
+		$this->type                = apply_filters( 'xprofile_field_type_before_save',              $this->type,              $this->id );
+		$this->name                = apply_filters( 'xprofile_field_name_before_save',              $this->name,              $this->id );
+		$this->description         = apply_filters( 'xprofile_field_description_before_save',       $this->description,       $this->id );
+		$this->is_required         = apply_filters( 'xprofile_field_is_required_before_save',       $this->is_required,       $this->id );
+		$this->is_default_option   = apply_filters( 'xprofile_field_is_default_option_before_save', $this->is_default_option, $this->id );
+		$this->field_order         = apply_filters( 'xprofile_field_field_order_before_save',       $this->field_order,       $this->id );
+		$this->option_order        = apply_filters( 'xprofile_field_option_order_before_save',      $this->option_order,      $this->id );
+		$this->order_by	           = apply_filters( 'xprofile_field_order_by_before_save',          $this->order_by,          $this->id );
+		$this->can_delete          = apply_filters( 'xprofile_field_can_delete_before_save',        $this->can_delete,        $this->id );
+		$this->type_obj            = bp_xprofile_create_field_type( $this->type );
+		$this->type_obj->field_obj = $this;
 
 		/**
 		 * Fires before the current field instance gets saved.
@@ -275,116 +280,122 @@
 		 */
 		do_action_ref_array( 'xprofile_field_before_save', array( $this ) );
 
-		if ( $this->id != null ) {
-			$sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET group_id = %d, parent_id = 0, type = %s, name = %s, description = %s, is_required = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d WHERE id = %d", $this->group_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->id );
+		// Existing field
+		if ( ! empty( $this->id ) ) {
+			$sql = $wpdb->prepare( "UPDATE {$bp->profile->table_name_fields} SET group_id = %d, parent_id = %d, type = %s, name = %s, description = %s, is_required = %d, is_default_option = %d, order_by = %s, field_order = %d, option_order = %d, can_delete = %d WHERE id = %d", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->is_default_option, $this->order_by, $this->field_order, $this->option_order, $this->can_delete, $this->id );
+
+		// New field
 		} else {
-			$sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, order_by, field_order, option_order, can_delete ) VALUES (%d, %d, %s, %s, %s, %d, %s, %d, %d, %d )", $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->order_by, $this->field_order, $this->option_order, $this->can_delete );
+			$sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, is_default_option, order_by, field_order, option_order, can_delete ) VALUES (%d, %d, %s, %s, %s, %d, %d, %s, %d, %d, %d )",             $this->group_id, $this->parent_id, $this->type, $this->name, $this->description, $this->is_required, $this->is_default_option, $this->order_by, $this->field_order, $this->option_order, $this->can_delete );
 		}
 
+		// Attempt to update or insert
+		$query = $wpdb->query( $sql );
+
 		/**
-		 * Check for null so field options can be changed without changing any
-		 * other part of the field. The described situation will return 0 here.
+		 * Check for `null` instead of `false` or `empty()` to allow child
+		 * field-options to be modified even if no other field properties changed.
 		 */
-		if ( $wpdb->query( $sql ) !== null ) {
+		if ( ( null === $query ) || is_wp_error( $query ) ) {
+			return false;
+		}
 
-			if ( !empty( $this->id ) ) {
-				$field_id = $this->id;
-			} else {
-				$field_id = $wpdb->insert_id;
-			}
+		// Update existing field
+		if ( empty( $this->id ) ) {
+			$this->id = $wpdb->insert_id;
+		}
 
-			// Only do this if we are editing an existing field
-			if ( $this->id != null ) {
+		// Delete the relevant caches
+		$this->purge_caches();
 
-				/**
-				 * Remove any radio or dropdown options for this
-				 * field. They will be re-added if needed.
-				 * This stops orphan options if the user changes a
-				 * field from a radio button field to a text box.
-				 */
-				$this->delete_children();
-			}
+		/**
+		 * Check to see if this is a field with child options.
+		 * We need to add the options to the db, if it is.
+		 */
+		if ( ( false === $updating_position ) && ! empty( $this->type_obj->supports_options ) ) {
 
 			/**
-			 * Check to see if this is a field with child options.
-			 * We need to add the options to the db, if it is.
+			 * Remove any field-options for this field. They will be re-added if
+			 * needed. This prenets orphaned options if the user changes a field
+			 * from a radio-button with options to a textbox without.
+			 *
+			 * It's maybe a bit of a brute-force approach, but seems easier than
+			 * querying for items & updating/deleting/creating each time;
+			 * and 60% of the time, it works everytime, so that's nice.
 			 */
-			if ( $this->type_obj->supports_options ) {
+			$this->delete_children();
 
-				if ( !empty( $this->id ) ) {
-					$parent_id = $this->id;
-				} else {
-					$parent_id = $wpdb->insert_id;
-				}
+			// Allow plugins to filter the field's child options (i.e. the items in a selectbox).
+			$post_options  = ! empty( $_POST["{$this->type}_option"]           ) ? $_POST["{$this->type}_option"]           : '';
+			$post_defaults = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : '';
 
-				// Allow plugins to filter the field's child options (i.e. the items in a selectbox).
-				$post_option  = ! empty( $_POST["{$this->type}_option"]           ) ? $_POST["{$this->type}_option"]           : '';
-				$post_default = ! empty( $_POST["isDefault_{$this->type}_option"] ) ? $_POST["isDefault_{$this->type}_option"] : '';
+			/**
+			 * Filters the submitted field option value before saved.
+			 *
+			 * @since BuddyPress (1.5.0)
+			 *
+			 * @param string            $post_options Submitted option value.
+			 * @param BP_XProfile_Field $type         Current field type being saved for.
+			 */
+			$options = apply_filters( 'xprofile_field_options_before_save', $post_options,  $this->type );
 
-				/**
-				 * Filters the submitted field option value before saved.
-				 *
-				 * @since BuddyPress (1.5.0)
-				 *
-				 * @param string            $post_option Submitted option value.
-				 * @param BP_XProfile_Field $type        Current field type being saved for.
-				 */
-				$options      = apply_filters( 'xprofile_field_options_before_save', $post_option,  $this->type );
+			/**
+			 * Filters the default field option value before saved.
+			 *
+			 * @since BuddyPress (1.5.0)
+			 *
+			 * @param string            $post_defaults Default option value.
+			 * @param BP_XProfile_Field $type          Current field type being saved for.
+			 */
+			$defaults = apply_filters( 'xprofile_field_default_before_save', $post_defaults, $this->type );
 
-				/**
-				 * Filters the default field option value before saved.
-				 *
-				 * @since BuddyPress (1.5.0)
-				 *
-				 * @param string            $post_default Default option value.
-				 * @param BP_XProfile_Field $type         Current field type being saved for.
-				 */
-				$defaults     = apply_filters( 'xprofile_field_default_before_save', $post_default, $this->type );
+			// There are options that need recreating
+			if ( ! empty( $options ) ) {
 
+				// Start counter at 1 to avoid 0 values
 				$counter = 1;
-				if ( !empty( $options ) ) {
-					foreach ( (array) $options as $option_key => $option_value ) {
-						$is_default = 0;
 
-						if ( is_array( $defaults ) ) {
-							if ( isset( $defaults[ $option_key ] ) ) {
-								$is_default = 1;
-							}
-						} else {
-							if ( (int) $defaults == $option_key ) {
-								$is_default = 1;
-							}
-						}
+				// Loop through options and re-create them
+				foreach ( (array) $options as $option_key => $option_name ) {
 
-						if ( '' != $option_value ) {
-							$sql = $wpdb->prepare( "INSERT INTO {$bp->profile->table_name_fields} (group_id, parent_id, type, name, description, is_required, option_order, is_default_option) VALUES (%d, %d, 'option', %s, '', 0, %d, %d)", $this->group_id, $parent_id, $option_value, $counter, $is_default );
-							if ( ! $wpdb->query( $sql ) ) {
-								return false;
-							}
-						}
+					// Determine if option is the default
+					if ( is_array( $defaults ) && isset( $defaults[ $option_key ] ) ) {
+						$is_default = 1;
+					} elseif ( (int) $defaults == $option_key ) {
+						$is_default = 1;
+					} else {
+						$is_default = 0;
+					}
 
-						$counter++;
+					// Recreate the option based on value and other criteria
+					if ( ! empty( $option_name ) ) {
+						$option                    = new BP_XProfile_Field();
+						$option->group_id          = $this->group_id;
+						$option->parent_id         = $this->id;
+						$option->type              = 'option';
+						$option->name              = $option_name;
+						$option->description       = '';
+						$option->is_required       = 0;
+						$option->is_default_option = $is_default;
+						$option->option_order      = $counter;
+						$option->save();
 					}
+
+					$counter++;
 				}
 			}
+		}
 
-			/**
-			 * Fires after the current field instance gets saved.
-			 *
-			 * @since BuddyPress (1.0.0)
-			 *
-			 * @param BP_XProfile_Field Current instance of the field being saved.
-			 */
-			do_action_ref_array( 'xprofile_field_after_save', array( $this ) );
+		/**
+		 * Fires after the current field instance gets saved.
+		 *
+		 * @since BuddyPress (1.0.0)
+		 *
+		 * @param BP_XProfile_Field Current instance of the field being saved.
+		 */
+		do_action_ref_array( 'xprofile_field_after_save', array( $this ) );
 
-			// Recreate type_obj in case someone changed $this->type via a filter
-	 		$this->type_obj            = bp_xprofile_create_field_type( $this->type );
-	 		$this->type_obj->field_obj = $this;
-
-			return $field_id;
-		} else {
-			return false;
-		}
+		return (int) $this->id;
 	}
 
 	/**
@@ -404,36 +415,33 @@
 	 *
 	 * @since BuddyPress (1.2.0)
 	 *
-	 * @global object $wpdb
-	 *
 	 * @param  bool  $for_editing
 	 * @return array
 	 */
 	public function get_children( $for_editing = false ) {
-		global $wpdb;
 
 		// This is done here so we don't have problems with sql injection
-		if ( empty( $for_editing ) && ( 'asc' === $this->order_by ) ) {
-			$sort_sql = 'ORDER BY name ASC';
-		} elseif ( empty( $for_editing ) && ( 'desc' === $this->order_by ) ) {
-			$sort_sql = 'ORDER BY name DESC';
+		if ( ( false === $for_editing ) && in_array( strtoupper( $this->order_by ), array( 'ASC', 'DESC' ) ) ) {
+			$order_by = 'name';
+			$sort     = strtoupper( $this->order_by );
 		} else {
-			$sort_sql = 'ORDER BY option_order ASC';
+			$order_by = 'option_order';
+			$sort     = 'ASC';
 		}
 
-		// This eliminates a problem with getting all fields when there is no
-		// id for the object
-		if ( empty( $this->id ) ) {
-			$parent_id = -1;
-		} else {
-			$parent_id = $this->id;
+		// Get children
+		$children = self::get( array(
+			'parent_id'         => $this->id,
+			'order_by'          => $order_by,
+			'sort'              => $sort,
+			'update_meta_cache' => false
+		) );
+
+		// Set children to false if empty or invalid
+		if ( empty( $children ) || ! is_array( $children ) ) {
+			$children = false;
 		}
 
-		$bp  = buddypress();
-		$sql = $wpdb->prepare( "SELECT * FROM {$bp->profile->table_name_fields} WHERE parent_id = %d AND group_id = %d {$sort_sql}", $parent_id, $this->group_id );
-
-		$children = $wpdb->get_results( $sql );
-
 		/**
 		 * Filters the found children for a field.
 		 *
@@ -446,6 +454,62 @@
 	}
 
 	/**
+	 * Delete a profile field
+	 *
+	 * @since BuddyPress (1.1.0)
+	 *
+	 * @global object  $wpdb
+	 * @param  boolean $delete_data
+	 * @param  boolean $delete_children
+	 * @return boolean
+	 */
+	public function delete( $delete_data = false, $delete_children = true ) {
+		global $wpdb;
+
+		// Prevent deletion if no ID is present
+		// Prevent deletion by url when can_delete is false.
+		if ( empty( $this->id ) || empty( $this->can_delete ) ) {
+			return false;
+		}
+
+		$bp = buddypress();
+
+		// Attempt to get fields to delete
+		$sql      = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE id = %d LIMIT 1", $this->id );
+		$field_id = $wpdb->get_var( $sql );
+
+		// Bail if no children exist
+		if ( empty( $field_id ) || is_wp_error( $field_id ) ) {
+			return false;
+		}
+
+		// Attempt to delete
+		$sql     = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE id = %d LIMIT 1", $this->id );
+		$deleted = $wpdb->query( $sql );
+
+		// Bail if no children exist
+		if ( empty( $deleted ) || is_wp_error( $deleted ) ) {
+			return false;
+		}
+
+		// Delete cache
+		// Delete the relevant caches
+		$this->purge_caches();
+
+		// Maybe delete children
+		if ( true === $delete_children ) {
+			$this->delete_children();
+		}
+
+		// delete the data in the DB for this field
+		if ( true === $delete_data ) {
+			BP_XProfile_ProfileData::delete_for_field( $this->id );
+		}
+
+		return true;
+	}
+
+	/**
 	 * Delete all field children for this field
 	 *
 	 * @since BuddyPress (1.2.0)
@@ -455,32 +519,334 @@
 	public function delete_children() {
 		global $wpdb;
 
-		$bp  = buddypress();
-		$sql = $wpdb->prepare( "DELETE FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id );
+		$bp = buddypress();
 
-		$wpdb->query( $sql );
+		// Attempt to get fields to delete
+		$sql       = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE parent_id = %d", $this->id );
+		$field_ids = $wpdb->get_col( $sql );
+
+		// Bail if no children exist
+		if ( empty( $field_ids ) || is_wp_error( $field_ids ) ) {
+			return false;
+		}
+
+		// Delete child fields
+		foreach ( $field_ids as $field_id ) {
+			$field = new BP_XProfile_Field( $field_id );
+			$field->delete( false, false );
+		}
+
+		// We care less about actual option deletion at this point
+		return true;
 	}
 
 	/** Static Methods ********************************************************/
 
-	public static function get_type( $field_id = 0 ) {
+	/**
+	 * Get profile fields, as specified by parameters
+	 *
+	 * @see BP_Activity_Activity::get_filter_sql() for a description of the
+	 *      'filter' parameter.
+	 * @see WP_Meta_Query::queries for a description of the 'meta_query'
+	 *      parameter format.
+	 *
+	 * @param array $args {
+	 *     An array of arguments. All items are optional.
+	 *
+	 *     @type int          $page              Which page of results to fetch. Using page=1 without per_page will result
+	 *                                           in no pagination. Default: 1.
+	 *     @type int|bool     $per_page          Number of results per page. Default: 25.
+	 *     @type int|bool     $max               Maximum number of results to return. Default: false (unlimited).
+	 *     @type string       $sort              ASC or DESC. Default: 'DESC'.
+	 *     @type array        $exclude           Array of activity IDs to exclude. Default: false.
+	 *     @type array        $in                Array of ids to limit query by (IN). Default: false.
+	 *     @type array        $meta_query        Array of meta_query conditions. See WP_Meta_Query::queries.
+	 *     @type array        $date_query        Array of date_query conditions. See first parameter of
+	 *                                           WP_Date_Query::__construct().
+	 *     @type array        $filter_query      Array of advanced query conditions. See BP_Activity_Query::__construct().
+	 *     @type string|array $scope             Pre-determined set of activity arguments.
+	 *     @type array        $filter            See BP_Activity_Activity::get_filter_sql().
+	 *     @type string       $search_terms      Limit results by a search term. Default: false.
+	 *     @type bool         $display_comments  Whether to include activity comments. Default: false.
+	 *     @type bool         $show_hidden       Whether to show items marked hide_sitewide. Default: false.
+	 *     @type string       $spam              Spam status. Default: 'ham_only'.
+	 *     @type bool         $update_meta_cache Whether to pre-fetch metadata for queried activity items. Default: true.
+	 *     @type string|bool  $count_total       If true, an additional DB query is run to count the total activity items
+	 *                                           for the query. Default: false.
+	 * }
+	 * @return array The array returned has two keys:
+	 *     - 'total' is the count of located activities
+	 *     - 'activities' is an array of the located activities
+	 */
+	public static function get( $args = '' ) {
 		global $wpdb;
 
+		$r  = wp_parse_args( $args, array(
+			'include'           => false,
+			'exclude'           => false,
+			'group_id'          => false,
+			'group_not_in'      => false,
+			'parent_id'         => false,
+			'parent_not_in'     => false,
+			'type_in'           => false,
+			'type_not_in'       => false,
+			'name'              => '',
+			'is_required'       => false,
+			'is_default_option' => false,
+			'can_delete'        => 1,
+			'order_by'          => false,
+			'sort'              => 'ASC',
+			'fetch_data'        => false,
+			'meta_query'        => false, // @todo implement
+			'update_meta_cache' => true
+		) );
+
+		// Get the profile fields table name
+		$table_name = buddypress()->profile->table_name_fields;
+
+		// Select conditions
+		$select_sql = "SELECT *";
+
+		$from_sql   = " FROM {$table_name} f";
+
+		$join_sql   = '';
+
+		// Where conditions
+		$where_conditions = array();
+
+		/** Field ID **********************************************************/
+
+		// Setup IN query for field IDs
+		if ( ! empty( $r['include'] ) ) {
+			$include_in                  = implode( ',', wp_parse_id_list( $r['include'] ) );
+			$where_conditions['include'] = "id IN ({$include_in})";
+		}
+
+		// Setup NOT IN query for field IDs
+		if ( ! empty( $r['exclude'] ) ) {
+			$exclude_not_in              = implode( ',', wp_parse_id_list( $r['exclude'] ) );
+			$where_conditions['exclude'] = "id NOT IN ({$exclude_not_in})";
+		}
+
+		/** Group ID **********************************************************/
+
+		// Setup IN query for field-group IDs
+		if ( ! empty( $r['group_id'] ) ) {
+			$group_in                     = implode( ',', wp_parse_id_list( $r['group_id'] ) );
+			$where_conditions['group_in'] = "group_id IN ({$group_in})";
+		}
+
+		// Setup NOT IN query for field-group IDs
+		if ( ! empty( $r['group_not_in'] ) ) {
+			$group_not_in                     = implode( ',', wp_parse_id_list( $r['group_not_in'] ) );
+			$where_conditions['group_not_in'] = "group_id NOT IN ({$group_not_in})";
+		}
+
+		/** Parent ID *********************************************************/
+
+		// Setup IN query for parent-field IDs
+		if ( ! empty( $r['parent_id'] ) || ( '0' === $r['parent_id'] ) ) {
+			$parent_in                     = implode( ',', wp_parse_id_list( $r['parent_id'] ) );
+			$where_conditions['parent_in'] = "parent_id IN ({$parent_in})";
+		}
+
+		// Setup NOT IN query for parent-field IDs
+		if ( ! empty( $r['parent_not_in'] ) ) {
+			$parent_not_in                     = implode( ',', wp_parse_id_list( $r['parent_not_in'] ) );
+			$where_conditions['parent_not_in'] = "parent_id NOT IN ({$parent_not_in})";
+		}
+
+		/** Types *************************************************************/
+
+		// Setup IN query for type-field IDs
+		if ( ! empty( $r['type_in'] ) ) {
+			$type_in                     = implode( ',', wp_parse_id_list( $r['type_in'] ) );
+			$where_conditions['type_in'] = "type IN ({$type_in})";
+		}
+
+		// Setup NOT IN query for type-field IDs
+		if ( ! empty( $r['type_not_in'] ) ) {
+			$type_not_in                     = implode( ',', wp_parse_id_list( $r['type_not_in'] ) );
+			$where_conditions['type_not_in'] = "type NOT IN ({$type_not_in})";
+		}
+
+		/** Name **************************************************************/
+
+		// Setup IN query for type-field IDs
+		if ( ! empty( $r['name'] ) ) {
+			$where_conditions['name'] = $wpdb->prepare( "name = %s", $r['name'] );
+		}
+
+		/** Other *************************************************************/
+
+		// Sorting
+		$sort = $r['sort'];
+		if ( ! in_array( $sort, array( 'ASC', 'DESC' ) ) ) {
+			$sort = 'ASC';
+		}
+
+		// Ordering
+		$order_by = 'field_order';
+		if ( ! empty( $r['order_by'] ) ) {
+			$order_by = $r['order_by'];
+		}
+
+		// Process meta_query into SQL
+//		$meta_query_sql = self::get_meta_query_sql( $r['meta_query'] );
+//
+//		if ( ! empty( $meta_query_sql['join'] ) ) {
+//			$join_sql .= $meta_query_sql['join'];
+//		}
+//
+//		if ( ! empty( $meta_query_sql['where'] ) ) {
+//			$where_conditions[] = $meta_query_sql['where'];
+//		}
+
+		/**
+		 * Filters the MySQL WHERE conditions for the XProfile field get method.
+		 *
+		 * @since BuddyPress (2.3.0)
+		 *
+		 * @param array  $where_conditions Current conditions for MySQL WHERE statement.
+		 * @param array  $r                Parsed arguments passed into method.
+		 * @param string $select_sql       Current SELECT MySQL statement at point of execution.
+		 * @param string $from_sql         Current FROM MySQL statement at point of execution.
+		 * @param string $join_sql         Current INNER JOIN MySQL statement at point of execution.
+		 */
+		$where_conditions = apply_filters( 'bp_xprofile_get_where_conditions', $where_conditions, $r, $select_sql, $from_sql, $join_sql );
+
+		// Bail if no WHERE conditions
+		if ( empty( $where_conditions ) ) {
+			return array();
+		}
+
+		// Join the where conditions together
+		$where_sql = 'WHERE ' . join( ' AND ', $where_conditions );
+
+		/**
+		 * Filters the preferred order of indexes for xprofile fields.
+		 *
+		 * @since BuddyPress (2.3.0)
+		 *
+		 * @param array Array of indexes in preferred order.
+		 */
+		$indexes = apply_filters( 'bp_xprofile_preferred_index_order', array( 'group_id', 'parent_id', 'field_order', 'can_delete', 'is_required' ) );
+
+		foreach ( $indexes as $key => $index ) {
+			if ( false !== strpos( $where_sql, $index ) ) {
+				$the_index = $index;
+				break; // Take the first one we find
+			}
+		}
+
+		if ( ! empty( $the_index ) ) {
+			$index_hint_sql = "USE INDEX ({$the_index})";
+		} else {
+			$index_hint_sql = '';
+		}
+
+		// Query first for activity IDs
+		$field_ids_sql = "{$select_sql} {$from_sql} {$index_hint_sql} {$join_sql} {$where_sql} ORDER BY f.{$order_by} {$sort}";
+		$fields        = $wpdb->get_results( $field_ids_sql );
+		$field_ids     = wp_list_pluck( $fields, 'id' );
+
+		// Bail if no field IDs
+		if ( empty( $field_ids ) ) {
+			return array();
+		}
+
+		// Get fields from queried IDs
+		self::update_field_caches( $field_ids );
+
+		// Maybe update meta cache
+		if ( ! empty( $field_ids ) ) {
+
+			// Maybe fetch data for all fields
+			if ( ! empty( $r['fetch_data'] ) ) {
+
+				// Wipe out the fields array
+				$fields = array();
+
+				// Loop through fields and get data for them
+				foreach ( $field_ids as $field_id ) {
+					$field       = new BP_XProfile_Field( $field_id );
+					$field->data = $field->get_field_data( $r['fetch_data'] );
+					$fields[]    = $field;
+				}
+			}
+
+			// Update caches
+			if ( ! empty( $r['update_meta_cache'] ) ) {
+				bp_xprofile_update_meta_cache( array( 'field' => $field_ids ) );
+			}
+		}
+
+		return $fields;
+	}
+
+	/**
+	 * Convert field IDs to field objects, as expected in template loop.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @param array $field_ids Array of field IDs.
+	 * @return array
+	 */
+	protected static function update_field_caches( $field_ids = array() ) {
+		global $wpdb;
+
+		// Bail if no field ID's passed
+		if ( empty( $field_ids ) ) {
+			return array();
+		}
+
+		// Get BuddyPress
+		$uncached_ids = bp_get_non_cached_ids( $field_ids, 'bp_xprofile_fields' );
+
+		// Prime caches as necessary
+		if ( ! empty( $uncached_ids ) ) {
+
+			// Format the field ID's for use in the query below
+			$uncached_ids_sql = implode( ',', wp_parse_id_list( $uncached_ids ) );
+
+			// Fetch data from field table, preserving order
+			$table_name    = buddypress()->profile->table_name_fields;
+			$sql           = "SELECT * FROM {$table_name} WHERE id IN ({$uncached_ids_sql})";
+			$queried_fdata = $wpdb->get_results( $sql );
+
+			// Reset the field IDs array
+			$field_ids = array();
+
+			// Put that data into the placeholders created earlier,
+			// and add it to the cache
+			if ( ! empty( $queried_fdata ) ) {
+				foreach ( $queried_fdata as $fdata ) {
+					$field_ids[] = $fdata->id;
+					wp_cache_set( $fdata->id, $fdata, 'bp_xprofile_fields' );
+				}
+			}
+		}
+
+		// Return newly cached field IDs
+		return $field_ids;
+	}
+
+	public static function get_type( $field_id = 0 ) {
+
 		// Bail if no field ID
 		if ( empty( $field_id ) ) {
 			return false;
 		}
 
-		$bp   = buddypress();
-		$sql  = $wpdb->prepare( "SELECT type FROM {$bp->profile->table_name_fields} WHERE id = %d", $field_id );
-		$type = $wpdb->get_var( $sql );
+		// Attempt to get the field
+		$field = new BP_XProfile_Field( $field_id );
 
-		// Return field type
-		if ( ! empty( $type ) ) {
-			return $type;
+		// Bail if no field found
+		if ( empty( $field->type ) ) {
+			return false;
 		}
 
-		return false;
+		return $field->type;
 	}
 
 	/**
@@ -519,23 +885,33 @@
 	 *
 	 * @since BuddyPress (1.5.0)
 	 *
-	 * @global object $wpdb
-	 * @param  string $field_name
+	 * @param string $field_name
 	 *
 	 * @return boolean
 	 */
 	public static function get_id_from_name( $field_name = '' ) {
-		global $wpdb;
 
-		$bp = buddypress();
+		// Bail if no field ID
+		if ( empty( $field_name ) ) {
+			return false;
+		}
 
-		if ( empty( $bp->profile->table_name_fields ) || empty( $field_name ) ) {
+		// Attempt to get the field
+		$fields = self::get( array(
+			'name'      => $field_name,
+			'parent_id' => '0'
+		) );
+
+		// Return field if found
+		if ( empty( $fields ) ) {
 			return false;
 		}
 
-		$sql = $wpdb->prepare( "SELECT id FROM {$bp->profile->table_name_fields} WHERE name = %s AND parent_id = 0", $field_name );
+		// Take the first field
+		$field = reset( $fields );
 
-		return $wpdb->get_var( $sql );
+		// Return false if not found
+		return $field->id;
 	}
 
 	/**
@@ -552,7 +928,6 @@
 	 * @return boolean
 	 */
 	public static function update_position( $field_id, $position = null, $field_group_id = null ) {
-		global $wpdb;
 
 		// Bail if invalid position or field group
 		if ( ! is_numeric( $position ) || ! is_numeric( $field_group_id ) ) {
@@ -560,21 +935,31 @@
 		}
 
 		// Get table name and field parent
-		$table_name = buddypress()->profile->table_name_fields;
-		$sql        = $wpdb->prepare( "UPDATE {$table_name} SET field_order = %d, group_id = %d WHERE id = %d", $position, $field_group_id, $field_id );
-		$parent     = $wpdb->query( $sql );
+		$field              = new BP_XProfile_Field( $field_id );
+		$field->field_order = $position;
+		$field->group_id    = $field_group_id;
 
-		// Update $field_id with new $position and $field_group_id
-		if ( ! empty( $parent ) && ! is_wp_error( $parent ) ) {
+		// Bail if field did not save - pass `true` to prevent option deletion
+		if ( ! $field->save( true ) ) {
+			return false;
+		}
 
-			// Update any children of this $field_id
-			$sql = $wpdb->prepare( "UPDATE {$table_name} SET group_id = %d WHERE parent_id = %d", $field_group_id, $field_id );
-			$wpdb->query( $sql );
+		// Regather the field and its children
+		$field    = new BP_XProfile_Field( $field_id );
+		$children = $field->get_children();
 
-			return $parent;
+		// If child fields exist, update them
+		if ( ! empty( $children ) ) {
+
+			// Loop through children and update field group ID
+			foreach ( $children as $option_field ) {
+				$option           = new BP_XProfile_Field( $option_field->id );
+				$option->group_id = $field->group_id;
+				$option->save( true );
+			}
 		}
 
-		return false;
+		return true;
 	}
 
 	/**
@@ -635,7 +1020,7 @@
 				$this->description = $_POST['description'];
 				$this->is_required = $_POST['required'];
 				$this->type        = $_POST['fieldtype'];
-				$this->order_by    = $_POST["sort_order_{$this->type}"];
+				$this->order_by    = ! empty( $_POST["sort_order_{$this->type}"] ) ? $_POST["sort_order_{$this->type}"] : '';
 				$this->field_order = $_POST['field_order'];
 			}
 		} else {
@@ -680,9 +1065,12 @@
 							// Output the required metabox
 							$this->required_metabox();
 
-							// Output the field visibility metaboxes
+							// Output the field visibility metabox
 							$this->visibility_metabox();
 
+							// Output the signup metabox
+							$this->signup_metabox();
+
 							/**
 							 * Fires after XProfile Field sidebar metabox.
 							 *
@@ -902,6 +1290,29 @@
 	}
 
 	/**
+	 * Output the metabox for enabling this field to appear on user registration
+	 *
+	 * @since BuddyPress (2.3.0)
+	 */
+	private function signup_metabox() {
+	?>
+
+		<div class="postbox">
+			<h3><label for="signup-field"><?php _e( 'Sign Ups', 'buddypress' ); ?></label></h3>
+			<div class="inside">
+				<ul>
+					<li>
+						<input type="checkbox" id="signup-position" name="signup-position" value="1" <?php checked( $this->signup_position, ! null ); ?> />
+						<label for="signup-position"><?php esc_html_e( 'Display on Registration', 'buddypress' ); ?></label>
+					</li>
+				</ul>
+			</div>
+		</div>
+
+	<?php
+	}
+
+	/**
 	 * Output the metabox for setting what type of field this is
 	 *
 	 * @since BuddyPress (2.3.0)
@@ -975,4 +1386,27 @@
 		// Compare & return
 		return (bool) ( 1 === (int) $field_id );
 	}
+
+	/**
+	 * Purge caches relevant to field updates
+	 *
+	 * @since BuddyPress (2.3.0)
+	 */
+	private function purge_caches() {
+
+		// Delete the cached value for this field
+		wp_cache_delete( $this->id, 'bp_xprofile_fields' );
+
+		// Bust cache of parent field
+		if ( ! empty( $this->parent_id ) ) {
+			wp_cache_delete( $this->parent_id, 'bp_xprofile_fields' );
+		}
+
+		// Bust cache of parent group
+		if ( ! empty( $this->group_id ) ) {
+			wp_cache_delete( 'all',           'bp_xprofile_groups' );
+			wp_cache_delete( $this->group_id, 'bp_xprofile_groups' );
+		}
+
+	}
 }
Index: src/bp-xprofile/classes/class-bp-xprofile-group.php
--- src/bp-xprofile/classes/class-bp-xprofile-group.php
+++ src/bp-xprofile/classes/class-bp-xprofile-group.php
@@ -301,6 +301,8 @@
 			return $groups;
 		}
 
+		/** Fields ************************************************************/
+
 		// Get the group ids from the groups we found
 		$group_ids = wp_list_pluck( $groups, 'id' );
 
@@ -312,27 +314,25 @@
 			return $groups;
 		}
 
-		// Setup IN query from group IDs
-		$group_ids_in = implode( ',', (array) $group_ids );
-
 		// Support arrays and comma-separated strings
 		$exclude_fields_cs = wp_parse_id_list( $r['exclude_fields'] );
 
 		// Visibility - Handled here so as not to be overridden by sloppy use of the
 		// exclude_fields parameter. See bp_xprofile_get_hidden_fields_for_user()
 		$hidden_user_fields = bp_xprofile_get_hidden_fields_for_user( $r['user_id'] );
-		$exclude_fields_cs  = array_merge( $exclude_fields_cs, $hidden_user_fields );
-		$exclude_fields_cs  = implode( ',', $exclude_fields_cs );
+		$exclude_field_ids  = array_filter( array_merge( $exclude_fields_cs, $hidden_user_fields ) );
 
-		// Setup IN query for field IDs
-		if ( ! empty( $exclude_fields_cs ) ) {
-			$exclude_fields_sql = "AND id NOT IN ({$exclude_fields_cs})";
-		} else {
-			$exclude_fields_sql = '';
-		}
-
+		// Setup ::get() arguments
 		// Fetch the fields
-		$fields    = $wpdb->get_results( "SELECT id, name, description, type, group_id, is_required FROM {$bp->profile->table_name_fields} WHERE group_id IN ( {$group_ids_in} ) AND parent_id = 0 {$exclude_fields_sql} ORDER BY field_order" );
+		$fields = BP_XProfile_Field::get( array(
+			'exclude'           => $exclude_field_ids,
+			'group_id'          => $group_ids,
+			'parent_id'         => '0',
+			'update_meta_cache' => false, // for now
+			'fetch_data'        => false  // for now
+		) );
+
+		// Pluck the ID's 
 		$field_ids = wp_list_pluck( $fields, 'id' );
 
 		// Store field IDs for meta cache priming
@@ -343,6 +343,8 @@
 			return $groups;
 		}
 
+		/** Field Data ********************************************************/
+
 		// Maybe fetch field data
 		if ( ! empty( $r['fetch_field_data'] ) ) {
 
@@ -361,7 +363,7 @@
 					$maybe_value = maybe_unserialize( $data->value );
 
 					// Valid field values of 0 or '0' get caught by empty(), so we have an extra check for these. See #BP5731
-					if ( ( ! empty( $maybe_value ) || '0' == $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) {
+					if ( ( ! empty( $maybe_value ) || '0' === $maybe_value ) && false !== $key = array_search( $data->field_id, $field_ids ) ) {
 
 						// Fields that have data get removed from the list
 						unset( $field_ids[ $key ] );
@@ -369,7 +371,7 @@
 				}
 
 				// The remaining members of $field_ids are empty. Remove them.
-				foreach( $fields as $field_key => $field ) {
+				foreach ( $fields as $field_key => $field ) {
 					if ( in_array( $field->id, $field_ids ) ) {
 						unset( $fields[ $field_key ] );
 					}
