diff --git src/bp-core/bp-core-actions.php src/bp-core/bp-core-actions.php
index 2bb2334..82d0772 100644
--- src/bp-core/bp-core-actions.php
+++ src/bp-core/bp-core-actions.php
@@ -65,6 +65,7 @@ add_action( 'bp_loaded', 'bp_register_theme_directory', 14 );
  *                                                   v---Load order
  */
 add_action( 'bp_init', 'bp_core_set_uri_globals',    2  );
+add_action( 'bp_init', 'bp_register_taxonomies',     3  );
 add_action( 'bp_init', 'bp_setup_globals',           4  );
 add_action( 'bp_init', 'bp_setup_canonical_stack',   5  );
 add_action( 'bp_init', 'bp_setup_nav',               6  );
diff --git src/bp-core/bp-core-classes.php src/bp-core/bp-core-classes.php
index 1244cdf..300daf6 100644
--- src/bp-core/bp-core-classes.php
+++ src/bp-core/bp-core-classes.php
@@ -42,6 +42,8 @@ if ( !defined( 'ABSPATH' ) ) exit;
  *           IDs corresponding to the users that should be returned. When this
  *           parameter is passed, it will override all others; BP User objects
  *           will be constructed using these IDs only. Default: false.
+ *     @type array|string $member_type Optional. An array or comma-separated
+ *           list of member types to match.
  *     @type string|bool $meta_key Limit results to users that have usermeta
  *           associated with this meta_key. Usually used with $meta_value.
  *           Default: false.
@@ -160,6 +162,7 @@ class BP_User_Query {
 				'include'         => false,
 				'exclude'         => false,
 				'user_ids'        => false,
+				'member_type'     => '',
 				'meta_key'        => false,
 				'meta_value'      => false,
 				'populate_extras' => true,
@@ -390,6 +393,53 @@ class BP_User_Query {
 			);
 		}
 
+		// Member type.
+		if ( ! empty( $member_type ) ) {
+			$member_types = array();
+
+			if ( ! is_array( $member_type ) ) {
+				$member_type = preg_split( '/[,\s+]/', $member_type );
+			}
+
+			foreach ( $member_type as $mt ) {
+				if ( ! bp_get_member_type_object( $mt ) ) {
+					continue;
+				}
+
+				$member_types[] = $mt;
+			}
+
+			if ( ! empty( $member_types ) ) {
+				$member_type_tq = new WP_Tax_Query( array(
+					array(
+						'taxonomy' => 'bp_member_type',
+						'field'    => 'name',
+						'operator' => 'IN',
+						'terms'    => $member_types,
+					),
+				) );
+
+				// Switch to the root blog, where member type taxonomies live.
+				switch_to_blog( bp_get_root_blog_id() );
+
+				/*
+				 * We won't use a JOIN so it doesn't matter what
+				 * the primary table name and column are.
+				 */
+				$member_type_sql_clauses = $member_type_tq->get_sql( 'u', 'ID' );
+				restore_current_blog();
+
+
+				/*
+				 * Grab the first term_relationships clause and
+				 * convert to a subquery.
+				 */
+				if ( preg_match( '/' . $wpdb->term_relationships . '\.term_taxonomy_id.*/', $member_type_sql_clauses['where'], $matches ) ) {
+					$sql['where']['member_type'] = "u.{$this->uid_name} IN ( SELECT object_id FROM $wpdb->term_relationships WHERE {$matches[0]} )";
+				}
+			}
+		}
+
 		// 'meta_key', 'meta_value' allow usermeta search
 		// To avoid global joins, do a separate query
 		if ( false !== $meta_key ) {
diff --git src/bp-core/bp-core-dependency.php src/bp-core/bp-core-dependency.php
index 8ce93a7..3ebb06b 100644
--- src/bp-core/bp-core-dependency.php
+++ src/bp-core/bp-core-dependency.php
@@ -39,6 +39,13 @@ function bp_setup_canonical_stack() {
 }
 
 /**
+ * Fire the 'bp_register_taxonomies' action, where plugins should register taxonomies.
+ */
+function bp_register_taxonomies() {
+	do_action( 'bp_register_taxonomies' );
+}
+
+/**
  * Fire the 'bp_setup_globals' action, where plugins should initialize global settings.
  */
 function bp_setup_globals() {
diff --git src/bp-core/bp-core-taxonomy.php src/bp-core/bp-core-taxonomy.php
new file mode 100644
index 0000000..021849e
--- /dev/null
+++ src/bp-core/bp-core-taxonomy.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * BuddyPress taxonomy functions.
+ *
+ * @since BuddyPress (2.2.0)
+ */
+
+/**
+ * Register our default taxonomies.
+ *
+ * @since BuddyPress (2.2.0)
+ */
+function bp_register_default_taxonomies() {
+	// Member Type.
+	register_taxonomy( 'bp_member_type', 'user', array(
+		'public' => false,
+	) );
+}
+add_action( 'bp_register_taxonomies', 'bp_register_default_taxonomies' );
+
+/**
+ * Set taxonomy terms on a BuddyPress object.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see wp_set_object_terms() for a full description of function and parameters.
+ *
+ * @param int $object_id Object ID.
+ * @param string|array Term or terms to set.
+ * @param string $taxonomy Taxonomy name.
+ * @param bool $append Optional. True to append terms to existing terms.
+ *        Default: false.
+ * @return array Array of term taxonomy IDs.
+ */
+function bp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
+	switch_to_blog( bp_get_root_blog_id() );
+	$retval = wp_set_object_terms( $object_id, $terms, $taxonomy, $append );
+	restore_current_blog();
+
+	return $retval;
+}
+
+/**
+ * Get taxonomy terms for a BuddyPress object.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @see wp_get_object_terms() for a full description of function and parameters.
+ *
+ * @param int|array $object_ids ID or IDs of objects.
+ * @param string|array $taxonomies Name or names of taxonomies to match.
+ * @param array $args See {@see wp_get_object_terms()}.
+ * @return array
+ */
+function bp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
+	switch_to_blog( bp_get_root_blog_id() );
+	$retval = wp_get_object_terms( $object_ids, $taxonomies, $args );
+	restore_current_blog();
+
+	return $retval;
+}
diff --git src/bp-loader.php src/bp-loader.php
index 41c13e0..ebaf013 100644
--- src/bp-loader.php
+++ src/bp-loader.php
@@ -432,6 +432,7 @@ class BuddyPress {
 		require( $this->plugin_dir . 'bp-core/bp-core-update.php'     );
 		require( $this->plugin_dir . 'bp-core/bp-core-options.php'    );
 		require( $this->plugin_dir . 'bp-core/bp-core-classes.php'    );
+		require( $this->plugin_dir . 'bp-core/bp-core-taxonomy.php'   );
 		require( $this->plugin_dir . 'bp-core/bp-core-filters.php'    );
 		require( $this->plugin_dir . 'bp-core/bp-core-avatars.php'    );
 		require( $this->plugin_dir . 'bp-core/bp-core-widgets.php'    );
diff --git src/bp-members/bp-members-admin.php src/bp-members/bp-members-admin.php
index 8ca63b7..6959c50 100644
--- src/bp-members/bp-members-admin.php
+++ src/bp-members/bp-members-admin.php
@@ -186,6 +186,9 @@ class BP_Members_Admin {
 		// Add user row actions for single site
 		add_filter( 'user_row_actions', array( $this, 'row_actions' ), 10, 2 );
 
+		// Process changes to member type.
+		add_action( 'bp_members_admin_load', array( $this, 'process_member_type_update' ) );
+
 		/** Signups ***********************************************************/
 
 		if ( is_admin() ) {
@@ -719,6 +722,19 @@ class BP_Members_Admin {
 				sanitize_key( $this->stats_metabox->priority )
 			);
 
+			// Member Type metabox.
+			$member_types = bp_get_member_types();
+			if ( ! empty( $member_types ) ) {
+				add_meta_box(
+					'bp_members_admin_member_type',
+					_x( 'Member Type', 'members user-admin edit screen', 'buddypress' ),
+					array( $this, 'user_admin_member_type_metabox' ),
+					get_current_screen()->id,
+					'side',
+					'core'
+				);
+			}
+
 			/**
 			 * Custom metabox ?
 			 * Plugins can restrict metabox to "bp_moderate" admins checking
@@ -998,6 +1014,63 @@ class BP_Members_Admin {
 	}
 
 	/**
+	 * Render the Member Type metabox.
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 *
+	 * @param WP_User $user The WP_User object to be edited.
+	 */
+	public function user_admin_member_type_metabox( $user = null ) {
+
+		// Bail if no user ID.
+		if ( empty( $user->ID ) ) {
+			return;
+		}
+
+		$types = bp_get_member_types( array(), 'objects' );
+		$current_type = bp_get_member_type( $user->ID );
+
+		?>
+		<select name="bp-members-profile-member-type">
+			<option value="" <?php selected( '', $current_type ); ?>><?php _ex( ' - ', 'member type null option', 'buddypress' ) ?></option>
+			<?php foreach ( $types as $type ) : ?>
+				<option value="<?php echo esc_attr( $type->name ) ?>" <?php selected( $type->name, $current_type ) ?>><?php echo esc_html( $type->labels['singular_name'] ) ?></option>
+			<?php endforeach; ?>
+		</select>
+
+		<?php
+
+		wp_nonce_field( 'bp-member-type-change-' . $user->ID, 'bp-member-type-nonce' );
+	}
+
+	/**
+	 * Process changes from the Member Type metabox.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 * @access public
+	 */
+	public function process_member_type_update() {
+		if ( ! isset( $_POST['bp-member-type-nonce'] ) || ! isset( $_POST['bp-members-profile-member-type'] ) ) {
+			return;
+		}
+
+		$user_id = $this->get_user_id();
+
+		check_admin_referer( 'bp-member-type-change-' . $user_id, 'bp-member-type-nonce' );
+
+		/*
+		 * If an invalid member type is passed, someone's doing something
+		 * fishy with the POST request, so we can fail silently.
+		 */
+		$member_type = stripslashes( $_POST['bp-members-profile-member-type'] );
+		if ( bp_set_member_type( $user_id, $member_type ) ) {
+			// @todo Success messages can't be posted because other stuff happens on the pageload.
+		}
+
+	}
+
+	/**
 	 * Add a link to Profile in Users listing row actions.
 	 *
 	 * @access public
diff --git src/bp-members/bp-members-functions.php src/bp-members/bp-members-functions.php
index 2f9b1b3..bd36c77 100644
--- src/bp-members/bp-members-functions.php
+++ src/bp-members/bp-members-functions.php
@@ -2148,3 +2148,128 @@ function bp_live_spammer_login_error() {
 	add_action( 'login_head', 'wp_shake_js', 12 );
 }
 add_action( 'login_form_bp-spam', 'bp_live_spammer_login_error' );
+
+/** Member Types *************************************************************/
+
+/**
+ * Register a member type.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param string $member_type
+ * @param array $args
+ * @return object|WP_Error
+ */
+function bp_register_member_type( $member_type, $args = array() ) {
+	$bp = buddypress();
+
+	if ( isset( $bp->members->types[ $member_type ] ) ) {
+		return new WP_Error( 'bp_member_type_exists', __( 'Member type already exists.', 'buddypress' ), $member_type );
+	}
+
+	$r = bp_parse_args( $args, array(
+		'labels' => array(),
+	), 'register_member_type' );
+
+	$type = (object) $r;
+
+	// Store the post type name as data in the object (not just as the array key).
+	$type->name = $member_type;
+
+	// Make sure the relevant labels have been filled in.
+	$default_name = isset( $r['labels']['name'] ) ? $r['labels']['name'] : ucfirst( $type->name );
+	$r['labels'] = array_merge( array(
+		'name'          => $default_name,
+		'singular_name' => $default_name,
+	), $r['labels'] );
+
+	$bp->members->types[ $member_type ] = $type;
+
+	do_action( 'bp_registered_member_type', $member_type, $type );
+
+	return $type;
+}
+
+/**
+ * Retrieve a member type object by name.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param string $post_type The name of the member type.
+ * @return object A member type object.
+ */
+function bp_get_member_type_object( $member_type ) {
+	if ( empty( buddypress()->members->types[ $member_type ] ) ) {
+		return null;
+	}
+
+	return buddypress()->members->types[ $member_type ];
+}
+
+/**
+ * Get a list of all registered member type objects.
+ *
+ * @since BuddyPres (2.2.0)
+ *
+ * @see bp_register_member_type() for accepted arguments.
+ *
+ * @param array|string $args     Optional. An array of key => value arguments to match against
+ *                               the post type objects. Default empty array.
+ * @param string       $output   Optional. The type of output to return. Accepts post type 'names'
+ *                               or 'objects'. Default 'names'.
+ * @param string       $operator Optional. The logical operation to perform. 'or' means only one
+ *                               element from the array needs to match; 'and' means all elements
+ *                               must match. Accepts 'or' or 'and'. Default 'and'.
+ * @return array A list of post type names or objects.
+ */
+function bp_get_member_types( $args = array(), $output = 'names', $operator = 'and' ) {
+	$field = 'names' == $output ? 'name' : false;
+
+	return wp_filter_object_list( buddypress()->members->types, $args, $operator, $field);
+}
+
+/**
+ * Set type for a member.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int $user_id ID of the user.
+ * @param string $member_type Member type.
+ * @param bool $append Optional. True to append this to existing types for user,
+ *             false to replace. Default: false.
+ */
+function bp_set_member_type( $user_id, $member_type, $append = false ) {
+	// Pass an empty $member_type to remove a user's type.
+	if ( ! empty( $member_type ) && ! bp_get_member_type_object( $member_type ) ) {
+		return false;
+	}
+
+	return bp_set_object_terms( $user_id, $member_type, 'bp_member_type', $append );
+}
+
+/**
+ * Get type for a member.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int $user_id ID of the user.
+ * @param bool $single Optional. Whether to return a single type string. If
+ *        multiple types are found for the user, the oldest one will be
+ *        returned. Default: true.
+ * @return string|array|bool On success, returns a single member type (if
+ *         $single is true) or an array of member types (if $single is false).
+ *         Returns false on failure.
+ */
+function bp_get_member_type( $user_id, $single = true ) {
+	$types = bp_get_object_terms( $user_id, 'bp_member_type'  );
+
+	$type = false;
+	if ( ! empty( $types ) ) {
+		$type = wp_list_pluck( $types, 'name' );
+		if ( $single ) {
+			$type = array_pop( $type );
+		}
+	}
+
+	return $type;
+}
diff --git src/bp-members/bp-members-loader.php src/bp-members/bp-members-loader.php
index 1ab4a64..9b8b56d 100644
--- src/bp-members/bp-members-loader.php
+++ src/bp-members/bp-members-loader.php
@@ -11,6 +11,16 @@
 if ( !defined( 'ABSPATH' ) ) exit;
 
 class BP_Members_Component extends BP_Component {
+	/**
+	 * Member types.
+	 *
+	 * @see bp_register_member_type()
+	 *
+	 * @access public
+	 * @since BuddyPress (2.2.0)
+	 * @var array
+	 */
+	public $types = array();
 
 	/**
 	 * Start the members component creation process.
diff --git tests/phpunit/testcases/core/class-bp-user-query.php tests/phpunit/testcases/core/class-bp-user-query.php
index c9f1371..d30f0fc 100644
--- tests/phpunit/testcases/core/class-bp-user-query.php
+++ tests/phpunit/testcases/core/class-bp-user-query.php
@@ -407,4 +407,93 @@ class BP_Tests_BP_User_Query_TestCases extends BP_UnitTestCase {
 		$this->assertEmpty( $found_user_ids );
 	}
 
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_single_value() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'bar',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEquals( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_array_with_single_value() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => array( 'bar' ),
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEquals( array( $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_comma_separated_values() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'foo, bar',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_array_with_multiple_values() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => array( 'foo', 'bar' ),
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0], $users[1] ), $found );
+	}
+
+	/**
+	 * @group member_types
+	 */
+	public function test_member_type_comma_separated_values_should_discard_non_existent_taxonomies() {
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		$users = $this->factory->user->create_many( 3 );
+		bp_set_member_type( $users[0], 'foo' );
+		bp_set_member_type( $users[1], 'bar' );
+
+		$q = new BP_User_Query( array(
+			'member_type' => 'foo, baz',
+		) );
+
+		$found = array_values( wp_list_pluck( $q->results, 'ID' ) );
+		$this->assertEqualSets( array( $users[0] ), $found );
+	}
 }
diff --git tests/phpunit/testcases/members/types.php tests/phpunit/testcases/members/types.php
new file mode 100644
index 0000000..2ee071c
--- /dev/null
+++ tests/phpunit/testcases/members/types.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @group members
+ * @group member_types
+ */
+class BP_Tests_Members_Types extends BP_UnitTestCase {
+	public function setUp() {
+		parent::setUp();
+
+		buddypress()->members->types = array();
+	}
+
+	public function test_bp_register_member_type_should_fail_for_existing_member_type() {
+		bp_register_member_type( 'foo' );
+		$this->assertWPError( bp_register_member_type( 'foo' ) );
+	}
+
+	public function test_bp_register_member_type_should_return_type_object() {
+		$this->assertInternalType( 'object', bp_register_member_type( 'foo' ) );
+	}
+
+	public function test_bp_register_member_type_should_store_member_type_string_as_name_property() {
+		$object = bp_register_member_type( 'foo' );
+		$this->assertSame( 'foo', $object->name );
+	}
+
+	public function test_bp_register_member_type_should_fill_in_missing_labels_with_ucfirst_member_type() {
+		$object = bp_register_member_type( 'foo' );
+		foreach ( $object->labels as $label ) {
+			$this->assertSame( 'Foo', $label );
+		}
+	}
+
+	public function test_bp_get_member_type_object_should_return_null_for_non_existent_member_type() {
+		$this->assertSame( null, bp_get_member_type_object( 'foo' ) );
+	}
+
+	public function test_bp_get_member_type_object_should_return_type_object() {
+		bp_register_member_type( 'foo' );
+		$this->assertInternalType( 'object', bp_get_member_type_object( 'foo' ) );
+	}
+
+	public function test_bp_set_member_type_should_return_false_for_invalid_member_type() {
+		$this->assertFalse( bp_set_member_type( 'foo', 1 ) );
+	}
+
+	public function test_bp_set_member_type_should_remove_member_type_when_passing_an_empty_value() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_set_member_type( $u, 'foo' );
+
+		// Make sure it's set up.
+		$this->assertSame( 'foo', bp_get_member_type( $u ) );
+
+		$this->assertSame( array(), bp_set_member_type( $u, '' ) );
+		$this->assertFalse( bp_get_member_type( $u ) );
+	}
+
+	public function test_bp_set_member_type_success() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+
+		$this->assertNotEmpty( bp_set_member_type( $u, 'foo' ) );
+	}
+
+	public function test_bp_get_member_type_with_default_value_for_single() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u, 'foo' );
+		bp_set_member_type( $u, 'bar', true );
+
+		$this->assertSame( 'foo', bp_get_member_type( $u ) );
+	}
+
+	public function test_bp_get_member_type_with_single_true() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u, 'foo' );
+		bp_set_member_type( $u, 'bar', true );
+
+		$this->assertSame( 'foo', bp_get_member_type( $u, true ) );
+	}
+
+	public function test_bp_get_member_type_with_single_false() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+		bp_register_member_type( 'bar' );
+		bp_set_member_type( $u, 'foo' );
+		bp_set_member_type( $u, 'bar', true );
+
+		$this->assertEqualSets( array( 'foo', 'bar' ), bp_get_member_type( $u, false ) );
+	}
+
+	public function test_bp_get_member_type_should_return_false_when_no_value_is_found() {
+		$u = $this->factory->user->create();
+		bp_register_member_type( 'foo' );
+
+		$this->assertFalse( bp_get_member_type( $u ) );
+	}
+}
