diff --git a/locales/en.yml b/locales/en.yml
index db7ad786b9..0a05393227 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -98,7 +98,9 @@ common/views/components/nav.vue:
   feedback: "Feedback"
 
 common/views/components/note-menu.vue:
+  favorite: "Favorite this note"
   pin: "Pin to profile page"
+  remote: "Show on origin"
 
 common/views/components/poll.vue:
   vote-to: "Vote for '{}'"
diff --git a/locales/fr.yml b/locales/fr.yml
index 09e5b3878c..e640c4883c 100644
--- a/locales/fr.yml
+++ b/locales/fr.yml
@@ -98,7 +98,9 @@ common/views/components/nav.vue:
   feedback: "フィードバック"
 
 common/views/components/note-menu.vue:
+  favorite: "Favorite this note"
   pin: "Épingler sur votre profile"
+  remote: "投稿元で見る"
 
 common/views/components/poll.vue:
   vote-to: "Voter pour '{}'"
diff --git a/locales/ja.yml b/locales/ja.yml
index 2401bf1fc1..3d023281cd 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -98,7 +98,9 @@ common/views/components/nav.vue:
   feedback: "フィードバック"
 
 common/views/components/note-menu.vue:
+  favorite: "お気に入り"
   pin: "ピン留め"
+  remote: "投稿元で見る"
 
 common/views/components/poll.vue:
   vote-to: "「{}」に投票する"
diff --git a/src/client/app/common/views/components/note-menu.vue b/src/client/app/common/views/components/note-menu.vue
index 877d2c16bb..3e4be425d9 100644
--- a/src/client/app/common/views/components/note-menu.vue
+++ b/src/client/app/common/views/components/note-menu.vue
@@ -2,6 +2,7 @@
 <div class="mk-note-menu">
 	<div class="backdrop" ref="backdrop" @click="close"></div>
 	<div class="popover" :class="{ compact }" ref="popover">
+		<button @click="favorite">%i18n:@favorite%</button>
 		<button v-if="note.userId == os.i.id" @click="pin">%i18n:@pin%</button>
 		<a v-if="note.uri" :href="note.uri" target="_blank">%i18n:@remote%</a>
 	</div>
@@ -58,6 +59,14 @@ export default Vue.extend({
 			});
 		},
 
+		favorite() {
+			(this as any).api('notes/favorites/create', {
+				noteId: this.note.id
+			}).then(() => {
+				this.$destroy();
+			});
+		},
+
 		close() {
 			(this.$refs.backdrop as any).style.pointerEvents = 'none';
 			anime({
@@ -142,6 +151,7 @@ $border-color = rgba(27, 31, 35, 0.15)
 		> a
 			display block
 			padding 8px 16px
+			width 100%
 
 			&:hover
 				color $theme-color-foreground
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index 8d95d81775..3b0ed48cd0 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -25,6 +25,7 @@ import updateBanner from './api/update-banner';
 
 import MkIndex from './views/pages/index.vue';
 import MkUser from './views/pages/user/user.vue';
+import MkFavorites from './views/pages/favorites.vue';
 import MkSelectDrive from './views/pages/selectdrive.vue';
 import MkDrive from './views/pages/drive.vue';
 import MkHomeCustomize from './views/pages/home-customize.vue';
@@ -50,6 +51,7 @@ init(async (launch) => {
 		routes: [
 			{ path: '/', name: 'index', component: MkIndex },
 			{ path: '/i/customize-home', component: MkHomeCustomize },
+			{ path: '/i/favorites', component: MkFavorites },
 			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 			{ path: '/i/drive', component: MkDrive },
 			{ path: '/i/drive/folder/:folder', component: MkDrive },
diff --git a/src/client/app/desktop/views/pages/favorites.vue b/src/client/app/desktop/views/pages/favorites.vue
new file mode 100644
index 0000000000..d908c08f7c
--- /dev/null
+++ b/src/client/app/desktop/views/pages/favorites.vue
@@ -0,0 +1,73 @@
+<template>
+<mk-ui>
+	<main v-if="!fetching">
+		<template v-for="favorite in favorites">
+			<mk-note-detail :note="favorite.note" :key="favorite.note.id"/>
+		</template>
+		<a v-if="existMore" @click="more">さらに読み込む</a>
+	</main>
+</mk-ui>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import Progress from '../../../common/scripts/loading';
+
+export default Vue.extend({
+	data() {
+		return {
+			fetching: true,
+			favorites: [],
+			existMore: false,
+			moreFetching: false
+		};
+	},
+	created() {
+		this.fetch();
+	},
+	methods: {
+		fetch() {
+			Progress.start();
+			this.fetching = true;
+
+			(this as any).api('i/favorites', {
+				limit: 11
+			}).then(favorites => {
+				if (favorites.length == 11) {
+					this.existMore = true;
+					favorites.pop();
+				}
+
+				this.favorites = favorites;
+				this.fetching = false;
+
+				Progress.done();
+			});
+		},
+		more() {
+			this.moreFetching = true;
+			(this as any).api('i/favorites', {
+				limit: 11,
+				maxId: this.favorites[this.favorites.length - 1].id
+			}).then(favorites => {
+				if (favorites.length == 11) {
+					this.existMore = true;
+					favorites.pop();
+				} else {
+					this.existMore = false;
+				}
+
+				this.favorites = this.favorites.concat(favorites);
+				this.moreFetching = false;
+			});
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+main
+	margin 0 auto
+	padding 16px
+	max-width 700px
+</style>
diff --git a/src/models/favorite.ts b/src/models/favorite.ts
index 5387b29457..d24833f191 100644
--- a/src/models/favorite.ts
+++ b/src/models/favorite.ts
@@ -1,5 +1,7 @@
 import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
 import db from '../db/mongodb';
+import { pack as packNote } from './note';
 
 const Favorite = db.get<IFavorite>('favorites');
 Favorite.createIndex(['userId', 'noteId'], { unique: true });
@@ -38,3 +40,35 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori
 		_id: f._id
 	});
 }
+
+/**
+ * Pack a favorite for API response
+ */
+export const pack = (
+	favorite: any,
+	me: any
+) => new Promise<any>(async (resolve, reject) => {
+	let _favorite: any;
+
+	// Populate the favorite if 'favorite' is ID
+	if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) {
+		_favorite = await Favorite.findOne({
+			_id: favorite
+		});
+	} else if (typeof favorite === 'string') {
+		_favorite = await Favorite.findOne({
+			_id: new mongo.ObjectID(favorite)
+		});
+	} else {
+		_favorite = deepcopy(favorite);
+	}
+
+	// Rename _id to id
+	_favorite.id = _favorite._id;
+	delete _favorite._id;
+
+	// Populate note
+	_favorite.note = await packNote(_favorite.noteId, me);
+
+	resolve(_favorite);
+});
diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts
index 7cf49debe9..3686918147 100644
--- a/src/server/api/endpoints.ts
+++ b/src/server/api/endpoints.ts
@@ -233,6 +233,12 @@ const endpoints: Endpoint[] = [
 		kind: 'notification-read'
 	},
 
+	{
+		name: 'i/favorites',
+		withCredential: true,
+		kind: 'favorites-read'
+	},
+
 	{
 		name: 'othello/match',
 		withCredential: true
diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts
index b40f2b3887..f390ef9ec7 100644
--- a/src/server/api/endpoints/i/favorites.ts
+++ b/src/server/api/endpoints/i/favorites.ts
@@ -2,43 +2,52 @@
  * Module dependencies
  */
 import $ from 'cafy';
-import Favorite from '../../../../models/favorite';
-import { pack } from '../../../../models/note';
+import Favorite, { pack } from '../../../../models/favorite';
 
 /**
- * Get followers of a user
- *
- * @param {any} params
- * @param {any} user
- * @return {Promise<any>}
+ * Get favorited notes
  */
 module.exports = (params, user) => new Promise(async (res, rej) => {
 	// Get 'limit' parameter
 	const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$;
 	if (limitErr) return rej('invalid limit param');
 
-	// Get 'offset' parameter
-	const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$;
-	if (offsetErr) return rej('invalid offset param');
+	// Get 'sinceId' parameter
+	const [sinceId, sinceIdErr] = $(params.sinceId).optional.id().$;
+	if (sinceIdErr) return rej('invalid sinceId param');
 
-	// Get 'sort' parameter
-	const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$;
-	if (sortError) return rej('invalid sort param');
+	// Get 'untilId' parameter
+	const [untilId, untilIdErr] = $(params.untilId).optional.id().$;
+	if (untilIdErr) return rej('invalid untilId param');
+
+	// Check if both of sinceId and untilId is specified
+	if (sinceId && untilId) {
+		return rej('cannot set sinceId and untilId');
+	}
+
+	const query = {
+		userId: user._id
+	} as any;
+
+	const sort = {
+		_id: -1
+	};
+
+	if (sinceId) {
+		sort._id = 1;
+		query._id = {
+			$gt: sinceId
+		};
+	} else if (untilId) {
+		query._id = {
+			$lt: untilId
+		};
+	}
 
 	// Get favorites
 	const favorites = await Favorite
-		.find({
-			userId: user._id
-		}, {
-			limit: limit,
-			skip: offset,
-			sort: {
-				_id: sort == 'asc' ? 1 : -1
-			}
-		});
+		.find(query, { limit, sort });
 
 	// Serialize
-	res(await Promise.all(favorites.map(async favorite =>
-		await pack(favorite.noteId)
-	)));
+	res(await Promise.all(favorites.map(favorite => pack(favorite, user))));
 });