diff --git src/bp-blogs/bp-blogs-template.php src/bp-blogs/bp-blogs-template.php
index 838f54f..c72912f 100644
--- src/bp-blogs/bp-blogs-template.php
+++ src/bp-blogs/bp-blogs-template.php
@@ -106,79 +106,31 @@ function bp_blogs_directory_permalink() {
  *
  * Responsible for loading a group of blogs into a loop for display.
  */
-class BP_Blogs_Template {
+class BP_Blogs_Template extends BP_Template_Loop {
 
 	/**
-	 * The loop iterator.
+	 * Default arguments for Blogs loop.
 	 *
-	 * @access public
-	 * @var int
-	 */
-	public $current_blog = -1;
-
-	/**
-	 * The number of blogs returned by the paged query.
+	 * @since BuddyPress (2.3.0)
 	 *
-	 * @access public
-	 * @var int
-	 */
-	public $blog_count = 0;
-
-	/**
-	 * Array of blogs located by the query..
-	 *
-	 * @access public
+	 * @access protected
 	 * @var array
 	 */
-	public $blogs = array();
-
-	/**
-	 * The blog object currently being iterated on.
-	 *
-	 * @access public
-	 * @var object
-	 */
-	public $blog;
-
-	/**
-	 * A flag for whether the loop is currently being iterated.
-	 *
-	 * @access public
-	 * @var bool
-	 */
-	public $in_the_loop = false;
-
-	/**
-	 * The page number being requested.
-	 *
-	 * @access public
-	 * @var public
-	 */
-	public $pag_page = 1;
-
-	/**
-	 * The number of items being requested per page.
-	 *
-	 * @access public
-	 * @var public
-	 */
-	public $pag_num = 20;
-
-	/**
-	 * An HTML string containing pagination links.
-	 *
-	 * @access public
-	 * @var string
-	 */
-	public $pag_links = '';
-
-	/**
-	 * The total number of blogs matching the query parameters.
-	 *
-	 * @access public
-	 * @var int
-	 */
-	public $total_blog_count = 0;
+	protected $default_args = array(
+		'labels' => array(
+			'item'              => 'blog',
+			'item_plural'       => 'blogs',
+			'item_total_count'  => 'total_blog_count',
+			'item_count'        => 'blog_count',
+			'current_item'      => 'current_blog',
+		),
+		'params' => array(
+			'page_arg'          => 'bpage',
+			'page'              => 1,
+			'per_page'          => 20,
+			'max'               => 0,
+		)
+	);
 
 	/**
 	 * Constructor method.
@@ -198,148 +150,39 @@ class BP_Blogs_Template {
 	 * @param array $include_blog_ids Array of blog IDs to include.
 	 */
 	public function __construct( $type, $page, $per_page, $max, $user_id, $search_terms, $page_arg = 'bpage', $update_meta_cache = true, $include_blog_ids = false ) {
+		$args = array( 'params' => array(
+			'type'              => $type,
+			'page'              => $page,
+			'per_page'          => $per_page,
+			'max'               => $max,
+			'user_id'           => $user_id,
+			'search_terms'      => $search_terms,
+			'page_arg'          => $page_arg,
+			'update_meta_cache' => $update_meta_cache,
+			'include_blog_ids'  => $include_blog_ids,
+		) );
 
-		$this->pag_page = isset( $_REQUEST[ $page_arg ] ) ? intval( $_REQUEST[ $page_arg ] ) : $page;
-		$this->pag_num  = isset( $_REQUEST['num']       ) ? intval( $_REQUEST['num']       ) : $per_page;
-
-		// Backwards compatibility support for blogs by first letter
-		if ( ! empty( $_REQUEST['letter'] ) ) {
-			$this->blogs = BP_Blogs_Blog::get_by_letter( $_REQUEST['letter'], $this->pag_num, $this->pag_page );
-
-		// Typical blogs query
-		} else {
-			$this->blogs = bp_blogs_get_blogs( array(
-				'type'              => $type,
-				'per_page'          => $this->pag_num,
-				'page'              => $this->pag_page,
-				'user_id'           => $user_id,
-				'search_terms'      => $search_terms,
-				'update_meta_cache' => $update_meta_cache,
-				'include_blog_ids'  => $include_blog_ids,
-			) );
-		}
-
-		// Set the total blog count
-		if ( empty( $max ) || ( $max >= (int) $this->blogs['total'] ) ) {
-			$this->total_blog_count = (int) $this->blogs['total'];
-		} else {
-			$this->total_blog_count = (int) $max;
-		}
-
-		// Set the blogs array (to loop through later
-		$this->blogs = $this->blogs['blogs'];
-
-		// Get the current blog count to compare maximum against
-		$blog_count = count( $this->blogs );
-
-		// Set the current blog count
-		if ( empty( $max ) || ( $max >= (int) $blog_count ) ) {
-			$this->blog_count = (int) $blog_count;
-		} else {
-			$this->blog_count = (int) $max;
-		}
-
-		// Build pagination links based on total blogs and current page number
-		if ( ! empty( $this->total_blog_count ) && ! empty( $this->pag_num ) ) {
-			$this->pag_links = paginate_links( array(
-				'base'      => add_query_arg( $page_arg, '%#%' ),
-				'format'    => '',
-				'total'     => ceil( (int) $this->total_blog_count / (int) $this->pag_num ),
-				'current'   => (int) $this->pag_page,
-				'prev_text' => _x( '&larr;', 'Blog pagination previous text', 'buddypress' ),
-				'next_text' => _x( '&rarr;', 'Blog pagination next text',     'buddypress' ),
-				'mid_size'  => 1
-			) );
-		}
+		$this->set_loop( $args );
 	}
 
 	/**
-	 * Whether there are blogs available in the loop.
-	 *
-	 * @see bp_has_blogs()
+	 * Get Blogs.
 	 *
-	 * @return bool True if there are items in the loop, otherwise false.
-	 */
-	public function has_blogs() {
-		return (bool) ! empty( $this->blog_count );
-	}
-
-	/**
-	 * Set up the next blog and iterate index.
-	 *
-	 * @return object The next blog to iterate over.
-	 */
-	public function next_blog() {
-		$this->current_blog++;
-		$this->blog = $this->blogs[ $this->current_blog ];
-
-		return $this->blog;
-	}
-
-	/**
-	 * Rewind the blogs and reset blog index.
-	 */
-	public function rewind_blogs() {
-		$this->current_blog = -1;
-		if ( $this->blog_count > 0 ) {
-			$this->blog = $this->blogs[0];
-		}
-	}
-
-	/**
-	 * Whether there are blogs left in the loop to iterate over.
+	 * @since BuddyPress (2.3.0)
 	 *
-	 * This method is used by {@link bp_blogs()} as part of the while loop
-	 * that controls iteration inside the blogs loop, eg:
-	 *     while ( bp_blogs() ) { ...
-	 *
-	 * @see bp_blogs()
-	 *
-	 * @return bool True if there are more blogs to show, otherwise false.
+	 * @access public
 	 */
-	public function blogs() {
-		if ( ( $this->current_blog + 1 ) < $this->blog_count ) {
-			return true;
-		} elseif ( ( $this->current_blog + 1 ) === $this->blog_count ) {
+	public function get_items() {
+		// Backwards compatibility support for blogs by first letter
+		if ( ! empty( $_REQUEST['letter'] ) ) {
+			$blogs = BP_Blogs_Blog::get_by_letter( $_REQUEST['letter'], $this->per_page, $this->page );
 
-			/**
-			 * Fires right before the rewinding of blogs listing after all are shown.
-			 *
-			 * @since BuddyPress (1.5.0)
-			 */
-			do_action( 'blog_loop_end' );
-			// Do some cleaning up after the loop
-			$this->rewind_blogs();
+		// Typical blogs query
+		} else {
+			$blogs = bp_blogs_get_blogs( $this->args['params'] );
 		}
 
-		$this->in_the_loop = false;
-		return false;
-	}
-
-	/**
-	 * Set up the current blog inside the loop.
-	 *
-	 * Used by {@link bp_the_blog()} to set up the current blog data while
-	 * looping, so that template tags used during that iteration make
-	 * reference to the current blog.
-	 *
-	 * @see bp_the_blog()
-	 */
-	public function the_blog() {
-
-		$this->in_the_loop = true;
-		$this->blog        = $this->next_blog();
-
-		// loop has just started
-		if ( 0 === $this->current_blog ) {
-
-			/**
-			 * Fires if on the first blog in the loop.
-			 *
-			 * @since BuddyPress (1.5.0)
-			 */
-			do_action( 'blog_loop_start' );
-		}
+		return $blogs;
 	}
 }
 
@@ -349,7 +192,7 @@ class BP_Blogs_Template {
 function bp_rewind_blogs() {
 	global $blogs_template;
 
-	$blogs_template->rewind_blogs();
+	$blogs_template->rewind_items();
 }
 
 /**
@@ -428,7 +271,7 @@ function bp_has_blogs( $args = '' ) {
 	 * @param BP_Blogs_Template $blogs_template Current blogs template object.
 	 * @param array             $r              Parsed arguments used in blogs template query.
 	 */
-	return apply_filters( 'bp_has_blogs', $blogs_template->has_blogs(), $blogs_template, $r );
+	return apply_filters( 'bp_has_blogs', $blogs_template->has_items(), $blogs_template, $r );
 }
 
 /**
@@ -441,7 +284,7 @@ function bp_has_blogs( $args = '' ) {
 function bp_blogs() {
 	global $blogs_template;
 
-	return $blogs_template->blogs();
+	return $blogs_template->items();
 }
 
 /**
@@ -454,7 +297,7 @@ function bp_blogs() {
 function bp_the_blog() {
 	global $blogs_template;
 
-	return $blogs_template->the_blog();
+	return $blogs_template->the_item();
 }
 
 /**
@@ -465,9 +308,9 @@ function bp_the_blog() {
 function bp_blogs_pagination_count() {
 	global $blogs_template;
 
-	$start_num = intval( ( $blogs_template->pag_page - 1 ) * $blogs_template->pag_num ) + 1;
+	$start_num = intval( ( $blogs_template->page - 1 ) * $blogs_template->per_page ) + 1;
 	$from_num  = bp_core_number_format( $start_num );
-	$to_num    = bp_core_number_format( ( $start_num + ( $blogs_template->pag_num - 1 ) > $blogs_template->total_blog_count ) ? $blogs_template->total_blog_count : $start_num + ( $blogs_template->pag_num - 1 ) );
+	$to_num    = bp_core_number_format( ( $start_num + ( $blogs_template->per_page - 1 ) > $blogs_template->total_blog_count ) ? $blogs_template->total_blog_count : $start_num + ( $blogs_template->per_page - 1 ) );
 	$total     = bp_core_number_format( $blogs_template->total_blog_count );
 
 	echo sprintf( _n( 'Viewing 1 site', 'Viewing %1$s - %2$s of %3$s sites', $total, 'buddypress' ), $from_num, $to_num, $total );
diff --git src/bp-core/bp-core-classes.php src/bp-core/bp-core-classes.php
index bc3afdb..19ed833 100644
--- src/bp-core/bp-core-classes.php
+++ src/bp-core/bp-core-classes.php
@@ -2754,3 +2754,240 @@ class BP_Members_Suggestions extends BP_Suggestions {
 		return apply_filters( 'bp_members_suggestions_get_suggestions', $results, $this );
 	}
 }
+
+/**
+ * Base class for components template loops.
+ *
+ * @since 2.3.0
+ */
+abstract class BP_Template_Loop {
+
+	/**
+	 * Default arguments common to all components loop.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	protected $default_args = array(
+		'labels' => array(
+			'item'              => 'item',
+			'item_plural'       => 'items',
+			'item_total_count'  => 'total_item_count',
+			'item_count'        => 'item_count',
+			'current_item'      => 'current_item',
+		),
+		'params' => array(
+			'page_arg'          => 'page',
+			'page'              => 1,
+			'per_page'          => 20,
+			'max'               => 0,
+		)
+	);
+
+	/**
+	 * Holds the arguments to customize the loop labels and the component's items query.
+	 *
+	 * This includes `$default_args`, as well as the user-supplied values.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	protected $args = array();
+
+	/**
+	 * A flag for whether the loop is currently being iterated.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 * @var bool
+	 */
+	public $in_the_loop;
+
+	/**
+	 * An HTML string containing pagination links.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 * @var string
+	 */
+	public $pag_links;
+
+	/**
+	 * Set the loop
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 * @param array $args component's loop arguments
+	 */
+	public function set_loop( $args = array() ) {
+		$this->args = wp_parse_args( $args, $this->default_args );
+		$this->{$this->args['labels']['current_item']} = -1;
+
+		// Setting loop parameters
+		if ( ! empty( $this->args['params'] ) ) {
+			foreach ( (array) $this->args['params'] as $key => $value ) {
+				$this->{$key} = $value;
+			}
+
+			if ( ! empty( $_REQUEST[ $this->page_arg ] ) ) {
+				$this->page = intval( $_REQUEST[ $this->page_arg ] );
+			}
+
+			if ( ! empty( $_REQUEST['num'] ) ) {
+				$this->per_page = intval( $_REQUEST['num'] );
+			}
+		}
+
+		// Setup the Items to loop through
+		$items = $this->get_items();
+
+		if ( ! isset( $items[ $this->args['labels']['item_plural'] ] ) || ! isset( $items['total'] ) ) {
+			return false;
+		}
+
+		// Set items
+		$this->{$this->args['labels']['item_plural']} = $items[ $this->args['labels']['item_plural'] ];
+
+		if ( empty( $this->{$this->args['labels']['item_plural']} ) ) {
+			$this->{$this->args['labels']['item_count']}       = 0;
+			$this->{$this->args['labels']['item_total_count']} = 0;
+
+		} else {
+			// Set the total items count
+			$this->{$this->args['labels']['item_total_count']} = (int) $items['total'];
+
+			if ( ! empty( $this->max ) && ( $this->max < (int) $items['total'] ) ) {
+				$this->{$this->args['labels']['item_total_count']} = (int) $this->max;
+			}
+
+			// Set current items count
+			$this->{$this->args['labels']['item_count']} = count( $this->{$this->args['labels']['item_plural']} );
+
+			if ( ! empty( $this->max ) && ( $this->max < (int) $this->{$this->args['labels']['item_count']} ) ) {
+				$this->{$this->args['labels']['item_count']} = (int) $this->max;
+			}
+		}
+
+		if ( ! empty( $this->{$this->args['labels']['item_total_count']} ) && ! empty( $this->per_page ) && ! empty( $this->page ) ) {
+			$this->set_pag_links();
+		}
+	}
+
+	/**
+	 * Get the component's items
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 */
+	abstract public function get_items();
+
+	/**
+	 * Set the pagination links
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 */
+	public function set_pag_links() {
+		$this->pag_links = paginate_links( array(
+			'base'      => add_query_arg( $this->page_arg, '%#%' ),
+			'format'    => '',
+			'total'     => ceil( (int) $this->{$this->args['labels']['item_total_count']} / (int) $this->per_page ),
+			'current'   => (int) $this->page,
+			'prev_text' => _x( '&larr;', 'pagination previous text', 'buddypress' ),
+			'next_text' => _x( '&rarr;', 'pagination next text',     'buddypress' ),
+			'mid_size'  => 1,
+		) );
+	}
+
+	/**
+	 * Whether there are Items available in the loop.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @return bool True if there are items in the loop, otherwise false.
+	 */
+	public function has_items() {
+		if ( ! empty( $this->{$this->args['labels']['item_count']} ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Set up the next item and iterate index.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @return object The next item to iterate over.
+	 */
+	public function next_item() {
+
+		$this->{$this->args['labels']['current_item']}++;
+
+		$this->{$this->args['labels']['item']} = $this->{$this->args['labels']['item_plural']}[ $this->{$this->args['labels']['current_item']} ];
+
+		return $this->{$this->args['labels']['item']};
+	}
+
+	/**
+	 * Rewind the items and reset items index.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 */
+	public function rewind_items() {
+
+		$this->{$this->args['labels']['current_item']} = -1;
+
+		if ( $this->{$this->args['labels']['item_count']} > 0 ) {
+			$this->{$this->args['labels']['item']} = $this->{$this->args['labels']['item_plural']}[0];
+		}
+	}
+
+	/**
+	 * Whether there are items left in the loop to iterate over.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @return bool True if there are more items to show,
+	 *         otherwise false.
+	 */
+	public function items() {
+
+		if ( $this->{$this->args['labels']['current_item']} + 1 < $this->{$this->args['labels']['item_count']} ) {
+			return true;
+
+		} elseif ( $this->{$this->args['labels']['current_item']} + 1 == $this->{$this->args['labels']['item_count']} ) {
+			do_action( $this->args['labels']['item'] . '_loop_end' );
+
+			$this->rewind_items();
+		}
+
+		$this->in_the_loop = false;
+		return false;
+	}
+
+	/**
+	 * Set up the current item inside the loop.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 */
+	public function the_item() {
+		$this->in_the_loop  = true;
+		$this->{$this->args['labels']['item']} = $this->next_item();
+
+		// loop has just started
+		if ( 0 === $this->{$this->args['labels']['current_item']} ) {
+			do_action( $this->args['labels']['item'] . '_loop_start' );
+		}
+	}
+}
diff --git tests/phpunit/testcases/blogs/class-bp-blogs-template.php tests/phpunit/testcases/blogs/class-bp-blogs-template.php
index e69de29..df90241 100644
--- tests/phpunit/testcases/blogs/class-bp-blogs-template.php
+++ tests/phpunit/testcases/blogs/class-bp-blogs-template.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * @group BP_Blogs_Template
+ */
+class BP_Tests_BP_Blogs_Template_TestCases extends BP_UnitTestCase {
+
+	public function setUp() {
+		parent::setUp();
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+	}
+
+	/**
+	 * @group BP_Blogs_Template
+	 */
+	public function test_bp_blogs_template() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		// Parse arguments
+		$r = array(
+			'type'              => 'active',
+			'page_arg'          => 'bpage',
+			'page'              => 1,
+			'per_page'          => 1,
+			'max'               => false,
+			'user_id'           => 0,
+			'include_blog_ids'  => false,
+			'search_terms'      => '',
+			'update_meta_cache' => true
+		);
+
+		// Get the blogs
+		$blogs_empty = new BP_Blogs_Template( $r['type'], $r['page'], $r['per_page'], $r['max'], $r['user_id'], $r['search_terms'], $r['page_arg'], $r['update_meta_cache'], $r['include_blog_ids'] );
+
+		$this->assertEquals( 0, count( $blogs_empty->blogs ) );
+		$this->assertEquals( 0, $blogs_empty->blog_count );
+		$this->assertEquals( 0, $blogs_empty->total_blog_count );
+
+		$blogs = array(
+			'test_loop1' => array( 'blogname' => 'TestLoop1', 'blogdescription' => 'TestLoop1 Description', 'public' => 1 ),
+			'test_loop2' => array( 'blogname' => 'TestLoop2', 'blogdescription' => 'TestLoop2 Description', 'public' => 1 ),
+		);
+
+		$users = array(
+			'test_loop1' => 0,
+			'test_loop2' => 0,
+		);
+
+		$test_loop = array();
+		$expected_loop = array();
+		$r['include_blog_ids'] = array();
+
+		foreach( $blogs as $path => $meta ) {
+			$users[ $path ] = $this->factory->user->create( array( 'role' => '' ) );
+
+			$blogs[ $path ]['id'] = $this->factory->blog->create( array(
+				'path'    => '/' . $path,
+				'meta'    => $meta,
+				'user_id' => $users[ $path ],
+			) );
+
+			$r['include_blog_ids'][] = $blogs[ $path ]['id'];
+
+			$expected_loop[] = array(
+				'blog_id'       => $blogs[ $path ]['id'],
+				'admin_user_id' => $users[ $path ],
+				'name'          => $meta['blogname'],
+				'description'   => $meta['blogdescription'],
+			);
+		}
+
+		bp_blogs_record_existing_blogs();
+
+		$blogs_q1 = new BP_Blogs_Template( $r['type'], $r['page'], $r['per_page'], $r['max'], $r['user_id'], $r['search_terms'], $r['page_arg'], $r['update_meta_cache'], $r['include_blog_ids'] );
+		$the_blogs = $blogs_q1->blogs;
+
+		$this->assertEquals( 1, count( $blogs_q1->blogs ) );
+		$this->assertEquals( 1, $blogs_q1->blog_count );
+		$this->assertEquals( 2, $blogs_q1->total_blog_count );
+
+		preg_match( '/\?bpage=(.*)\'/', $blogs_q1->pag_links, $matches );
+		$r['page'] = intval( $matches[1] );
+
+		$blogs_q2 = new BP_Blogs_Template( $r['type'], $r['page'], $r['per_page'], $r['max'], $r['user_id'], $r['search_terms'], $r['page_arg'], $r['update_meta_cache'], $r['include_blog_ids'] );
+		$the_blogs[] = $blogs_q2->blogs[0];
+
+		foreach ( $the_blogs as $blog ) {
+			$test_loop[] = array(
+				'blog_id'       => $blog->blog_id,
+				'admin_user_id' => $blog->admin_user_id,
+				'name'          => $blog->name,
+				'description'   => $blog->description,
+			);
+		}
+
+		$this->assertEqualSets( $expected_loop, $test_loop );
+	}
+}
