diff --git src/bp-activity/bp-activity-actions.php src/bp-activity/bp-activity-actions.php
index 576628f..8219a50 100644
--- src/bp-activity/bp-activity-actions.php
+++ src/bp-activity/bp-activity-actions.php
@@ -13,6 +13,43 @@
 if ( !defined( 'ABSPATH' ) ) exit;
 
 /**
+ * Allow post types to register to the tracking feature
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @uses buddypress()
+ * @uses get_post_types()
+ * @uses post_type_supports()
+ */
+function bp_activity_tracking_register_post_types() {
+	$bp = buddypress();
+	$bp->activity->track = array();
+
+	// Fetch public post types
+	$post_types = get_post_types( array( 'public' => true ), 'objects' );
+
+	foreach ( $post_types as $key => $object ) {
+		// Check if they support BuddyPress activity tracking feature
+		if ( ! post_type_supports( $key, 'buddypress-activity' ) ) {
+			continue;
+		}
+
+		$bp->activity->track[ 'new_' . $key ] = apply_filters( 'bp_activity_tracking_register_' . $key . '_post', array(
+			'component_id'      => $bp->activity->id,
+			'action_id'         => 'new_' . $key,
+			'admin_filter'      => sprintf( _x( 'New %s published', 'Custom Post Type generic activity post admin filter label', 'buddypress' ), strtolower( $object->labels->singular_name ) ),
+			'callback'          => 'bp_activity_format_activity_action_custom_post_type_post',
+			'filter'            => $object->labels->name,
+			'contexts'          => array( 'activity' ),
+			'singular'          => strtolower( $object->labels->singular_name ),
+			'plural'            => strtolower( $object->labels->name ),
+			'activity_commment' => ! post_type_supports( $key, 'comments' ),
+		), $object );
+	}
+}
+add_action( 'bp_init', 'bp_activity_tracking_register_post_types', 7 );
+
+/**
  * Allow core components and dependent plugins to register activity actions.
  *
  * @since BuddyPress (1.2.0)
@@ -766,3 +803,58 @@ function bp_ajax_get_suggestions() {
 	wp_send_json_success( $results );
 }
 add_action( 'wp_ajax_bp_get_suggestions', 'bp_ajax_get_suggestions' );
+
+/**
+ * Detect a change in post type status, and initiate an activity update if necessary.
+ *
+ * @since BuddyPress (?)
+ *
+ * @todo Support untrashing better
+ *
+ * @param string $new_status New status for the post.
+ * @param string $old_status Old status for the post.
+ * @param object $post Post data.
+ */
+function bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post ) {
+	$bp = buddypress();
+
+	// This is an edit
+	if ( $new_status === $old_status ) {
+		if ( $new_status == 'publish' ) {
+			// Update the post type
+			if ( ! empty( $bp->activity->track[ 'new_' . $post->post_type ] ) ) {
+				bp_activity_custom_post_type_update( $post );
+			} else {
+				do_action( 'bp_activity_post_type_update_' . $post->post_type, $post );
+			}
+			return;
+		}
+	}
+
+	// Publishing a previously unpublished post
+	if ( 'publish' === $new_status ) {
+		// Untrashing the post type
+		// Nothing here yet
+		if ( 'trash' == $old_status ) {
+			do_action( 'bp_activity_post_type_untrash_' . $post->post_type, $post );
+		} else {
+			// Record the post type
+			if ( ! empty( $bp->activity->track[ 'new_' . $post->post_type ] ) ) {
+				bp_activity_custom_post_type_publish( $post->ID, $post );
+			} else {
+				do_action( 'bp_activity_post_type_publish_' . $post->post_type, $post->ID, $post );
+			}
+		}
+
+	// Unpublishing a previously published post
+	} else if ( 'publish' === $old_status ) {
+		// Some form of pending status
+		// Only remove the activity entry
+		if ( ! empty( $bp->activity->track[ 'new_' . $post->post_type ] ) ) {
+			bp_activity_custom_post_type_unpublish( $post->ID, $post );
+		} else {
+			do_action( 'bp_activity_post_type_unpublish_' . $post->post_type, $post->id );
+		}
+	}
+}
+add_action( 'transition_post_status', 'bp_activity_catch_transition_post_type_status', 10, 3 );
diff --git src/bp-activity/bp-activity-functions.php src/bp-activity/bp-activity-functions.php
index fcbbc07..a4a2ef4 100644
--- src/bp-activity/bp-activity-functions.php
+++ src/bp-activity/bp-activity-functions.php
@@ -1043,6 +1043,22 @@ function bp_activity_register_activity_actions() {
 
 	// Backpat. Don't use this.
 	do_action( 'updates_register_activity_actions' );
+
+	/**
+	 * Set activity actions for post types supporting activity tracking
+	 */
+	if ( ! empty( $bp->activity->track ) ) {
+		foreach ( $bp->activity->track as $post_type ) {
+			bp_activity_set_action(
+				$post_type['component_id'],
+				$post_type['action_id'],
+				$post_type['admin_filter'],
+				$post_type['callback'],
+				$post_type['filter'],
+				$post_type['contexts']
+			);
+		}
+	}
 }
 add_action( 'bp_register_activity_actions', 'bp_activity_register_activity_actions' );
 
@@ -1130,6 +1146,45 @@ function bp_activity_format_activity_action_activity_comment( $action, $activity
 	return apply_filters( 'bp_activity_comment_action', $action, $activity );
 }
 
+/**
+ * Format custom post type activity post actions.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param string $action Static activity action.
+ * @param object $activity Activity data object.
+ * @return string
+ */
+function bp_activity_format_activity_action_custom_post_type_post( $action, $activity ) {
+	$bp = buddypress();
+
+	if ( empty( $activity->type ) || empty( $bp->activity->track[ $activity->type ] ) ) {
+		return $action;
+	}
+
+	$user_link = bp_core_get_userlink( $activity->user_id );
+	$blog_url  = get_home_url( $activity->item_id );
+	$post_url  = add_query_arg( 'p', $activity->secondary_item_id, trailingslashit( $blog_url ) );
+	$post_link = '<a href="' . $post_url . '">' . $bp->activity->track[ $activity->type ]['singular'] . '</a>';
+
+	$action  = sprintf( _x( '%1$s wrote a new %2$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_link );
+
+	if ( is_multisite() ) {
+		$blog_link = '<a href="' . $blog_url . '">' . get_blog_option( $activity->item_id, 'blogname' ) . '</a>';
+		$action    = sprintf( _x( '%1$s wrote a new %2$s, on the site %3$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_link, $blog_link );
+	}
+
+	/**
+	 * Filters the formatted custom post type activity post action string.
+	 *
+	 * @since BuddyPress (2.2.0)
+	 *
+	 * @param string               $action Activity action string value.
+	 * @param BP_Activity_Activity $activity Activity item object.
+	 */
+	return apply_filters( 'bp_activity_custom_post_type_post_action', $action, $activity );
+}
+
 /******************************************************************************
  * Business functions are where all the magic happens in BuddyPress. They will
  * handle the actual saving or manipulation of information. Usually they will
@@ -1513,6 +1568,175 @@ function bp_activity_post_update( $args = '' ) {
 }
 
 /**
+ * Publish an activity for the custom post type.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int     $post_id
+ * @param WP_Post $post
+ * @param int     $user_id
+ */
+function bp_activity_custom_post_type_publish( $post_id = 0, $post = null, $user_id = 0 ) {
+	$bp = buddypress();
+
+	if ( ! is_a( $post, 'WP_Post' ) ) {
+		return;
+	}
+
+	if ( 'publish' != $post->post_status || ! empty( $post->post_password ) || empty( $bp->activity->track[ 'new_' . $post->post_type ] ) ) {
+		return;
+	}
+
+	if ( empty( $post_id ) ) {
+		$post_id = $post->ID;
+	}
+
+	$blog_id = get_current_blog_id();
+
+	if ( empty( $user_id ) ) {
+		$user_id = (int) $post->post_author;
+	}
+
+	// Record this in activity streams
+	$blog_url = get_home_url( $blog_id );
+	$post_url = add_query_arg(
+		'p',
+		$post_id,
+		trailingslashit( $blog_url )
+	);
+	$activity_post_object = (object) $bp->activity->track[ 'new_' . $post->post_type ];
+	$post_link = '<a href="' . $post_url . '">' . $activity_post_object->singular . '</a>';
+	$user_link = bp_core_get_userlink( $user_id);
+
+	$action  = sprintf( _x( '%1$s wrote a new %2$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_link );
+
+	if ( is_multisite() ) {
+		$blog_link = '<a href="' . $blog_url . '">' . get_blog_option( $blog_id, 'blogname' ) . '</a>';
+		$action    = sprintf( _x( '%1$s wrote a new %2$s, on the site %3$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_link, $blog_link );
+	}
+
+	$existing = bp_activity_get( array(
+		'filter' => array(
+			'action'       => $activity_post_object->action_id,
+			'primary_id'   => $blog_id,
+			'secondary_id' => $post_id,
+		)
+	) );
+
+	if ( ! empty( $existing['activities'] ) ) {
+		return;
+	}
+
+	$activity_args = array(
+		'user_id'           => $user_id,
+		'content'           => apply_filters( 'bp_activity_custom_post_type_publish_pre_content',  $post->post_content, $post, $post_link ),
+		'primary_link'      => apply_filters( 'bp_activity_custom_post_type_publish_primary_link', $post_link,          $post_id          ),
+		'component'         => $activity_post_object->component_id,
+		'type'              => $activity_post_object->action_id,
+		'item_id'           => $blog_id,
+		'secondary_item_id' => $post_id,
+		'primary_link'      => $post_url,
+		'recorded_time'     => $post->post_date_gmt,
+		'hide_sitewide'     => apply_filters( 'bp_activity_custom_post_type_publish_primary_link', false, $post ),
+	);
+
+	// Remove large images and replace them with just one image thumbnail
+	if ( ! empty( $activity_args['content'] ) ) {
+		$activity_args['content'] = bp_activity_thumbnail_content_images( $activity_args['content'], $activity_args['primary_link'], $activity_args );
+	}
+
+	if ( ! empty( $activity_args['content'] ) ) {
+		$activity_args['content'] = apply_filters( 'bp_activity_custom_post_type_publish_content', bp_create_excerpt( $activity_args['content'] ), $activity_args['content'], $activity_args );
+	}
+
+	do_action( 'bp_activity_custom_post_type_published', bp_activity_add( $activity_args ), $activity_args, $post );
+}
+
+/**
+ * Update an activity for the custom post type.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param WP_Post $post
+ */
+function bp_activity_custom_post_type_update( $post = null ) {
+	$bp = buddypress();
+
+	if ( ! is_a( $post, 'WP_Post' ) ) {
+		return;
+	}
+
+	if ( empty( $bp->activity->track[ 'new_' . $post->post_type ] ) ) {
+		return;
+	}
+
+	$activity_post_object = (object) $bp->activity->track[ 'new_' . $post->post_type ];
+
+	$activity_id = bp_activity_get_activity_id( array(
+		'component'         => $activity_post_object->component_id,
+		'item_id'           => get_current_blog_id(),
+		'secondary_item_id' => $post->ID,
+		'type'              => $activity_post_object->action_id,
+	 ) );
+
+	// activity ID doesn't exist, so stop!
+	if ( empty( $activity_id ) ) {
+		return;
+	}
+
+	// update the activity entry
+	$activity = new BP_Activity_Activity( $activity_id );
+
+	if ( ! empty( $post->post_content ) ) {
+		// Make sure to update the thumbnail image
+		$post_content = bp_activity_thumbnail_content_images( $post->post_content, $activity->primary_link, (array) $activity );
+
+		// Make sure to apply the excerpt
+		$activity->content = apply_filters( 'bp_activity_custom_post_type_update_content', bp_create_excerpt( $post_content ), $post_content, (array) $activity );
+	}
+
+	// Save the updated activity
+	$activity->save();
+
+	do_action( 'bp_activity_custom_post_type_update_updated', $activity, $post );
+}
+
+/**
+ * Unpublish an activity for the custom post type.
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int     $post_id
+ * @param WP_Post $post
+ */
+function bp_activity_custom_post_type_unpublish( $post_id = 0, $post = null ) {
+	$bp = buddypress();
+
+	if ( ! is_a( $post, 'WP_Post' ) ) {
+		return;
+	}
+
+	if ( empty( $bp->activity->track[ 'new_' . $post->post_type ] ) ) {
+		return;
+	}
+
+	if ( empty( $post_id ) ) {
+		$post_id = $post->ID;
+	}
+
+	$activity_post_object = (object) $bp->activity->track[ 'new_' . $post->post_type ];
+
+	$delete_activity_args = array(
+		'item_id'           => get_current_blog_id(),
+		'secondary_item_id' => $post_id,
+		'component'         => $activity_post_object->component_id,
+		'type'              => $activity_post_object->action_id
+	);
+
+	do_action( 'bp_activity_custom_post_type_unpublished', bp_blogs_delete_activity( $delete_activity_args ), $delete_activity_args, $post );
+}
+
+/**
  * Add an activity comment.
  *
  * @since BuddyPress (1.2.0)
@@ -1988,6 +2212,7 @@ function bp_activity_delete_comment( $activity_id, $comment_id ) {
  * @return string $link Permalink for the activity item.
  */
 function bp_activity_get_permalink( $activity_id, $activity_obj = false ) {
+	$bp = buddypress();
 
 	if ( empty( $activity_obj ) ) {
 		$activity_obj = new BP_Activity_Activity( $activity_id );
@@ -1997,7 +2222,18 @@ function bp_activity_get_permalink( $activity_id, $activity_obj = false ) {
 		$activity_obj = $activity_obj->current_comment;
 	}
 
-	if ( 'new_blog_post' == $activity_obj->type || 'new_blog_comment' == $activity_obj->type || 'new_forum_topic' == $activity_obj->type || 'new_forum_post' == $activity_obj->type ) {
+	$use_primary_links = array(
+		'new_blog_post',
+		'new_blog_comment',
+		'new_forum_topic',
+		'new_forum_post',
+	);
+
+	if ( ! empty( $bp->activity->track ) ) {
+		$use_primary_links = array_merge( $use_primary_links, array_keys( $bp->activity->track ) );
+	}
+
+	if ( false !== array_search( $activity_obj->type, $use_primary_links ) ) {
 		$link = $activity_obj->primary_link;
 	} else {
 		if ( 'activity_comment' == $activity_obj->type ) {
diff --git src/bp-activity/bp-activity-template.php src/bp-activity/bp-activity-template.php
index 0318b4e..1ac682b 100644
--- src/bp-activity/bp-activity-template.php
+++ src/bp-activity/bp-activity-template.php
@@ -3308,30 +3308,38 @@ function bp_activity_filter_links( $args = false ) {
  */
 function bp_activity_can_comment() {
 	global $activities_template;
+	$bp = buddypress();
 
 	// Assume activity can be commented on
 	$can_comment = true;
 
 	// Determine ability to comment based on activity action name
 	$activity_action = bp_get_activity_action_name();
-	switch ( $activity_action ) {
-
-		// Maybe turn off for blog and forum updates
-		case 'new_blog_post'    :
-		case 'new_blog_comment' :
-		case 'new_forum_topic'  :
-		case 'new_forum_post'   :
-			if ( ! empty( $activities_template->disable_blogforum_replies ) ) {
-				$can_comment = false;
-			}
-			break;
 
-		// Turn off for activity comments
-		case 'activity_comment' :
-			$can_comment = false;
-			break;
+	$turn_off = 0;
+	if ( ! empty( $activities_template->disable_blogforum_replies ) ) {
+		$turn_off = 1;
 	}
 
+	$maybe_turn_off = array_fill_keys( array(
+		'new_blog_post',
+		'new_blog_comment',
+		'new_forum_topic',
+		'new_forum_post',
+	), $turn_off );
+
+	$maybe_turn_off['activity_comment'] = 1;
+
+	if ( ! empty( $bp->activity->track ) ) {
+		foreach ( array_keys( $bp->activity->track ) as $action  ) {
+			if ( empty( $bp->activity->track[ $action ]['activity_commment'] ) ) {
+				$maybe_turn_off[ $action ] = $turn_off;
+			}
+		}
+	}
+
+	$can_comment = empty( $maybe_turn_off[ $activity_action ] );
+
 	/**
 	 * Filters whether a comment can be made on an activity item.
 	 *
diff --git src/bp-blogs/bp-blogs-functions.php src/bp-blogs/bp-blogs-functions.php
index 41f62f6..4e7f663 100644
--- src/bp-blogs/bp-blogs-functions.php
+++ src/bp-blogs/bp-blogs-functions.php
@@ -402,56 +402,6 @@ function bp_blogs_update_option_thread_comments_depth( $oldvalue, $newvalue ) {
 add_action( 'update_option_thread_comments_depth', 'bp_blogs_update_option_thread_comments_depth', 10, 2 );
 
 /**
- * Detect a change in post status, and initiate an activity update if necessary.
- *
- * Posts get new activity updates when (a) they are being published, and (b)
- * they have not already been published. This enables proper posting for
- * regular posts as well as scheduled posts, while preventing post bumping.
- *
- * See #4090, #3746, #2546 for background.
- *
- * @since BuddyPress (2.0.0)
- *
- * @todo Support untrashing better
- *
- * @param string $new_status New status for the post.
- * @param string $old_status Old status for the post.
- * @param object $post Post data.
- */
-function bp_blogs_catch_transition_post_status( $new_status, $old_status, $post ) {
-
-	// This is an edit
-	if ( $new_status === $old_status ) {
-		if ( $new_status == 'publish' ) {
-			bp_blogs_update_post( $post );
-			return;
-		}
-	}
-
-	// Publishing a previously unpublished post
-	if ( 'publish' === $new_status ) {
-		// Untrashing the post
-		// Nothing here yet
-		if ( 'trash' == $old_status ) {}
-
-		// Record the post
-		bp_blogs_record_post( $post->ID, $post );
-
-	// Unpublishing a previously published post
-	} else if ( 'publish' === $old_status ) {
-		// Some form of pending status
-		// Only remove the activity entry
-		bp_blogs_delete_activity( array(
-			'item_id'           => get_current_blog_id(),
-			'secondary_item_id' => $post->ID,
-			'component'         => buddypress()->blogs->id,
-			'type'              => 'new_blog_post'
-		) );
-	}
-}
-add_action( 'transition_post_status', 'bp_blogs_catch_transition_post_status', 10, 3 );
-
-/**
  * Record a new blog post in the BuddyPress activity stream.
  *
  * @param int $post_id ID of the post being recorded.
@@ -548,6 +498,7 @@ function bp_blogs_record_post( $post_id, $post, $user_id = 0 ) {
 
 	do_action( 'bp_blogs_new_blog_post', $post_id, $post, $user_id );
 }
+add_action( 'bp_activity_post_type_publish_post', 'bp_blogs_record_post', 10, 2 );
 
 /**
  * Updates a blog post's corresponding activity entry during a post edit.
@@ -655,6 +606,28 @@ function bp_blogs_update_post( $post ) {
 		bp_activity_delete_meta( $activity_id, 'post_comment_status' );
 	}
 }
+add_action( 'bp_activity_post_type_update_post', 'bp_blogs_update_post', 10, 1 );
+
+/**
+ * Delete a blog post activity
+ *
+ * Used if the post has been unpublished (draft, private, trash)
+ *
+ * @since BuddyPress (2.2.0)
+ *
+ * @param int $blog_id
+ * @param int $post_id
+ * @uses  bp_blogs_delete_activity() to delete the new blog post activity
+ */
+function bp_blogs_unpublish_post( $post_id = 0 ) {
+	bp_blogs_delete_activity( array(
+		'item_id'           => get_current_blog_id(),
+		'secondary_item_id' => $post_id,
+		'component'         => buddypress()->blogs->id,
+		'type'              => 'new_blog_post'
+	) );
+}
+add_action( 'bp_activity_post_type_unpublish_post', 'bp_blogs_unpublish_post', 10, 2 );
 
 /**
  * Record a new blog comment in the BuddyPress activity stream.
diff --git src/bp-core/deprecated/2.2.php src/bp-core/deprecated/2.2.php
index e69de29..d4473b7 100644
--- src/bp-core/deprecated/2.2.php
+++ src/bp-core/deprecated/2.2.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Deprecated functions
+ *
+ * @package BuddyPress
+ * @subpackage Core
+ * @deprecated 2.2.0
+ */
+
+// Exit if accessed directly
+if ( ! defined( 'ABSPATH' ) ) exit;
+
+/**
+ * Detect a change in post status, and initiate an activity update if necessary.
+ *
+ * Posts get new activity updates when (a) they are being published, and (b)
+ * they have not already been published. This enables proper posting for
+ * regular posts as well as scheduled posts, while preventing post bumping.
+ *
+ * See #4090, #3746, #2546 for background.
+ *
+ * @since BuddyPress (2.0.0)
+ * @deprecated BuddyPress (2.2.0)
+ *
+ * @todo Support untrashing better
+ *
+ * @param string $new_status New status for the post.
+ * @param string $old_status Old status for the post.
+ * @param object $post Post data.
+ */
+function bp_blogs_catch_transition_post_status( $new_status, $old_status, $post ) {
+	_deprecated_function( __FUNCTION__, '2.2', 'bp_activity_catch_transition_post_type_status()' );
+	bp_activity_catch_transition_post_type_status( $new_status, $old_status, $post );
+}
