Index: /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/class-base.php
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/class-base.php	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/class-base.php	(revision 3545)
@@ -12,8 +12,9 @@
 	 */
 	static function load_routes() {
-		new Routes\Commit_Subscriptions();
 		new Routes\Internal_Stats();
 		new Routes\Plugin();
 		new Routes\Plugin_Translations();
+		new Routes\Plugin_Favorites();
+		new Routes\Commit_Subscriptions();
 		new Routes\Popular_Categories();
 		new Routes\Query_Plugins();
Index: /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-favorites.php
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-favorites.php	(revision 3545)
+++ /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-favorites.php	(revision 3545)
@@ -0,0 +1,56 @@
+<?php
+namespace WordPressdotorg\Plugin_Directory\API\Routes;
+use WordPressdotorg\Plugin_Directory\Plugin_Directory;
+use WordPressdotorg\Plugin_Directory\API\Base;
+use WordPressdotorg\Plugin_Directory\Tools;
+
+/**
+ * An API endpoint for favoriting a particular plugin.
+ *
+ * @package WordPressdotorg_Plugin_Directory
+ */
+class Plugin_Favorites extends Base {
+
+	public function __construct() {
+		register_rest_route( 'plugins/v1', '/plugin/(?P<plugin_slug>[^/]+)/favorite', array(
+			'methods'  => array( \WP_REST_Server::READABLE, \WP_REST_Server::CREATABLE ),
+			'callback' => array( $this, 'favorite' ),
+			'args' => array(
+				'plugin_slug' => array(
+					'validate_callback' => array( $this, 'validate_plugin_slug_callback' ),
+				),
+				'favorite' => array(
+					'validate_callback' => function( $bool ) { return is_numeric( $bool ); },
+				),
+				'unfavorite' => array(
+					'validate_callback' => function( $bool ) { return is_numeric( $bool ); },
+				),
+			),
+			'permission_callback' => 'is_user_logged_in'
+		) );
+	}
+
+	/**
+	 * Endpoint to favorite a plugin.
+	 *
+	 * @param \WP_REST_Request $request The Rest API Request.
+	 * @return bool True if the favoriting was successful.
+	 */
+	public function favorite( $request ) {
+		$location = get_permalink( Plugin_Directory::get_plugin_post( $request['plugin_slug'] ) );
+		header( "Location: $location" );
+
+		$result = array(
+			"location" => $location,
+		);
+
+		if ( ! isset( $request['favorite'] ) && ! isset( $request['unfavorite'] ) ) {
+			$result['error'] = 'Unknown Action';
+		}
+
+		$result['favorite'] = Tools::favorite_plugin( $request['plugin_slug'], get_current_user_id(), isset( $request['favorite'] ) );
+
+		return (object) $result;
+	}
+
+}
Index: /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php	(revision 3545)
@@ -462,3 +462,27 @@
 		return ent2ncr( htmlspecialchars_decode( htmlentities( $string, ENT_NOQUOTES, 'UTF-8' ), ENT_NOQUOTES ) );
 	}
+
+	/**
+	 * Generates a link to toggle a plugin favorites state.
+	 *
+	 * @param string $plugin_slug The plugin slug.
+	 * @param mixed  $user        The user to alter the favorite status for.
+	 * @return string URL to toggle status.
+	 */
+	public static function get_favourite_link( $plugin_slug, $user = 0 ) {
+		$post = Plugin_Directory::get_plugin_post( $plugin_slug );
+		if ( ! $post ) {
+			return false;
+		}
+
+		$favorited = Tools::favorited_plugin( $post, $user );
+
+		return add_query_arg(
+			array(
+				'_wpnonce' => wp_create_nonce( 'wp_rest' ),
+				( $favorited ? 'unfavorite' : 'favorite' ) => '1'
+			),
+			home_url( 'wp-json/plugins/v1/plugin/' . $post->post_name . '/favorite' )
+		);
+	}
 }
Index: /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-tools.php
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-tools.php	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-tools.php	(revision 3545)
@@ -257,4 +257,66 @@
 
 	/**
+	 * Determine if a plugin has been favorited by a user.
+	 *
+	 * @param string $plugin_slug The plugin to check.
+	 * @param mixed  $user        The user to check.
+	 * @return bool
+	 */
+	public static function favorited_plugin( $plugin_slug, $user = 0 ) {
+		$post = Plugin_Directory::get_plugin_post( $plugin_slug );
+		if ( ! $post ) {
+			return false;
+		}
+
+		$user = new WP_User( $user ?: get_current_user_id() );
+		if ( ! $user->exists() ) {
+			return false;
+		}
+
+		$users_favorites = get_user_meta( $user->ID, 'plugin_favorites', true ) ?: array();
+
+		return in_array( $post->post_name, $users_favorites, true );
+	}
+
+	/**
+	 * Favorite a plugin
+	 *
+	 * @param string $plugin_slug The plugin to favorite
+	 * @param mixed  $user        The user favorite
+	 * @param bool   $favorite    Whether it's a favorite, or unfavorite.
+	 * @return bool
+	 */
+	public static function favorite_plugin( $plugin_slug, $user = 0, $favorite = true ) {
+		$post = Plugin_Directory::get_plugin_post( $plugin_slug );
+		if ( ! $post ) {
+			return false;
+		}
+
+		$user = new WP_User( $user ?: get_current_user_id() );
+		if ( ! $user->exists() ) {
+			return false;
+		}
+
+		$users_favorites = get_user_meta( $user->ID, 'plugin_favorites', true ) ?: array();
+
+		$already_favorited = in_array( $post->post_name, $users_favorites, true );
+
+		if ( $favorite && $already_favorited ) {
+			return true;
+		} elseif ( $favorite ) {
+			// Add it
+			$users_favorites[] = $post->post_name;
+		} elseif ( ! $favorite && $already_favorited ) {
+			// Remove it
+			unset( $users_favorites[ array_search( $post->post_name, $users_favorites, true ) ] );
+		} else {
+			return true;
+		}
+
+		return update_user_meta( $user->ID, 'plugin_favorites', wp_slash( array_values( $users_favorites ) ) );
+
+	}
+
+	/**
 	 * Retrieve a list of users who are subscribed to plugin commits.
 	 *
Index: /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style-rtl.css
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style-rtl.css	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style-rtl.css	(revision 3545)
@@ -2159,6 +2159,26 @@
 }
 
-.single .type-plugin .plugin-header .plugin-download {
+.single .type-plugin .plugin-header .plugin-actions {
   float: left;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart {
+  color: gray;
+  line-heght: 3em;
+  font-size: 3em;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart.favorited, .single .type-plugin .plugin-header .plugin-favorite-heart:hover, .single .type-plugin .plugin-header .plugin-favorite-heart:focus {
+  color: red;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart:hover, .single .type-plugin .plugin-header .plugin-favorite-heart:focus {
+  text-decoration: none;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart:after {
+  content: "\f487";
+  font-family: dashicons;
+  vertical-align: top;
 }
 
Index: /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style.css
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style.css	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/css/style.css	(revision 3545)
@@ -2159,6 +2159,27 @@
 }
 
-.single .type-plugin .plugin-header .plugin-download {
+.single .type-plugin .plugin-header .plugin-actions {
   float: right;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart {
+  /* FIX ME */
+  color: gray;
+  line-heght: 3em;
+  font-size: 3em;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart.favorited, .single .type-plugin .plugin-header .plugin-favorite-heart:hover, .single .type-plugin .plugin-header .plugin-favorite-heart:focus {
+  color: red;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart:hover, .single .type-plugin .plugin-header .plugin-favorite-heart:focus {
+  text-decoration: none;
+}
+
+.single .type-plugin .plugin-header .plugin-favorite-heart:after {
+  content: "\f487";
+  font-family: dashicons;
+  vertical-align: top;
 }
 
Index: /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/sass/site/primary/_plugin-single.scss
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/sass/site/primary/_plugin-single.scss	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/sass/site/primary/_plugin-single.scss	(revision 3545)
@@ -47,6 +47,31 @@
 		}
 
-		.plugin-download {
+		.plugin-actions {
 			float: right;
+		}
+
+		.plugin-favorite-heart {
+			/* FIX ME */
+			color: gray;
+			line-heght: 3em;
+			font-size: 3em;
+
+			&.favorited,
+			&:hover,
+			&:focus {
+				color: red;
+			}
+
+			&:hover,
+			&:focus {
+				text-decoration: none;
+			}
+
+			&:after {
+				content: "\f487";
+				font-family: dashicons;
+				vertical-align: top;
+			}
+			
 		}
 
Index: /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/template-parts/plugin-single.php
===================================================================
--- /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/template-parts/plugin-single.php	(revision 3544)
+++ /sites/trunk/wordpress.org/public_html/wp-content/themes/pub/wporg-plugins/template-parts/plugin-single.php	(revision 3545)
@@ -11,4 +11,5 @@
 use WordPressdotorg\Plugin_Directory\Plugin_Directory;
 use WordPressdotorg\Plugin_Directory\Template;
+use WordPressdotorg\Plugin_Directory\Tools;
 
 $content = Plugin_Directory::instance()->split_post_content_into_pages( get_the_content() );
@@ -33,7 +34,17 @@
 		</div>
 
-		<a class="plugin-download button download-button button-large" href="<?php echo esc_url( Template::download_link() ); ?>" itemprop="downloadUrl"><?php _e( 'Download', 'wporg-plugins' ); ?></a>
-		<meta itemprop="softwareVersion" content="<?php echo esc_attr( get_post_meta( get_the_ID(), 'version', true ) ); ?>">
-		<meta itemprop="fileFormat" content="application/zip">
+		<div class="plugin-actions">
+
+			<?php
+			if ( is_user_logged_in() ) {
+				$url = Template::get_favourite_link( $post );
+				echo '<a href="' . esc_url( $url ) . '" class="plugin-favorite-heart' . (Tools::favorited_plugin( $post ) ? ' favorited' : '' ) . '" onclick="jQuery(this).toggleClass(\'favorited\')"></a>';
+			}
+			?>
+
+			<a class="plugin-download button download-button button-large" href="<?php echo esc_url( Template::download_link() ); ?>" itemprop="downloadUrl"><?php _e( 'Download', 'wporg-plugins' ); ?></a>
+			<meta itemprop="softwareVersion" content="<?php echo esc_attr( get_post_meta( get_the_ID(), 'version', true ) ); ?>">
+			<meta itemprop="fileFormat" content="application/zip">
+		</div>
 
 		<?php the_title( '<h1 class="plugin-title">', '</h1>' ); ?>
