diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ab867e57f..d387bff55e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@ ChangeLog (Release Notes)
 =========================
 主に notable な changes を書いていきます
 
+unreleased
+-----------------
+* ホームのカスタマイズを実装するなど
+
 2971 (2017/11/08)
 -----------------
 * バグ修正
diff --git a/package.json b/package.json
index b13d17e1fb..62006e0671 100644
--- a/package.json
+++ b/package.json
@@ -99,7 +99,7 @@
 		"autwh": "0.0.1",
 		"bcryptjs": "2.4.3",
 		"body-parser": "1.18.2",
-		"cafy": "3.1.1",
+		"cafy": "3.2.0",
 		"chalk": "2.3.0",
 		"compression": "1.7.1",
 		"cors": "2.8.4",
@@ -142,6 +142,7 @@
 		"rndstr": "1.0.0",
 		"s-age": "1.1.0",
 		"serve-favicon": "2.4.5",
+		"sortablejs": "1.7.0",
 		"summaly": "2.0.3",
 		"syuilo-password-strength": "0.0.1",
 		"tcp-port-used": "0.1.2",
diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts
index afefce39e5..2783c92027 100644
--- a/src/api/endpoints.ts
+++ b/src/api/endpoints.ts
@@ -159,6 +159,11 @@ const endpoints: Endpoint[] = [
 		},
 		kind: 'account-write'
 	},
+	{
+		name: 'i/update_home',
+		withCredential: true,
+		kind: 'account-write'
+	},
 	{
 		name: 'i/change_password',
 		withCredential: true
diff --git a/src/api/endpoints/i/appdata/get.ts b/src/api/endpoints/i/appdata/get.ts
index a1a57fa13a..571208d46c 100644
--- a/src/api/endpoints/i/appdata/get.ts
+++ b/src/api/endpoints/i/appdata/get.ts
@@ -13,38 +13,27 @@ import Appdata from '../../../models/appdata';
  * @param {Boolean} isSecure
  * @return {Promise<any>}
  */
-module.exports = (params, user, app, isSecure) => new Promise(async (res, rej) => {
+module.exports = (params, user, app) => new Promise(async (res, rej) => {
+	if (app == null) return rej('このAPIはサードパーティAppからのみ利用できます');
+
 	// Get 'key' parameter
 	const [key = null, keyError] = $(params.key).optional.nullable.string().match(/[a-z_]+/).$;
 	if (keyError) return rej('invalid key param');
 
-	if (isSecure) {
-		if (!user.data) {
-			return res();
-		}
-		if (key !== null) {
-			const data = {};
-			data[key] = user.data[key];
-			res(data);
-		} else {
-			res(user.data);
-		}
-	} else {
-		const select = {};
-		if (key !== null) {
-			select[`data.${key}`] = true;
-		}
-		const appdata = await Appdata.findOne({
-			app_id: app._id,
-			user_id: user._id
-		}, {
-				fields: select
-			});
+	const select = {};
+	if (key !== null) {
+		select[`data.${key}`] = true;
+	}
+	const appdata = await Appdata.findOne({
+		app_id: app._id,
+		user_id: user._id
+	}, {
+		fields: select
+	});
 
-		if (appdata) {
-			res(appdata.data);
-		} else {
-			res();
-		}
+	if (appdata) {
+		res(appdata.data);
+	} else {
+		res();
 	}
 });
diff --git a/src/api/endpoints/i/appdata/set.ts b/src/api/endpoints/i/appdata/set.ts
index 9c3dbe185b..2804a14cb3 100644
--- a/src/api/endpoints/i/appdata/set.ts
+++ b/src/api/endpoints/i/appdata/set.ts
@@ -3,9 +3,6 @@
  */
 import $ from 'cafy';
 import Appdata from '../../../models/appdata';
-import User from '../../../models/user';
-import serialize from '../../../serializers/user';
-import event from '../../../event';
 
 /**
  * Set app data
@@ -16,7 +13,9 @@ import event from '../../../event';
  * @param {Boolean} isSecure
  * @return {Promise<any>}
  */
-module.exports = (params, user, app, isSecure) => new Promise(async (res, rej) => {
+module.exports = (params, user, app) => new Promise(async (res, rej) => {
+	if (app == null) return rej('このAPIはサードパーティAppからのみ利用できます');
+
 	// Get 'data' parameter
 	const [data, dataError] = $(params.data).optional.object()
 		.pipe(obj => {
@@ -43,31 +42,17 @@ module.exports = (params, user, app, isSecure) => new Promise(async (res, rej) =
 		set[`data.${key}`] = value;
 	}
 
-	if (isSecure) {
-		const _user = await User.findOneAndUpdate(user._id, {
+	await Appdata.update({
+		app_id: app._id,
+		user_id: user._id
+	}, Object.assign({
+		app_id: app._id,
+		user_id: user._id
+	}, {
 			$set: set
+		}), {
+			upsert: true
 		});
 
-		res(204);
-
-		// Publish i updated event
-		event(user._id, 'i_updated', await serialize(_user, user, {
-			detail: true,
-			includeSecrets: true
-		}));
-	} else {
-		await Appdata.update({
-			app_id: app._id,
-			user_id: user._id
-		}, Object.assign({
-			app_id: app._id,
-			user_id: user._id
-		}, {
-				$set: set
-			}), {
-				upsert: true
-			});
-
-		res(204);
-	}
+	res(204);
 });
diff --git a/src/api/endpoints/i/update.ts b/src/api/endpoints/i/update.ts
index 111a4b1909..c484c51a96 100644
--- a/src/api/endpoints/i/update.ts
+++ b/src/api/endpoints/i/update.ts
@@ -48,13 +48,19 @@ module.exports = async (params, user, _, isSecure) => new Promise(async (res, re
 	if (bannerIdErr) return rej('invalid banner_id param');
 	if (bannerId) user.banner_id = bannerId;
 
+	// Get 'show_donation' parameter
+	const [showDonation, showDonationErr] = $(params.show_donation).optional.boolean().$;
+	if (showDonationErr) return rej('invalid show_donation param');
+	if (showDonation) user.client_settings.show_donation = showDonation;
+
 	await User.update(user._id, {
 		$set: {
 			name: user.name,
 			description: user.description,
 			avatar_id: user.avatar_id,
 			banner_id: user.banner_id,
-			profile: user.profile
+			profile: user.profile,
+			'client_settings.show_donation': user.client_settings.show_donation
 		}
 	});
 
diff --git a/src/api/endpoints/i/update_home.ts b/src/api/endpoints/i/update_home.ts
new file mode 100644
index 0000000000..b9a7642b8e
--- /dev/null
+++ b/src/api/endpoints/i/update_home.ts
@@ -0,0 +1,34 @@
+/**
+ * Module dependencies
+ */
+import $ from 'cafy';
+import User from '../../models/user';
+
+/**
+ * Update myself
+ *
+ * @param {any} params
+ * @param {any} user
+ * @param {any} _
+ * @param {boolean} isSecure
+ * @return {Promise<any>}
+ */
+module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => {
+	// Get 'home' parameter
+	const [home, homeErr] = $(params.home).array().each(
+		$().strict.object()
+			.have('name', $().string())
+			.have('id', $().string())
+			.have('place', $().string())
+			.have('data', $().object())).$;
+	if (homeErr) return rej('invalid home param');
+
+	await User.update(user._id, {
+		$set: {
+			'client_settings.home': home
+		}
+	});
+
+	// Send response
+	res();
+});
diff --git a/src/api/private/signup.ts b/src/api/private/signup.ts
index 7a6b9c098e..e24734f80c 100644
--- a/src/api/private/signup.ts
+++ b/src/api/private/signup.ts
@@ -1,3 +1,4 @@
+import * as uuid from 'uuid';
 import * as express from 'express';
 import * as bcrypt from 'bcryptjs';
 import recaptcha = require('recaptcha-promise');
@@ -11,6 +12,28 @@ recaptcha.init({
 	secret_key: config.recaptcha.secretKey
 });
 
+const home = {
+	left: [
+		'profile',
+		'calendar',
+		'activity',
+		'rss-reader',
+		'trends',
+		'photo-stream',
+		'version'
+	],
+	right: [
+		'broadcast',
+		'notifications',
+		'user-recommendation',
+		'recommended-polls',
+		'server',
+		'donation',
+		'nav',
+		'tips'
+	]
+};
+
 export default async (req: express.Request, res: express.Response) => {
 	// Verify recaptcha
 	// ただしテスト時はこの機構は障害となるため無効にする
@@ -60,6 +83,28 @@ export default async (req: express.Request, res: express.Response) => {
 	// Generate secret
 	const secret = generateUserToken();
 
+	//#region Construct home data
+	const homeData = [];
+
+	home.left.forEach(widget => {
+		homeData.push({
+			name: widget,
+			id: uuid(),
+			place: 'left',
+			data: {}
+		});
+	});
+
+	home.right.forEach(widget => {
+		homeData.push({
+			name: widget,
+			id: uuid(),
+			place: 'right',
+			data: {}
+		});
+	});
+	//#endregion
+
 	// Create account
 	const account: IUser = await User.insert({
 		token: secret,
@@ -88,6 +133,11 @@ export default async (req: express.Request, res: express.Response) => {
 			height: null,
 			location: null,
 			weight: null
+		},
+		settings: {},
+		client_settings: {
+			home: homeData,
+			show_donation: false
 		}
 	});
 
diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts
index 0d24d6cc04..3d84156606 100644
--- a/src/api/serializers/user.ts
+++ b/src/api/serializers/user.ts
@@ -35,9 +35,10 @@ export default (
 	let _user: any;
 
 	const fields = opts.detail ? {
-		data: false
+		settings: false
 	} : {
-		data: false,
+		settings: false,
+		client_settings: false,
 		profile: false,
 		keywords: false,
 		domains: false
@@ -72,7 +73,7 @@ export default (
 	delete _user._id;
 
 	// Remove needless properties
-	delete _user.lates_post;
+	delete _user.latest_post;
 
 	// Remove private properties
 	delete _user.password;
@@ -86,8 +87,8 @@ export default (
 
 	// Visible via only the official client
 	if (!opts.includeSecrets) {
-		delete _user.data;
 		delete _user.email;
+		delete _user.client_settings;
 	}
 
 	_user.avatar_url = _user.avatar_id != null
diff --git a/src/web/app/common/scripts/generate-default-userdata.js b/src/web/app/common/scripts/generate-default-userdata.js
deleted file mode 100644
index 1200563e1a..0000000000
--- a/src/web/app/common/scripts/generate-default-userdata.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import uuid from './uuid';
-
-const home = {
-	left: [
-		'profile',
-		'calendar',
-		'rss-reader',
-		'photo-stream',
-		'version'
-	],
-	right: [
-		'broadcast',
-		'notifications',
-		'user-recommendation',
-		'donation',
-		'nav',
-		'tips'
-	]
-};
-
-export default () => {
-	const homeData = [];
-
-	home.left.forEach(widget => {
-		homeData.push({
-			name: widget,
-			id: uuid(),
-			place: 'left'
-		});
-	});
-
-	home.right.forEach(widget => {
-		homeData.push({
-			name: widget,
-			id: uuid(),
-			place: 'right'
-		});
-	});
-
-	const data = {
-		home: JSON.stringify(homeData)
-	};
-
-	return data;
-};
diff --git a/src/web/app/desktop/router.js b/src/web/app/desktop/router.js
index 104b9bbe5d..bbd68cf99a 100644
--- a/src/web/app/desktop/router.js
+++ b/src/web/app/desktop/router.js
@@ -9,6 +9,7 @@ let page = null;
 export default me => {
 	route('/',                       index);
 	route('/selectdrive',            selectDrive);
+	route('/i/customize-home',       customizeHome);
 	route('/i/drive',                drive);
 	route('/i/drive/folder/:folder', drive);
 	route('/i/mentions',             mentions);
@@ -27,6 +28,10 @@ export default me => {
 		mount(document.createElement('mk-home-page'));
 	}
 
+	function customizeHome() {
+		mount(document.createElement('mk-home-customize-page'));
+	}
+
 	function entrance() {
 		mount(document.createElement('mk-entrance'));
 		document.documentElement.setAttribute('data-page', 'entrance');
diff --git a/src/web/app/desktop/tags/donation.tag b/src/web/app/desktop/tags/donation.tag
index 33f377a192..1c19fac1f5 100644
--- a/src/web/app/desktop/tags/donation.tag
+++ b/src/web/app/desktop/tags/donation.tag
@@ -54,11 +54,10 @@
 			e.preventDefault();
 			e.stopPropagation();
 
-			this.I.data.no_donation = 'true';
+			this.I.client_settings.show_donation = false;
 			this.I.update();
-			this.api('i/appdata/set', {
-				key: 'no_donation',
-				value: 'true'
+			this.api('i/update', {
+				show_donation: false
 			});
 
 			this.unmount();
diff --git a/src/web/app/desktop/tags/home-widgets/activity.tag b/src/web/app/desktop/tags/home-widgets/activity.tag
index 8bd8bfb2aa..7bdcd334c9 100644
--- a/src/web/app/desktop/tags/home-widgets/activity.tag
+++ b/src/web/app/desktop/tags/home-widgets/activity.tag
@@ -54,7 +54,7 @@
 		this.mixin('api');
 
 		this.initializing = true;
-		this.view = 0;
+		this.view = this.opts.data.hasOwnProperty('view') ? this.opts.data.view : 0;
 
 		this.on('mount', () => {
 			this.api('aggregation/users/activity', {
@@ -71,6 +71,14 @@
 		this.toggle = () => {
 			this.view++;
 			if (this.view == 2) this.view = 0;
+
+			// Save view state
+			this.I.client_settings.home.filter(w => w.id == this.opts.id)[0].data.view = this.view;
+			this.api('i/update_home', {
+				home: this.I.client_settings.home
+			}).then(() => {
+				this.I.update();
+			});
 		};
 	</script>
 </mk-activity-home-widget>
diff --git a/src/web/app/desktop/tags/home-widgets/server.tag b/src/web/app/desktop/tags/home-widgets/server.tag
index bc8f313d53..235867c38a 100644
--- a/src/web/app/desktop/tags/home-widgets/server.tag
+++ b/src/web/app/desktop/tags/home-widgets/server.tag
@@ -56,10 +56,11 @@
 	<script>
 		import Connection from '../../../common/scripts/server-stream';
 
+		this.mixin('i');
 		this.mixin('api');
 
 		this.initializing = true;
-		this.view = 0;
+		this.view = this.opts.data.hasOwnProperty('view') ? this.opts.data.view : 0;
 		this.connection = new Connection();
 
 		this.on('mount', () => {
@@ -78,6 +79,14 @@
 		this.toggle = () => {
 			this.view++;
 			if (this.view == 6) this.view = 0;
+
+			// Save view state
+			this.I.client_settings.home.filter(w => w.id == this.opts.id)[0].data.view = this.view;
+			this.api('i/update_home', {
+				home: this.I.client_settings.home
+			}).then(() => {
+				this.I.update();
+			});
 		};
 	</script>
 </mk-server-home-widget>
diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag
index 37b2d3cf7e..f0c71a7ea8 100644
--- a/src/web/app/desktop/tags/home.tag
+++ b/src/web/app/desktop/tags/home.tag
@@ -1,4 +1,30 @@
-<mk-home>
+<mk-home data-customize={ opts.customize }>
+	<div class="customize" if={ opts.customize }>
+		<div class="adder">
+			<p>ウィジェットを追加:</p>
+			<select ref="widgetSelector">
+				<option value="profile">プロフィール</option>
+				<option value="calendar">カレンダー</option>
+				<option value="activity">アクティビティ</option>
+				<option value="rss-reader">RSSリーダー</option>
+				<option value="trends">トレンド</option>
+				<option value="photo-stream">フォトストリーム</option>
+				<option value="version">バージョン</option>
+				<option value="broadcast">ブロードキャスト</option>
+				<option value="notifications">通知</option>
+				<option value="user-recommendation">おすすめユーザー</option>
+				<option value="recommended-polls">投票</option>
+				<option value="server">サーバー情報</option>
+				<option value="donation">寄付のお願い</option>
+				<option value="nav">ナビゲーション</option>
+				<option value="tips">ヒント</option>
+			</select>
+			<button onclick={ addWidget }>追加</button>
+		</div>
+		<div class="trash" ref="trash">
+			<p class="ignore"><b>ゴミ箱</b> (ここにウィジェットをドロップすると削除できます)</p>
+		</div>
+	</div>
 	<div class="main">
 		<div class="left" ref="left"></div>
 		<main>
@@ -11,25 +37,37 @@
 		:scope
 			display block
 
+			&:not([data-customize])
+				> .main > *:empty
+					display none
+
+			> .customize
+				display flex
+				margin 0 auto
+				max-width 1200px
+				background #fff1c8
+
+				> div
+					width 50%
+
+					&.trash
+						background #ffc5c5
+
 			> .main
+				display flex
+				justify-content center
 				margin 0 auto
 				max-width 1200px
 
-				&:after
-					content ""
-					display block
-					clear both
-
 				> *
-					float left
-
-					> *
+					> *:not(.customize-container)
+					> .customize-container > *
 						display block
-						//border solid 1px #eaeaea
 						border solid 1px rgba(0, 0, 0, 0.075)
 						border-radius 6px
-						//box-shadow 0px 2px 16px rgba(0, 0, 0, 0.2)
 
+					> *:not(.customize-container)
+					> .customize-container
 						&:not(:last-child)
 							margin-bottom 16px
 
@@ -40,6 +78,12 @@
 				> *:not(main)
 					width 275px
 
+					> .customize-container
+						cursor move
+
+						> *
+							pointer-events none
+
 				> .left
 					padding 16px 0 16px 16px
 
@@ -58,66 +102,49 @@
 
 	</style>
 	<script>
+		import uuid from 'uuid';
+		import Sortable from 'sortablejs';
+
 		this.mixin('i');
+		this.mixin('api');
 
 		this.mode = this.opts.mode || 'timeline';
 
-		const _home = {
-			left: [
-				'profile',
-				'calendar',
-				'activity',
-				'rss-reader',
-				'trends',
-				'photo-stream',
-				'version'
-			],
-			right: [
-				'broadcast',
-				'notifications',
-				'user-recommendation',
-				'recommended-polls',
-				'server',
-				'donation',
-				'nav',
-				'tips'
-			]
-		};
-
 		this.home = [];
 
 		this.on('mount', () => {
 			this.refs.tl.on('loaded', () => {
 				this.trigger('loaded');
 			});
-/*
-			this.I.data.home.forEach(widget => {
+
+			this.I.client_settings.home.forEach(widget => {
 				try {
-					const el = document.createElement(`mk-${widget.name}-home-widget`);
-					switch (widget.place) {
-						case 'left': this.refs.left.appendChild(el); break;
-						case 'right': this.refs.right.appendChild(el); break;
-					}
-					this.home.push(riot.mount(el, {
-						id: widget.id,
-						data: widget.data
-					})[0]);
+					this.setWidget(widget);
 				} catch (e) {
 					// noop
 				}
 			});
-*/
-			_home.left.forEach(widget => {
-				const el = document.createElement(`mk-${widget}-home-widget`);
-				this.refs.left.appendChild(el);
-				this.home.push(riot.mount(el)[0]);
-			});
 
-			_home.right.forEach(widget => {
-				const el = document.createElement(`mk-${widget}-home-widget`);
-				this.refs.right.appendChild(el);
-				this.home.push(riot.mount(el)[0]);
-			});
+			if (this.opts.customize) {
+				const sortableOption = {
+					group: 'kyoppie',
+					animation: 150,
+					filter: '.ignore',
+					onSort: this.saveHome
+				};
+
+				new Sortable(this.refs.left, sortableOption);
+				new Sortable(this.refs.right, sortableOption);
+				new Sortable(this.refs.trash, Object.assign({}, sortableOption, {
+					onAdd: evt => {
+						const el = evt.item;
+						const id = el.getAttribute('data-widget-id');
+						el.parentNode.removeChild(el);
+						this.I.client_settings.home = this.I.client_settings.home.filter(w => w.id != id);
+						this.saveHome();
+					}
+				}));
+			}
 		});
 
 		this.on('unmount', () => {
@@ -125,5 +152,83 @@
 				widget.unmount();
 			});
 		});
+
+		this.setWidget = (widget, prepend = false) => {
+			const el = document.createElement(`mk-${widget.name}-home-widget`);
+
+			let actualEl;
+
+			if (this.opts.customize) {
+				const container = document.createElement('div');
+				container.classList.add('customize-container');
+				container.setAttribute('data-widget-id', widget.id);
+				container.appendChild(el);
+				actualEl = container;
+			} else {
+				actualEl = el;
+			}
+
+			switch (widget.place) {
+				case 'left':
+					if (prepend) {
+						this.refs.left.insertBefore(actualEl, this.refs.left.firstChild);
+					} else {
+						this.refs.left.appendChild(actualEl);
+					}
+					break;
+				case 'right':
+					if (prepend) {
+						this.refs.right.insertBefore(actualEl, this.refs.right.firstChild);
+					} else {
+						this.refs.right.appendChild(actualEl);
+					}
+					break;
+			}
+
+			this.home.push(riot.mount(el, {
+				id: widget.id,
+				data: widget.data
+			})[0]);
+		};
+
+		this.addWidget = () => {
+			const widget = {
+				name: this.refs.widgetSelector.options[this.refs.widgetSelector.selectedIndex].value,
+				id: uuid(),
+				place: 'left',
+				data: {}
+			};
+
+			this.I.client_settings.home.unshift(widget);
+
+			this.setWidget(widget, true);
+
+			this.saveHome();
+		};
+
+		this.saveHome = () => {
+			const data = [];
+
+			Array.from(this.refs.left.children).forEach(el => {
+				const id = el.getAttribute('data-widget-id');
+				const widget = this.I.client_settings.home.find(w => w.id == id);
+				widget.place = 'left';
+				data.push(widget);
+			});
+
+			Array.from(this.refs.right.children).forEach(el => {
+				const id = el.getAttribute('data-widget-id');
+				const widget = this.I.client_settings.home.find(w => w.id == id);
+				widget.place = 'right';
+				data.push(widget);
+			});
+
+			this.api('i/update_home', {
+				home: data
+			}).then(() => {
+				this.I.client_settings.home = data;
+				this.I.update();
+			});
+		};
 	</script>
 </mk-home>
diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js
index 7997bcc7f2..c36a06e499 100644
--- a/src/web/app/desktop/tags/index.js
+++ b/src/web/app/desktop/tags/index.js
@@ -57,6 +57,7 @@ require('./pages/entrance.tag');
 require('./pages/entrance/signin.tag');
 require('./pages/entrance/signup.tag');
 require('./pages/home.tag');
+require('./pages/home-customize.tag');
 require('./pages/user.tag');
 require('./pages/post.tag');
 require('./pages/search.tag');
diff --git a/src/web/app/desktop/tags/pages/home-customize.tag b/src/web/app/desktop/tags/pages/home-customize.tag
new file mode 100644
index 0000000000..4434015615
--- /dev/null
+++ b/src/web/app/desktop/tags/pages/home-customize.tag
@@ -0,0 +1,14 @@
+<mk-home-customize-page>
+	<mk-ui ref="ui" page="timeline">
+		<mk-home ref="home" mode={ parent.opts.mode } customize={ true }/>
+	</mk-ui>
+	<style>
+		:scope
+			display block
+	</style>
+	<script>
+		this.on('mount', () => {
+			document.title = 'Misskey - ホームのカスタマイズ';
+		});
+	</script>
+</mk-home-customize-page>
diff --git a/src/web/app/desktop/tags/settings.tag b/src/web/app/desktop/tags/settings.tag
index eabddfb432..4c16f9eaa8 100644
--- a/src/web/app/desktop/tags/settings.tag
+++ b/src/web/app/desktop/tags/settings.tag
@@ -38,6 +38,7 @@
 
 		<section class="web" show={ page == 'web' }>
 			<h1>デザイン</h1>
+			<a href="/i/customize-home">ホームをカスタマイズ</a>
 		</section>
 
 		<section class="web" show={ page == 'web' }>
diff --git a/src/web/app/desktop/tags/ui.tag b/src/web/app/desktop/tags/ui.tag
index 0a3e8d9c53..6a4982877f 100644
--- a/src/web/app/desktop/tags/ui.tag
+++ b/src/web/app/desktop/tags/ui.tag
@@ -37,7 +37,7 @@
 </mk-ui>
 
 <mk-ui-header>
-	<mk-donation if={ SIGNIN && I.data.no_donation != 'true' }/>
+	<mk-donation if={ SIGNIN && I.client_settings.show_donation }/>
 	<mk-special-message/>
 	<div class="main">
 		<div class="backdrop"></div>
diff --git a/src/web/app/init.js b/src/web/app/init.js
index 5a6899ed4f..7e3c2ee377 100644
--- a/src/web/app/init.js
+++ b/src/web/app/init.js
@@ -11,7 +11,6 @@ import checkForUpdate from './common/scripts/check-for-update';
 import Connection from './common/scripts/home-stream';
 import Progress from './common/scripts/loading';
 import mixin from './common/mixins';
-import generateDefaultUserdata from './common/scripts/generate-default-userdata';
 import CONFIG from './common/scripts/config';
 require('./common/tags');
 
@@ -156,9 +155,7 @@ function fetchme(token, cb) {
 		res.json().then(i => {
 			me = i;
 			me.token = token;
-
-			// initialize it if user data is empty
-			me.data ? done() : init();
+			done();
 		});
 	}, () => { // When failure
 		// Render the error screen
@@ -170,17 +167,6 @@ function fetchme(token, cb) {
 	function done() {
 		if (cb) cb(me);
 	}
-
-	// Initialize user data
-	function init() {
-		const data = generateDefaultUserdata();
-		api(token, 'i/appdata/set', {
-			data
-		}).then(() => {
-			me.data = data;
-			done();
-		});
-	}
 }
 
 // BSoD
diff --git a/tools/migration/node.2017-11-08..js b/tools/migration/node.2017-11-08..js
new file mode 100644
index 0000000000..e25b83b3f3
--- /dev/null
+++ b/tools/migration/node.2017-11-08..js
@@ -0,0 +1,89 @@
+const uuid = require('uuid');
+const { default: User } = require('../../built/api/models/user')
+const { default: zip } = require('@prezzemolo/zip')
+
+const home = {
+	left: [
+		'profile',
+		'calendar',
+		'activity',
+		'rss-reader',
+		'trends',
+		'photo-stream',
+		'version'
+	],
+	right: [
+		'broadcast',
+		'notifications',
+		'user-recommendation',
+		'recommended-polls',
+		'server',
+		'donation',
+		'nav',
+		'tips'
+	]
+};
+
+
+const migrate = async (doc) => {
+
+	//#region Construct home data
+	const homeData = [];
+
+	home.left.forEach(widget => {
+		homeData.push({
+			name: widget,
+			id: uuid(),
+			place: 'left',
+			data: {}
+		});
+	});
+
+	home.right.forEach(widget => {
+		homeData.push({
+			name: widget,
+			id: uuid(),
+			place: 'right',
+			data: {}
+		});
+	});
+	//#endregion
+
+	const result = await User.update(doc._id, {
+		$unset: {
+			data: ''
+		},
+		$set: {
+			'settings': {},
+			'client_settings.home': homeData,
+			'client_settings.show_donation': false
+		}
+	})
+
+	return added && result.ok === 1
+}
+
+async function main() {
+	const count = await db.get('users').count();
+
+	console.log(`there are ${count} users.`)
+
+	const dop = Number.parseInt(process.argv[2]) || 5
+	const idop = ((count - (count % dop)) / dop) + 1
+
+	return zip(
+		1,
+		async (time) => {
+			console.log(`${time} / ${idop}`)
+			const docs = await db.get('users').find({}, { limit: dop, skip: time * dop })
+			return Promise.all(docs.map(migrate))
+		},
+		idop
+	).then(a => {
+		const rv = []
+		a.forEach(e => rv.push(...e))
+		return rv
+	})
+}
+
+main().then(console.dir).catch(console.error)