Signup fields via the REST API

With the purpose of adding parity between the web version and the REST API, we are introducing a minor breaking change in the next version of BuddyPress (v14.0.0 ) in how the BuddyPress REST API handles Signup XProfile fields.

In this article, I’ll try to explain what the change is, why is necessary, and how you can prepare your app for these changes which will be available when the new BuddyPress version is released.

What problem are we solving?

Currently, our REST API does not support getting Signup XProfile fields or saving them. This means that when creating a signup via the API, there is only one XProfile field available, the user_name argument. This argument is mapped to the required “Name (Primary)” field.

However, if a community administrator creates more custom Signup fields, they would not be able to use them via the REST API. This creates an incomplete experience for API consumers that need to have signup fields in their apps.

As part of the, not yet released, v14.0.0 version, we are changing this behavior. API consumers will be able to return the Signup XProfile fields, and send them while creating a signup via the API. 🥳

Fetch Signup fields

To fetch signup fields only, we introduced a new argument, signup_fields_only. Here is an example of a request to select the Signup group, using the XProfile Groups endpoint:

bp.apiRequest( {
  path: 'buddypress/v1/xprofile/groups',
  type: 'GET',
  data: {
    context: 'view',
    fetch_fields: true // <-- required, in this endpoint.
    signup_fields_only: true // <--- new argument.
  }
} ).done( function( data ) {
  return data; // <--- It returns the signup fields only.
} ).fail( function( error ) {
  return error;
} );

Here is an example of a request to select only the Signup fields, using the XProfile Fields endpoint:

bp.apiRequest( {
  path: 'buddypress/v1/xprofile/fields',
  type: 'GET',
  data: {
    context: 'view',
    signup_fields_only: true // <--- new argument.
  }
} ).done( function( data ) {
  return data; // <--- It returns the signup fields only.
} ).fail( function( error ) {
  return error;
} );

Send signup fields

While creating a signup via the API, a new argument, signup_field_data is required. This argument is mapped to the Signup fields (the order the field data is sent is not important).

bp.apiRequest( {
  path: 'buddypress/v1/signup',
  type: 'POST',
  data: {
    context: 'edit',
    user_login: 'testuser',
    user_email: '[email protected]',
    password: 'password' // Always use strong passwords, not like this one!
    "signup_field_data[0][field_id]": "36",
	"signup_field_data[0][value]": "Arabic, English",
	"signup_field_data[1][field_id]": "31",
	"signup_field_data[1][value]": "Sometimes, I never travel",
	"signup_field_data[2][field_id]": "35",
	"signup_field_data[2][value]": "This is some text for my profile.",
	"signup_field_data[3][field_id]": "1",
	"signup_field_data[3][value]": "New Profile",
	"signup_field_data[4][field_id]": "19",
	"signup_field_data[4][value]": "Option 01, Option 03",
  }
} ).done( function( data ) {
  return data;
} ).fail( function( error ) {
  return error;
} );

The most noticeable change is that required fields will be respected in the API now. If you send a request creating a signup without the required fields (or sending empty values for the required fields), the REST API will complain about it, just like the web version.

Here is an example of a failed request without a required field.

{
  "code": "bp_rest_signup_field_required",
  "data": {
    "status": 500
  },
  "message": "The CUSTOM field is required."
}

How to migrate

Now, how can you prepare for this change? If your project makes use of the now deprecated user_name argument, you’ll need to update your app to use the new signup_field_data argument when creating a signup via the REST API.

We haven’t updated our documentation yet with those changes, but here is the schema for the new argument:

"signup_field_data": {
  "description": "The XProfile field data for the new user.",
  "items": {
    "properties": {
      "field_id": {
        "description": "The XProfile field ID.",
        "required": true,
        "type": "integer",
      },
      "value": {
        "default": "",
        "description": "The value(s) (comma separated list of values needs to be used in case of multiple values) for the field data.",
        "required": true,
        "type": "string",
      },
      "visibility": {
        "default": "public",
        "description": "The visibility for the XProfile field.",
        "enum": [
          "public",
          "adminsonly",
          "loggedin",
          "friends"
        ],
        "required": false,
        "type": "string",
      }
    },
    "type": "object"
  },
}

The new field expects an array of data with the field_id, its value, and the visibility of the field.

Why do I need to move my code into the new signup_field_data?

One important goal of this change is to add parity with the web version. Keeping the new and the old argument user_name would defeat this purpose. It would also create unnecessary handling of fallbacks, and ultimately confusion.

A user could send the “Name (Primary)” field via user_name argument or using the new signup_field_data argument. Required fields would also not be truly required. Essentially, creating a lot of code to account for both situations.

It’s also accurate to say that the old behavior is a bug, not a feature (or at the very least an MVP of an MVP of an MVP). We are correcting this bug as part of this change.

=D

#developers, #rest-api

BuddyPress REST API, what’s new in 7.0.0 ?

First of all, the Developer documentation has been updated according to the latest improvements we’ve brought to the BuddyPress REST API!

Members Endpoints

What’s the friendship status of the logged in user with other(s)?

If the Friends component is active and you’re wondering what’s the answer to this question, then you can get a clue thanks to a new property we’ve added to the Members item schema: friendship_status_slug.

Here’s an example of use of this new property:

/**
 * Showing the `friendship_status_slug` Members Endpoint's schema property.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/members\',
			type: \'GET\',
			data: {
				context: \'view\',
				type: \'active\',
				exclude: [2]
			}
		} ).done( function( data ) {
			data.forEach( function( member ) {
				console.log( member.name + \' => \' + member.friendship_status_slug );
			} );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

As you can see in the above browser’s inspector, the logged in user is :

  • not friend with admin (friendship_status_slug: 'not_friends'),
  • has requested a friendship request to Mercime that is still pending her approval (friendship_status_slug: 'pending'),
  • has received a friendship request from John that is awaiting his response (friendship_status_slug: 'awaiting_response'),
  • friend with Boone (friendship_status_slug: 'is_friend').

When was a user last active, what is his/her latest update and how many friends do he/she has ?

There’s now a new request argument you can use to know these information : populate_extras. You simply need to set it to true to do so. The response will include these extra properties:

  1. The last_activity object which contains 2 properties: 
    • date: the date the user was last active,
    • timediff: a human diff string, eg: “20 minutes ago”.
  2. The latest_update object which contains 3 properties:
    • id: the ID of the activity,
    • raw: the content of the activity as it exists into the database.
    • rendered: the content of the activity ready for output.
  3. The total_friend_count variable which contains the number of friends of the user (integer)

Here’s an example of use of this new request argument:

/**
 * Showing the `populate_extras` Members/:user_id request argument.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/members/2\',
			type: \'GET\',
			data: {
				context: \'view\',
				populate_extras: true
			}
		} ).done( function( member ) {
			console.log( \'Last activity:\' );
			console.log( member.last_activity );
			console.log( \'Latest activity update:\' );
			console.log( member.latest_update );
			console.log( \'Total number of friends:\' );
			console.log( member.total_friend_count );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

Can I assign more than one Member Type to a member like it’s now possible in BuddyPress?

Yes you can! You simply need to use a comma separated list of Member Types into the member_type request arggument when updating/creating the user.

Here’s an example for the first case:

/**
 * Showing the `member_type` request argument for the members/:user_id endpoint.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/members/2\',
			type: \'PUT\',
			data: {
				context: \'edit\',
				member_type: \'leads,developers\'
			}
		} ).done( function( member ) {
			console.log( \'Member Types\' );
			console.log( member.member_types );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

Groups Endpoints

Is there a route to get the groups the logged in user belongs to?

Yes, it’s /buddypress/v1/groups/me. Querying it you’ll get a list of these groups for the current user.

Here’s an example about how to use it:

/**
 * Showing the groups/me endpoint.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/groups/me\',
			type: \'GET\',
			data: {
				context: \'view\'
			}
		} ).done( function( groups ) {
			console.log( \'My Groups\' );
			groups.forEach( function( group ) {
				console.log( group.name );
			} );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

When was the last time a group was active & how many users are members of this group ?

Juste like for the Members Endpoints (GET Members & GET Member), there’s now a new request argument you can use to know these information : populate_extras. You simply need to set it to true to do so. The response will include these extra properties:

  1. total_member_count: the count of all Group members,
  2. last_activity: the date the Group was last active,
  3. last_activity_diff: the human diff time the Group was last active.

Here’s an example about how to use it:

/**
 * Showing the `populate_extras` Groups/:group_id request argument.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/groups/1\',
			type: \'GET\',
			data: {
				context: \'view\',
				populate_extras: true
			}
		} ).done( function( groups ) {
			groups.forEach( function( group ) {
				console.log( \'Last activity date:\' );
				console.log( group.last_activity );
				console.log( \'Last activity time diff:\' );
				console.log( group.last_activity_diff );
				console.log( \'Total number of group members:\' );
				console.log( group.total_member_count );
			} );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

Is it possible to append one or more Group Types to the Group Type(s) the group is assigned to ? What about removing one or more specific Group Types from the ones the group is assigned to ?

The PUT verb of the Groups/:group_id endpoint now supports 2 new request arguments to achieve this:

  • append_types : a string containing the Group Type name to append to existing group’s Group Types. To append more than one Group Type, use a comma separated list of Group Type names.
  • remove_types : a string containing the Group Type name to remove to existing group’s Group Types. To remove more than one Group Type, use a comma separated list of Group Type names.

Here’s an exemple of appending Group Types to the one that is already assigned to the group:

/**
 * Showing the groups/:group_id update method new request arguments.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/groups/1\',
			type: \'PUT\',
			data: {
				context: \'view\',
				append_types: \'team,pizzalovers\'
			}
		} ).done( function( groups ) {
			console.log( \'The initial + appended group types:\' );
			groups.forEach( function( group ) {
				console.log( group.types );
			} );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

Activity Endpoint

Is it possible to filter the list of activities on more than one action/type?

Yes, to filter the fetched activities (GET Activities) on one action/type, you need to set the type collection parameter to an array containing the corresponding action/type. For more than one action/type, include all the needed actions/types into the array used as the type collection parameter.

Here’s an example of how to achieve this:

/**
 * Showing the activity streams filtered on 2 actions/types.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/activity\',
			type: \'GET\',
			data: {
				context: \'view\',
				type: [\'activity_update\', \'friendship_created\', \'friendship_accepted\']
			}
		} ).done( function( activities ) {
			console.log( \'Updates and created friendships:\' );
			activities.forEach( function( activity ) {
				console.log( activity.id + \' => \' + activity.type );
			} );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

Do we still need to set the component request argument to groups when fetching group activities about a specified group_id request argument?

No! From now on the group_id is enough, of course you can still alternatively use a request (GET Activities) having the primary_id argument set to the ID of the group and the component argument set to groups.

Here’s an example about how you can use this request argument:

/**
 * Showing the activity streams filtered on a specific group.
 */
function test_bp_rest_api() {
	wp_enqueue_script( 'bp-api-request' );
	wp_add_inline_script(
		'bp-api-request',
		'bp.apiRequest( {
			path: \'buddypress/v1/activity\',
			type: \'GET\',
			data: {
				context: \'view\',
				group_id: 1
			}
		} ).done( function( activities ) {
			console.log( \'Group Activities:\' );
			activities.forEach( function( activity ) {
				console.log( activity.id + \' => \' + activity.component );
			} );
		} ).fail( function( error ) {
			return error;
		} );'
	);
}
add_action( 'bp_enqueue_scripts', 'test_bp_rest_api' );
Click on the image to view it in full screen

Blogs Endpoint

And last but not least, we’ve added a new route to let users create a blog (POST Blog) when the Network option is allowing it, read more about it from our freshly updated Developers documentation.

PS: if you want to get all examples at once, here’s a Gist.

#7-0-0, #rest-api