diff --git src/bp-blogs/bp-blogs-template.php src/bp-blogs/bp-blogs-template.php
index 21fdb7c..fadb4aa 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.
-	 *
-	 * @access public
-	 * @var int
-	 */
-	public $blog_count = 0;
-
-	/**
-	 * Array of blogs located by the query..
+	 * @since BuddyPress (2.3.0)
 	 *
-	 * @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,149 +150,49 @@ 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 ) {
+		$params = 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,
-				'add_args'  => array(),
-			) );
-		}
-	}
-
-	/**
-	 * Whether there are blogs available in the loop.
-	 *
-	 * @see bp_has_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];
-		}
+		$this->set_loop( $params );
 	}
 
 	/**
-	 * Whether there are blogs left in the loop to iterate over.
-	 *
-	 * 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() ) { ...
+	 * Get Blogs.
 	 *
-	 * @see bp_blogs()
+	 * @since BuddyPress (2.3.0)
 	 *
-	 * @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;
+		return $blogs;
 	}
 
 	/**
-	 * 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.
+	 * Rewind Blogs is an alias of rewind_items()
 	 *
-	 * @see bp_the_blog()
+	 * @access public
 	 */
-	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' );
-		}
+	public function rewind_blogs() {
+		_doing_it_wrong( __FUNCTION__, __( 'BP_Blogs_Template->rewind_blogs() should not be used directly, please use bp_rewind_blogs()', 'buddypress' ), '2.3.0' );
+		$this->rewind_items();
 	}
 }
 
@@ -350,7 +202,7 @@ class BP_Blogs_Template {
 function bp_rewind_blogs() {
 	global $blogs_template;
 
-	$blogs_template->rewind_blogs();
+	$blogs_template->rewind_items();
 }
 
 /**
@@ -429,7 +281,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 );
 }
 
 /**
@@ -442,7 +294,7 @@ function bp_has_blogs( $args = '' ) {
 function bp_blogs() {
 	global $blogs_template;
 
-	return $blogs_template->blogs();
+	return $blogs_template->items();
 }
 
 /**
@@ -455,7 +307,7 @@ function bp_blogs() {
 function bp_the_blog() {
 	global $blogs_template;
 
-	return $blogs_template->the_blog();
+	return $blogs_template->the_item();
 }
 
 /**
@@ -466,9 +318,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 f47f0b8..e4ea96e 100644
--- src/bp-core/bp-core-classes.php
+++ src/bp-core/bp-core-classes.php
@@ -2978,4 +2978,289 @@ abstract class BP_Recursive_Query {
          * @return bool
          */
         abstract protected function is_first_order_clause( $query );
-}
\ No newline at end of file
+}
+
+/**
+ * Base class for components template loops.
+ *
+ * Each public method can be overriden from a custom template class
+ * For instance, you can customize the default paginate links within
+ * your class.
+ * @see  BP_Groups_Template->set_pag_links() for an example of use
+ *
+ * the abstract method get_items() is required. This is where you define
+ * an associative array having your custom 'item_plural' and 'total' keys
+ *
+ * @since 2.3.0
+ */
+abstract class BP_Template_Loop {
+
+	/**
+	 * Default arguments common to all components loop.
+	 *
+	 * You can add the custom item default args within the custom template loop class
+	 * @see  BP_Groups_Template->default_args for an example of use.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access protected
+	 * @var array $default_args {
+	 *     Array describing the default argument for the item loop.
+	 *     @type array $labels list of custom var names for the loop
+	 *           - @type string $item the singular item var name
+	 *           for instance 'group' for the groups loop
+	 *           - @type string $item_plural the plural item var name
+	 *           for instance 'groups' for the groups loop
+	 *           - @type string $item_total_count the total count var name
+	 *           for instance 'total_group_count' for the groups loop
+	 *           - @type string $item_count the current items count var name
+	 *           for instance 'group_count' for the groups loop
+	 *           - @type string $current_item the current item var name
+	 *           for instance 'current_group' for the groups loop
+	 *     @type array $params list of default arguments for the loop
+	 *           - @type string $page_arg the var name to get the page value.
+	 *           for instance 'grpage' for the groups loop
+	 *           - @type int    $page default value for the page argument
+	 *           - @type int    $per_page default value for the per page argument
+	 *           - @type int    $max default value for the max argument
+	 * }
+	 */
+	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
+	 *
+	 * This method needs to be set within the custom Template class
+	 * @see  BP_Blogs_Template->get_items() for an example of use
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 */
+	abstract public function get_items();
+
+	/**
+	 * Set the pagination links
+	 *
+	 * Each item loop can override this and sets its own pagination links
+	 * within its custom template class.
+	 * @see  BP_Groups_Template->set_pag_links() for an example of use
+	 *
+	 * @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,
+			'add_args'  => array(),
+		) );
+	}
+
+	/**
+	 * 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']} ) {
+			/**
+			 * Inform when the item loop ends. Dynamically built using the item name
+			 * For instance the groups loops is using 'group' as its item name, as
+			 * a result the action will be 'group_loop_end'
+			 */
+			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']} ) {
+			/**
+			 * Inform when the item loop starts. Dynamically built using the item name
+			 * For instance the groups loops is using 'group' as its item name, as
+			 * a result the action will be 'group_loop_start'
+			 */
+			do_action( $this->args['labels']['item'] . '_loop_start' );
+		}
+	}
+}
diff --git src/bp-groups/bp-groups-template.php src/bp-groups/bp-groups-template.php
index a2e546b..6dd7bcf 100644
--- src/bp-groups/bp-groups-template.php
+++ src/bp-groups/bp-groups-template.php
@@ -63,109 +63,36 @@ function bp_groups_directory_permalink() {
 	function bp_get_groups_directory_permalink() {
 		return apply_filters( 'bp_get_groups_directory_permalink', trailingslashit( bp_get_root_domain() . '/' . bp_get_groups_root_slug() ) );
 	}
-
 /**
- * The main Groups template loop class.
+ * The main blog template loop class.
  *
- * Responsible for loading a group of groups into a loop for display.
+ * Responsible for loading a group of blogs into a loop for display.
  */
-class BP_Groups_Template {
+class BP_Groups_Template extends BP_Template_Loop {
 
 	/**
-	 * The loop iterator.
+	 * Default arguments for Blogs loop.
 	 *
-	 * @access public
-	 * @var int
-	 */
-	var $current_group = -1;
-
-	/**
-	 * The number of groups returned by the paged query.
-	 *
-	 * @access public
-	 * @var int
-	 */
-	var $group_count;
-
-	/**
-	 * Array of groups located by the query.
+	 * @since BuddyPress (2.3.0)
 	 *
-	 * @access public
+	 * @access protected
 	 * @var array
 	 */
-	var $groups;
-
-	/**
-	 * The group object currently being iterated on.
-	 *
-	 * @access public
-	 * @var object
-	 */
-	var $group;
-
-	/**
-	 * A flag for whether the loop is currently being iterated.
-	 *
-	 * @access public
-	 * @var bool
-	 */
-	var $in_the_loop;
-
-	/**
-	 * The page number being requested.
-	 *
-	 * @access public
-	 * @var public
-	 */
-	var $pag_page;
-
-	/**
-	 * The number of items being requested per page.
-	 *
-	 * @access public
-	 * @var public
-	 */
-	var $pag_num;
-
-	/**
-	 * An HTML string containing pagination links.
-	 *
-	 * @access public
-	 * @var string
-	 */
-	var $pag_links;
-
-	/**
-	 * The total number of groups matching the query parameters.
-	 *
-	 * @access public
-	 * @var int
-	 */
-	var $total_group_count;
-
-	/**
-	 * Whether the template loop is for a single group page.
-	 *
-	 * @access public
-	 * @var bool
-	 */
-	var $single_group = false;
-
-	/**
-	 * Field to sort by.
-	 *
-	 * @access public
-	 * @var string
-	 */
-	var $sort_by;
-
-	/**
-	 * Sort order.
-	 *
-	 * @access public
-	 * @var string
-	 */
-	var $order;
+	protected $default_args = array(
+		'labels' => array(
+			'item'              => 'group',
+			'item_plural'       => 'groups',
+			'item_total_count'  => 'total_group_count',
+			'item_count'        => 'group_count',
+			'current_item'      => 'current_group',
+		),
+		'params' => array(
+			'page_arg'          => 'grpage',
+			'page'              => 1,
+			'per_page'          => 20,
+			'max'               => 0,
+		)
+	);
 
 	/**
 	 * Constructor method.
@@ -180,8 +107,7 @@ class BP_Groups_Template {
 	 *     @type int $page Default: 1.
 	 * }
 	 */
-	function __construct( $args = array() ){
-
+	public function __construct( $args = array() ) {
 		// Backward compatibility with old method of passing arguments
 		if ( ! is_array( $args ) || func_num_args() > 1 ) {
 			_deprecated_argument( __METHOD__, '1.7', sprintf( __( 'Arguments passed to %1$s should be in an associative array. See the inline documentation at %2$s for more details.', 'buddypress' ), __METHOD__, __FILE__ ) );
@@ -206,34 +132,43 @@ class BP_Groups_Template {
 		}
 
 		$defaults = array(
-			'type'            => 'active',
-			'page'            => 1,
-			'per_page'        => 20,
-			'max'             => false,
-			'show_hidden'     => false,
-			'page_arg'        => 'grpage',
-			'user_id'         => 0,
-			'slug'            => false,
-			'include'         => false,
-			'exclude'         => false,
-			'search_terms'    => '',
-			'meta_query'      => false,
-			'populate_extras' => true,
+			'type'              => 'active',
+			'page'              => 1,
+			'per_page'          => 20,
+			'max'               => false,
+			'show_hidden'       => false,
+			'page_arg'          => 'grpage',
+			'user_id'           => 0,
+			'slug'              => false,
+			'include'           => false,
+			'exclude'           => false,
+			'search_terms'      => '',
+			'meta_query'        => false,
+			'populate_extras'   => true,
 			'update_meta_cache' => true,
 		);
 
 		$r = wp_parse_args( $args, $defaults );
-		extract( $r );
 
-		$this->pag_page = isset( $_REQUEST[$page_arg] ) ? intval( $_REQUEST[$page_arg] ) : $page;
-		$this->pag_num  = isset( $_REQUEST['num'] ) ? intval( $_REQUEST['num'] ) : $per_page;
+		$this->set_loop( array( 'params' => $r ) );
+	}
 
-		if ( bp_current_user_can( 'bp_moderate' ) || ( is_user_logged_in() && $user_id == bp_loggedin_user_id() ) )
-			$show_hidden = true;
+	/**
+	 * Get Groups.
+	 *
+	 * @since BuddyPress (2.3.0)
+	 *
+	 * @access public
+	 */
+	public function get_items() {
+		if ( bp_current_user_can( 'bp_moderate' ) || ( is_user_logged_in() && $this->user_id == bp_loggedin_user_id() ) ) {
+			$this->args['params']['show_hidden'] = true;
+		}
+
+		if ( 'invites' == $this->type ) {
+			return groups_get_invites_for_user( $this->user_id, $this->per_page, $this->page, $this->exclude );
 
-		if ( 'invites' == $type ) {
-			$this->groups = groups_get_invites_for_user( $user_id, $this->pag_num, $this->pag_page, $exclude );
-		} else if ( 'single-group' == $type ) {
+		} else if ( 'single-group' == $this->type ) {
 			$this->single_group = true;
 
 			if ( groups_get_current_group() ) {
@@ -241,182 +176,60 @@ class BP_Groups_Template {
 
 			} else {
 				$group = groups_get_group( array(
-					'group_id'        => BP_Groups_Group::get_id_from_slug( $r['slug'] ),
-					'populate_extras' => $r['populate_extras'],
+					'group_id'        => BP_Groups_Group::get_id_from_slug( $this->slug ),
+					'populate_extras' => $this->populate_extras,
 				) );
 			}
 
-			// backwards compatibility - the 'group_id' variable is not part of the
-			// BP_Groups_Group object, but we add it here for devs doing checks against it
-			//
-			// @see https://buddypress.trac.wordpress.org/changeset/3540
-			//
-			// this is subject to removal in a future release; devs should check against
-			// $group->id instead
-			$group->group_id = $group->id;
-
-			$this->groups = array( $group );
-
-		} else {
-			$this->groups = groups_get_groups( array(
-				'type'              => $type,
-				'order'             => $order,
-				'orderby'           => $orderby,
-				'per_page'          => $this->pag_num,
-				'page'              => $this->pag_page,
-				'user_id'           => $user_id,
-				'search_terms'      => $search_terms,
-				'meta_query'        => $meta_query,
-				'include'           => $include,
-				'exclude'           => $exclude,
-				'populate_extras'   => $populate_extras,
-				'update_meta_cache' => $update_meta_cache,
-				'show_hidden'       => $show_hidden
-			) );
-		}
-
-		if ( 'invites' == $type ) {
-			$this->total_group_count = (int) $this->groups['total'];
-			$this->group_count       = (int) $this->groups['total'];
-			$this->groups            = $this->groups['groups'];
-		} else if ( 'single-group' == $type ) {
 			if ( empty( $group->id ) ) {
-				$this->total_group_count = 0;
-				$this->group_count       = 0;
-			} else {
-				$this->total_group_count = 1;
-				$this->group_count       = 1;
-			}
-		} else {
-			if ( empty( $max ) || $max >= (int) $this->groups['total'] ) {
-				$this->total_group_count = (int) $this->groups['total'];
-			} else {
-				$this->total_group_count = (int) $max;
-			}
-
-			$this->groups = $this->groups['groups'];
-
-			if ( !empty( $max ) ) {
-				if ( $max >= count( $this->groups ) ) {
-					$this->group_count = count( $this->groups );
-				} else {
-					$this->group_count = (int) $max;
-				}
-			} else {
-				$this->group_count = count( $this->groups );
-			}
-		}
-
-		// Build pagination links
-		if ( (int) $this->total_group_count && (int) $this->pag_num ) {
-			$pag_args = array(
-				$page_arg => '%#%'
-			);
-
-			if ( defined( 'DOING_AJAX' ) && true === (bool) DOING_AJAX ) {
-				$base = remove_query_arg( 's', wp_get_referer() );
+				return array( 'groups' => array(), 'total' => 0 );
 			} else {
-				$base = '';
+				return array( 'groups' => array( $group ), 'total' => 1 );
 			}
 
-			$add_args = array(
-				'num'     => $this->pag_num,
-				'sortby'  => $this->sort_by,
-				'order'   => $this->order,
-			);
-
-			if ( ! empty( $search_terms ) ) {
-				$add_args['s'] = urlencode( $search_terms );
-			}
-
-			$this->pag_links = paginate_links( array(
-				'base'      => add_query_arg( $pag_args, $base ),
-				'format'    => '',
-				'total'     => ceil( (int) $this->total_group_count / (int) $this->pag_num ),
-				'current'   => $this->pag_page,
-				'prev_text' => _x( '&larr;', 'Group pagination previous text', 'buddypress' ),
-				'next_text' => _x( '&rarr;', 'Group pagination next text', 'buddypress' ),
-				'mid_size'  => 1,
-				'add_args'  => $add_args,
-			) );
+		} else {
+			return groups_get_groups( $this->args['params'] );
 		}
 	}
 
 	/**
-	 * Whether there are groups available in the loop.
+	 * Set Groups pagination.
 	 *
-	 * @see bp_has_groups()
+	 * @since BuddyPress (2.3.0)
 	 *
-	 * @return bool True if there are items in the loop, otherwise false.
-	 */
-	function has_groups() {
-		if ( $this->group_count )
-			return true;
-
-		return false;
-	}
-
-	/**
-	 * Set up the next group and iterate index.
-	 *
-	 * @return object The next group to iterate over.
-	 */
-	function next_group() {
-		$this->current_group++;
-		$this->group = $this->groups[$this->current_group];
-
-		return $this->group;
-	}
-
-	/**
-	 * Rewind the groups and reset member index.
+	 * @access public
 	 */
-	function rewind_groups() {
-		$this->current_group = -1;
-		if ( $this->group_count > 0 ) {
-			$this->group = $this->groups[0];
-		}
-	}
+	public function set_pag_links() {
+		$pag_args = array(
+			$this->page_arg => '%#%'
+		);
 
-	/**
-	 * Whether there are groups left in the loop to iterate over.
-	 *
-	 * This method is used by {@link bp_groups()} as part of the while loop
-	 * that controls iteration inside the groups loop, eg:
-	 *     while ( bp_groups() ) { ...
-	 *
-	 * @see bp_groups()
-	 *
-	 * @return bool True if there are more groups to show, otherwise false.
-	 */
-	function groups() {
-		if ( $this->current_group + 1 < $this->group_count ) {
-			return true;
-		} elseif ( $this->current_group + 1 == $this->group_count ) {
-			do_action('group_loop_end');
-			// Do some cleaning up after the loop
-			$this->rewind_groups();
+		if ( defined( 'DOING_AJAX' ) && true === (bool) DOING_AJAX ) {
+			$base = remove_query_arg( 's', wp_get_referer() );
+		} else {
+			$base = '';
 		}
 
-		$this->in_the_loop = false;
-		return false;
-	}
+		$add_args = array(
+			'num'     => $this->per_page,
+			'sortby'  => $this->orderby,
+			'order'   => $this->order,
+		);
 
-	/**
-	 * Set up the current group inside the loop.
-	 *
-	 * Used by {@link bp_the_group()} to set up the current group data
-	 * while looping, so that template tags used during that iteration make
-	 * reference to the current member.
-	 *
-	 * @see bp_the_group()
-	 */
-	function the_group() {
-		$this->in_the_loop = true;
-		$this->group       = $this->next_group();
+		if ( ! empty( $this->search_terms ) ) {
+			$add_args['s'] = urlencode( $this->search_terms );
+		}
 
-		if ( 0 == $this->current_group ) // loop has just started
-			do_action('group_loop_start');
+		$this->pag_links = paginate_links( array(
+			'base'      => add_query_arg( $pag_args, $base ),
+			'format'    => '',
+			'total'     => ceil( (int) $this->{$this->args['labels']['item_total_count']} / (int) $this->per_page ),
+			'current'   => (int) $this->page,
+			'prev_text' => _x( '&larr;', 'Group pagination previous text', 'buddypress' ),
+			'next_text' => _x( '&rarr;', 'Group pagination next text', 'buddypress' ),
+			'mid_size'  => 1,
+			'add_args'  => $add_args,
+		) );
 	}
 }
 
@@ -539,7 +352,7 @@ function bp_has_groups( $args = '' ) {
 	) );
 
 	// Filter and return whether or not the groups loop has groups in it
-	return apply_filters( 'bp_has_groups', $groups_template->has_groups(), $groups_template, $r );
+	return apply_filters( 'bp_has_groups', $groups_template->has_items(), $groups_template, $r );
 }
 
 /**
@@ -549,7 +362,7 @@ function bp_has_groups( $args = '' ) {
  */
 function bp_groups() {
 	global $groups_template;
-	return $groups_template->groups();
+	return $groups_template->items();
 }
 
 /**
@@ -559,7 +372,7 @@ function bp_groups() {
  */
 function bp_the_group() {
 	global $groups_template;
-	return $groups_template->the_group();
+	return $groups_template->the_item();
 }
 
 /**
@@ -1575,9 +1388,9 @@ function bp_groups_pagination_count() {
 	function bp_get_groups_pagination_count() {
 		global $groups_template;
 
-		$start_num = intval( ( $groups_template->pag_page - 1 ) * $groups_template->pag_num ) + 1;
+		$start_num = intval( ( $groups_template->page - 1 ) * $groups_template->per_page ) + 1;
 		$from_num  = bp_core_number_format( $start_num );
-		$to_num    = bp_core_number_format( ( $start_num + ( $groups_template->pag_num - 1 ) > $groups_template->total_group_count ) ? $groups_template->total_group_count : $start_num + ( $groups_template->pag_num - 1 ) );
+		$to_num    = bp_core_number_format( ( $start_num + ( $groups_template->per_page - 1 ) > $groups_template->total_group_count ) ? $groups_template->total_group_count : $start_num + ( $groups_template->per_page - 1 ) );
 		$total     = bp_core_number_format( $groups_template->total_group_count );
 
 		return apply_filters( 'bp_get_groups_pagination_count', sprintf( _n( 'Viewing 1 group', 'Viewing %1$s - %2$s of %3$s groups', $total, 'buddypress' ), $from_num, $to_num, $total ), $from_num, $to_num, $total );
diff --git tests/phpunit/testcases/blogs/class-bp-blogs-template.php tests/phpunit/testcases/blogs/class-bp-blogs-template.php
index e69de29..61c2632 100644
--- tests/phpunit/testcases/blogs/class-bp-blogs-template.php
+++ tests/phpunit/testcases/blogs/class-bp-blogs-template.php
@@ -0,0 +1,109 @@
+<?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();
+	}
+
+	protected function blogs_get_args( $args = array() ) {
+		return wp_parse_args( $args, array(
+			'type'              => 'active',
+			'page_arg'          => 'bpage',
+			'page'              => 1,
+			'per_page'          => 20,
+			'max'               => false,
+			'user_id'           => 0,
+			'include_blog_ids'  => false,
+			'search_terms'      => '',
+			'update_meta_cache' => true
+		) );
+	}
+
+	/**
+	 * @group BP_Blogs_Template
+	 */
+	public function test_bp_blogs_template() {
+		if ( ! is_multisite() ) {
+			return;
+		}
+
+		// Default arguments
+		$r = $this->blogs_get_args();
+
+		// 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 ),
+		);
+
+		// Not setting user login or email generates errors in unit tests ?
+		$u_test_loop = array(
+			'test_loop1' => $this->factory->user->create( array( 'role' => '' ) ),
+			'test_loop2' => $this->factory->user->create( array( 'role' => '' ) ),
+		);
+
+		$test_loop = array();
+		$expected_loop = array();
+		$include_blog_ids = array();
+
+		foreach( $blogs as $path => $meta ) {
+
+			$blogs[ $path ]['id'] = $this->factory->blog->create( array(
+				'path'    => '/' . $path,
+				'meta'    => $meta,
+				'user_id' => $u_test_loop[ $path ],
+			) );
+
+			$include_blog_ids[] = $blogs[ $path ]['id'];
+
+			$expected_loop[] = array(
+				'blog_id'       => $blogs[ $path ]['id'],
+				'admin_user_id' => $u_test_loop[ $path ],
+				'name'          => $meta['blogname'],
+				'description'   => $meta['blogdescription'],
+			);
+		}
+
+		$r = $this->blogs_get_args( array(
+			'per_page'         => 1,
+			'include_blog_ids' => $include_blog_ids,
+		) );
+
+		$blogs_page_1 = 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_page_1->blogs;
+
+		$this->assertEquals( 1, count( $blogs_page_1->blogs ) );
+		$this->assertEquals( 1, $blogs_page_1->blog_count );
+		$this->assertEquals( 2, $blogs_page_1->total_blog_count );
+
+		preg_match( '/\?bpage=(.*)\'/', $blogs_page_1->pag_links, $matches );
+		$r['page'] = intval( $matches[1] );
+
+		$blogs_page_2 = 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_page_2->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 );
+	}
+}
diff --git tests/phpunit/testcases/groups/class-bp-groups-template.php tests/phpunit/testcases/groups/class-bp-groups-template.php
index e69de29..68d827f 100644
--- tests/phpunit/testcases/groups/class-bp-groups-template.php
+++ tests/phpunit/testcases/groups/class-bp-groups-template.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * @group BP_Groups_Template
+ */
+class BP_Tests_BP_Groups_Template_TestCases extends BP_UnitTestCase {
+
+	public function setUp() {
+		parent::setUp();
+	}
+
+	public function tearDown() {
+		parent::tearDown();
+	}
+
+	protected function groups_get_args( $args = array() ) {
+		return wp_parse_args( $args, array(
+			'type'              => '',
+			'order'             => 'DESC',
+			'orderby'           => 'last_activity',
+			'page'              => 1,
+			'per_page'          => 20,
+			'max'               => false,
+			'show_hidden'       => false,
+			'page_arg'          => 'grpage',
+			'user_id'           => 0,
+			'slug'              => false,
+			'search_terms'      => false,
+			'meta_query'        => false,
+			'include'           => false,
+			'exclude'           => false,
+			'populate_extras'   => true,
+			'update_meta_cache' => true,
+		) );
+	}
+
+	/**
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_directory() {
+
+		// Default arguments
+		$r = $this->groups_get_args();
+
+		$groups_empty = new BP_Groups_Template( $r );
+
+		$this->assertEquals( 0, count( $groups_empty->groups ) );
+		$this->assertEquals( 0, $groups_empty->group_count );
+		$this->assertEquals( 0, $groups_empty->total_group_count );
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1 ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u2, 'status' => 'private' ) );
+
+		// Add a user to one of the group
+		groups_join_group( $g1, $u2 );
+
+		// Update one of the group's last activity
+		groups_update_groupmeta( $g1, 'last_activity', date( 'Y-m-d H:i:s' , strtotime( '+1 hour' ) ) );
+
+		// Set pagination
+		$r = $this->groups_get_args( array( 'per_page' => 1 ) );
+
+		$groups_page_1 = new BP_Groups_Template( $r );
+		$the_groups = $groups_page_1->groups;
+
+		$this->assertEquals( 1, count( $groups_page_1->groups ) );
+		$this->assertEquals( 1, $groups_page_1->group_count );
+		$this->assertEquals( 2, $groups_page_1->total_group_count );
+
+		preg_match( '/\?grpage=(.*)\'/', $groups_page_1->pag_links, $matches );
+
+		// Set page
+		$r = $this->groups_get_args( array(
+			'per_page' => 1,
+			'page' => intval( $matches[1] )
+		) );
+
+		$groups_page_2 = new BP_Groups_Template( $r );
+		$the_groups[] = $groups_page_2->groups[0];
+		$expected = array();
+		$groups_loop = array();
+
+		foreach ( $the_groups as $group ) {
+
+			$groups_loop[] = array(
+				'id'                 => $group->id,
+				'creator_id'         => $group->creator_id,
+				'status'             => $group->status,
+				'total_member_count' => $group->total_member_count,
+				'last_activity'      => $group->last_activity,
+			);
+
+			$check_group = groups_get_group( array( 'group_id' => $group->id, 'populate_extras' => true ) );
+
+			$expected[] = array(
+				'id'                 => $check_group->id,
+				'creator_id'         => $check_group->creator_id,
+				'status'             => $check_group->status,
+				'total_member_count' => $check_group->total_member_count,
+				'last_activity'      => $check_group->last_activity,
+			);
+		}
+
+		$this->assertEqualSets( $groups_loop, $expected );
+	}
+
+	/**
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_invites() {
+
+		$u1 = $this->factory->user->create();
+		$u2 = $this->factory->user->create();
+		$g1 = $this->factory->group->create( array( 'creator_id' => $u1, 'status' => 'private' ) );
+		$g2 = $this->factory->group->create( array( 'creator_id' => $u1, 'status' => 'hidden' ) );
+
+		groups_invite_user( array(
+			'user_id'    => $u2,
+			'group_id'   => $g1,
+			'inviter_id' => $u1,
+		) );
+
+		groups_send_invites( $u1, $g1 );
+
+		groups_invite_user( array(
+			'user_id'    => $u2,
+			'group_id'   => $g2,
+			'inviter_id' => $u1,
+		) );
+
+		groups_send_invites( $u1, $g2 );
+
+		// Invites arguments
+		$r = $this->groups_get_args( array(
+			'type'        => 'invites',
+			'user_id'     => $u2,
+			'show_hidden' => true,
+		) );
+
+		$groups_invites = new BP_Groups_Template( $r );
+
+		$this->assertEquals( 2, $groups_invites->total_group_count );
+
+		$this->assertEqualSets( array( $g1, $g2 ), wp_list_pluck( $groups_invites->groups, 'id' ) );
+	}
+
+	/**
+	 * @group BP_Groups_Template
+	 */
+	public function test_bp_groups_template_single_group() {
+
+		$u = $this->factory->user->create();
+		$g = $this->factory->group->create( array( 'creator_id' => $u ) );
+
+		$group = groups_get_group( array( 'group_id' => $g ) );
+
+		// Single group arguments
+		$r = $this->groups_get_args( array(
+			'type' => 'single-group',
+			'slug' => $group->slug,
+		) );
+
+		$groups_single = new BP_Groups_Template( $r );
+
+		$this->assertEquals( 1, $groups_single->total_group_count );
+		$this->assertTrue( $groups_single->single_group );
+		$this->assertEquals( $g, $groups_single->groups[0]->id );
+	}
+}
