Index: src/bp-activity/bp-activity-filters.php
===================================================================
--- src/bp-activity/bp-activity-filters.php
+++ src/bp-activity/bp-activity-filters.php
@@ -152,8 +152,14 @@
 
 	// Unset the activity component so activity stream update fails
 	// @todo This is temporary until some kind of moderation is built.
-	if ( !bp_core_check_for_moderation( $activity->user_id, '', $activity->content ) )
+	$moderate = bp_core_check_for_moderation( $activity->user_id, '', $activity->content, 'wp_error' );
+	if ( is_wp_error( $moderate ) ) {
+		// Send back the same error object.
+		$activity->errors = $moderate;
+
+		// Backpat.
 		$activity->component = false;
+	}
 }
 
 /**
Index: src/bp-activity/bp-activity-functions.php
===================================================================
--- src/bp-activity/bp-activity-functions.php
+++ src/bp-activity/bp-activity-functions.php
@@ -1836,12 +1836,7 @@
  * Add an activity item.
  *
  * @since 1.1.0
- *
- * @uses wp_parse_args()
- * @uses BP_Activity_Activity::save() {@link BP_Activity_Activity}
- * @uses BP_Activity_Activity::rebuild_activity_comment_tree() {@link BP_Activity_Activity}
- * @uses wp_cache_delete()
- * @uses do_action() To call the 'bp_activity_add' hook.
+ * @since 2.x.0 Added 'error_type' parameter to $args.
  *
  * @param array|string $args {
  *     An array of arguments.
@@ -1873,6 +1868,7 @@
  *     @type bool     $hide_sitewide     Should the item be hidden on sitewide streams?
  *                                       Default: false.
  *     @type bool     $is_spam           Should the item be marked as spam? Default: false.
+ *     @type string   $error_type        Optional. Error type. Either 'bool' or 'wp_error'. Default: 'bool'.
  * }
  * @return int|bool The ID of the activity on success. False on error.
  */
@@ -1891,6 +1887,7 @@
 		'recorded_time'     => bp_core_current_time(), // The GMT time that this activity was recorded.
 		'hide_sitewide'     => false,                  // Should this be hidden on the sitewide activity stream?
 		'is_spam'           => false,                  // Is this activity item to be marked as spam?
+		'error_type'        => 'bool'
 	), 'activity_add' );
 
 	// Make sure we are backwards compatible.
@@ -1914,11 +1911,15 @@
 	$activity->date_recorded     = $r['recorded_time'];
 	$activity->hide_sitewide     = $r['hide_sitewide'];
 	$activity->is_spam           = $r['is_spam'];
+	$activity->error_type        = $r['error_type'];
 	$activity->action            = ! empty( $r['action'] )
-										? $r['action']
-										: bp_activity_generate_action_string( $activity );
+						? $r['action']
+						: bp_activity_generate_action_string( $activity );
 
-	if ( ! $activity->save() ) {
+	$save = $activity->save();
+	if ( 'wp_error' === $r['error_type'] && is_wp_error( $save ) ) {
+		return $save;
+	} elseif ('bool' === $r['error_type'] && false === $save ) {
 		return false;
 	}
 
@@ -1970,7 +1971,8 @@
 
 	$r = wp_parse_args( $args, array(
 		'content' => false,
-		'user_id' => bp_loggedin_user_id()
+		'user_id' => bp_loggedin_user_id(),
+		'error_type' => 'bool',
 	) );
 
 	if ( empty( $r['content'] ) || !strlen( trim( $r['content'] ) ) ) {
@@ -2010,8 +2012,13 @@
 		'primary_link' => $add_primary_link,
 		'component'    => buddypress()->activity->id,
 		'type'         => 'activity_update',
+		'error_type'   => $r['error_type']
 	) );
 
+	if ( is_wp_error( $activity_id ) ) {
+		return $activity_id;
+	}
+
 	/**
 	 * Filters the latest update content for the activity item.
 	 *
Index: src/bp-activity/classes/class-bp-activity-activity.php
===================================================================
--- src/bp-activity/classes/class-bp-activity-activity.php
+++ src/bp-activity/classes/class-bp-activity-activity.php
@@ -134,6 +134,24 @@
 	var $is_spam;
 
 	/**
+	 * Error holder.
+	 *
+	 * @since 2.5.0
+	 *
+	 * @var WP_Error
+	 */
+	public $errors;
+
+	/**
+	 * Error type to return. Either 'bool' or 'wp_error'.
+	 *
+	 * @since 2.5.0
+	 *
+	 * @var string
+	 */
+	public $error_type = 'bool';
+
+	/**
 	 * Constructor method.
 	 *
 	 * @since 1.5.0
@@ -141,6 +159,9 @@
 	 * @param int|bool $id Optional. The ID of a specific activity item.
 	 */
 	public function __construct( $id = false ) {
+		// Instantiate errors object.
+		$this->errors = new WP_Error;
+
 		if ( !empty( $id ) ) {
 			$this->id = $id;
 			$this->populate();
@@ -235,8 +256,22 @@
 		 */
 		do_action_ref_array( 'bp_activity_before_save', array( &$this ) );
 
+		if ( 'wp_error' === $this->error_type && ! empty( $this->errors->errors ) ) {
+			return $this->errors;
+		}
+
 		if ( empty( $this->component ) || empty( $this->type ) ) {
-			return false;
+			if ( 'bool' === $this->error_type ) {
+				return false;
+			} else {
+				if ( empty( $this->component ) ) {
+					$this->errors->add( 'bp_activity_missing_component' );
+				} else {
+					$this->errors->add( 'bp_activity_missing_type' );
+				}
+
+				return $this->errors;
+			}
 		}
 
 		if ( empty( $this->primary_link ) ) {
Index: src/bp-core/bp-core-moderation.php
===================================================================
--- src/bp-core/bp-core-moderation.php
+++ src/bp-core/bp-core-moderation.php
@@ -52,17 +52,15 @@
  * Check for moderation keys and too many links.
  *
  * @since 1.6.0
+ * @since 2.x.0 Added $error_type parameter.
  *
- * @uses bp_current_author_ip() To get current user IP address.
- * @uses bp_current_author_ua() To get current user agent.
- * @uses bp_current_user_can() Allow super admins to bypass blacklist.
- *
- * @param int    $user_id Topic or reply author ID.
- * @param string $title   The title of the content.
- * @param string $content The content being posted.
+ * @param int    $user_id    Topic or reply author ID.
+ * @param string $title      The title of the content.
+ * @param string $content    The content being posted.
+ * @param string $error_type The error type to return. Either 'bool' or 'wp_error'.
  * @return bool True if test is passed, false if fail.
  */
-function bp_core_check_for_moderation( $user_id = 0, $title = '', $content = '' ) {
+function bp_core_check_for_moderation( $user_id = 0, $title = '', $content = '', $error_type = 'bool' ) {
 
 	/**
 	 * Filters whether or not to bypass checking for moderation keys and too many links.
@@ -136,7 +134,11 @@
 
 		// Das ist zu viele links!
 		if ( $num_links >= $max_links ) {
-			return false;
+			if ( 'bool' === $error_type ) {
+				return false;
+			} else {
+				return new WP_Error( 'bp_moderation_too_many_links', __( 'You have inputted too many links', 'buddypress' ) );
+			}
 		}
 	}
 
@@ -174,8 +176,11 @@
 				// Check each user data for current word.
 				if ( preg_match( $pattern, $post_data ) ) {
 
-					// Post does not pass.
-					return false;
+					if ( 'bool' === $error_type ) {
+						return false;
+					} else {
+						return new WP_Error( 'bp_moderation_blacklist_match', __( 'You have inputted an inappropriate word.', 'buddypress' ) );
+					}
 				}
 			}
 		}
Index: src/bp-groups/bp-groups-activity.php
===================================================================
--- src/bp-groups/bp-groups-activity.php
+++ src/bp-groups/bp-groups-activity.php
@@ -379,7 +379,8 @@
 		'item_id'           => false,
 		'secondary_item_id' => false,
 		'recorded_time'     => bp_core_current_time(),
-		'hide_sitewide'     => $hide_sitewide
+		'hide_sitewide'     => $hide_sitewide,
+		'error_type'        => 'bool'
 	) );
 
 	return bp_activity_add( $r );
Index: src/bp-groups/bp-groups-functions.php
===================================================================
--- src/bp-groups/bp-groups-functions.php
+++ src/bp-groups/bp-groups-functions.php
@@ -944,6 +944,7 @@
  * Post an Activity status update affiliated with a group.
  *
  * @since 1.2.0
+ * @since 2.x.0 Added 'error_type' parameter to $args.
  *
  * @param array|string $args {
  *     Array of arguments.
@@ -963,9 +964,10 @@
 	$bp = buddypress();
 
 	$defaults = array(
-		'content'  => false,
-		'user_id'  => bp_loggedin_user_id(),
-		'group_id' => 0
+		'content'    => false,
+		'user_id'    => bp_loggedin_user_id(),
+		'group_id'   => 0,
+		'error_type' => 'bool'
 	);
 
 	$r = wp_parse_args( $args, $defaults );
@@ -1006,11 +1008,12 @@
 	$content_filtered = apply_filters( 'groups_activity_new_update_content', $activity_content );
 
 	$activity_id = groups_record_activity( array(
-		'user_id' => $user_id,
-		'action'  => $action,
-		'content' => $content_filtered,
-		'type'    => 'activity_update',
-		'item_id' => $group_id
+		'user_id'    => $user_id,
+		'action'     => $action,
+		'content'    => $content_filtered,
+		'type'       => 'activity_update',
+		'item_id'    => $group_id,
+		'error_type' => $error_type
 	) );
 
 	groups_update_groupmeta( $group_id, 'last_activity', bp_core_current_time() );
Index: src/bp-templates/bp-legacy/buddypress-functions.php
===================================================================
--- src/bp-templates/bp-legacy/buddypress-functions.php
+++ src/bp-templates/bp-legacy/buddypress-functions.php
@@ -945,11 +945,11 @@
 	}
 
 	if ( ! $object && bp_is_active( 'activity' ) ) {
-		$activity_id = bp_activity_post_update( array( 'content' => $_POST['content'] ) );
+		$activity_id = bp_activity_post_update( array( 'content' => $_POST['content'], 'error_type' => 'wp_error' ) );
 
 	} elseif ( 'groups' === $object ) {
 		if ( $item_id && bp_is_active( 'groups' ) )
-			$activity_id = groups_post_update( array( 'content' => $_POST['content'], 'group_id' => $item_id ) );
+			$activity_id = groups_post_update( array( 'content' => $_POST['content'], 'group_id' => $item_id, 'error_type' => 'wp_error' ) );
 
 	} else {
 
@@ -957,8 +957,11 @@
 		$activity_id = apply_filters( 'bp_activity_custom_update', false, $object, $item_id, $_POST['content'] );
 	}
 
-	if ( empty( $activity_id ) )
+	if ( false === $activity_id ) {
 		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . __( 'There was a problem posting your update. Please try again.', 'buddypress' ) . '</p></div>' );
+	} elseif ( is_wp_error( $activity_id ) && ! empty( $activity_id->errors ) ) {
+		exit( '-1<div id="message" class="error bp-ajax-message"><p>' . $activity_id->get_error_message() . '</p></div>' );
+	}
 
 	$last_recorded = ! empty( $_POST['since'] ) ? date( 'Y-m-d H:i:s', intval( $_POST['since'] ) ) : 0;
 	if ( $last_recorded ) {
