diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 904737884..c3eb0bb52 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -762,6 +762,9 @@ common/views/components/user-group-editor.vue:
   users: "メンバー"
   rename: "グループ名を変更"
   delete: "グループを削除"
+  transfer: "グループを譲渡"
+  transfer-are-you-sure: "グループ「$1」を「@$2」さんに譲渡しますか?"
+  transferred: "グループを譲渡しました"
   remove-user: "このグループから削除"
   delete-are-you-sure: "グループ「$1」を削除しますか?"
   deleted: "削除しました"
diff --git a/src/client/app/common/views/pages/user-group-editor.vue b/src/client/app/common/views/pages/user-group-editor.vue
index ef79689ae..a32148cd7 100644
--- a/src/client/app/common/views/pages/user-group-editor.vue
+++ b/src/client/app/common/views/pages/user-group-editor.vue
@@ -7,6 +7,7 @@
 			<ui-margin>
 				<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
 				<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
+				<ui-button @click="transfer"><fa :icon="faCrown"/> {{ $t('transfer') }}</ui-button>
 			</ui-margin>
 		</section>
 	</ui-container>
@@ -28,9 +29,10 @@
 					<div>
 						<header>
 							<b><mk-user-name :user="user"/></b>
+							<span class="is-owner" v-if="group.owner === user.id">owner</span>
 							<span class="username">@{{ user | acct }}</span>
 						</header>
-						<div>
+						<div v-if="group.owner !== user.id">
 							<a @click="remove(user)">{{ $t('remove-user') }}</a>
 						</div>
 					</div>
@@ -44,7 +46,7 @@
 <script lang="ts">
 import Vue from 'vue';
 import i18n from '../../../i18n';
-import { faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { faCrown, faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 
 export default Vue.extend({
@@ -60,7 +62,7 @@ export default Vue.extend({
 		return {
 			group: null,
 			users: [],
-			faICursor, faTrashAlt, faUsers, faPlus
+			faCrown, faICursor, faTrashAlt, faUsers, faPlus
 		};
 	},
 
@@ -78,6 +80,14 @@ export default Vue.extend({
 	},
 
 	methods: {
+		fetchGroup() {
+			this.$root.api('users/groups/show', {
+				groupId: this.group.id
+			}).then(group => {
+				this.group = group;
+			})
+		},
+
 		fetchUsers() {
 			this.$root.api('users/show', {
 				userIds: this.group.userIds
@@ -97,8 +107,15 @@ export default Vue.extend({
 				this.$root.api('users/groups/update', {
 					groupId: this.group.id,
 					name: name
+				}).then(() => {
+					this.fetchGroup();
+				}).catch(e => {
+					this.$root.dialog({
+						type: 'error',
+						text: e
+					});
 				});
-			});
+			})
 		},
 
 		del() {
@@ -130,12 +147,17 @@ export default Vue.extend({
 				groupId: this.group.id,
 				userId: user.id
 			}).then(() => {
+				this.fetchGroup();
 				this.fetchUsers();
+			}).catch(e => {
+				this.$root.dialog({
+					type: 'error',
+					text: e
+				});
 			});
 		},
 
 		async invite() {
-			const t = this.$t('invited');
 			const { result: user } = await this.$root.dialog({
 				user: {
 					local: true
@@ -148,7 +170,44 @@ export default Vue.extend({
 			}).then(() => {
 				this.$root.dialog({
 					type: 'success',
-					text: t
+					text: this.$t('invited')
+				});
+			}).catch(e => {
+				this.$root.dialog({
+					type: 'error',
+					text: e
+				});
+			});
+		},
+
+		async transfer() {
+			const { result: user } = await this.$root.dialog({
+				user: {
+					local: true
+				}
+			});
+			if (user == null) return;
+
+			this.$root.dialog({
+				type: 'warning',
+				text: this.$t('transfer-are-you-sure').replace('$1', this.group.name).replace('$2', user.username),
+				showCancelButton: true
+			}).then(({ canceled }) => {
+				if (canceled) return;
+
+				this.$root.api('users/groups/transfer', {
+					groupId: this.group.id,
+					userId: user.id
+				}).then(() => {
+					this.$root.dialog({
+						type: 'success',
+						text: this.$t('transferred')
+					});
+				}).catch(e => {
+					this.$root.dialog({
+						type: 'error',
+						text: e
+					});
 				});
 			});
 		}
@@ -179,6 +238,16 @@ export default Vue.extend({
 			> header
 				color var(--text)
 
+				> .is-owner
+					flex-shrink 0
+					align-self center
+					margin-left 8px
+					padding 1px 6px
+					font-size 80%
+					background var(--groupUserListOwnerBg)
+					color var(--groupUserListOwnerFg)
+					border-radius 3px
+
 				> .username
 					margin-left 8px
 					opacity 0.7
diff --git a/src/client/app/init.ts b/src/client/app/init.ts
index da7baff4f..52da380e8 100644
--- a/src/client/app/init.ts
+++ b/src/client/app/init.ts
@@ -78,6 +78,7 @@ import {
 	faKey,
 	faBan,
 	faCogs,
+	faCrown,
 	faUnlockAlt,
 	faPuzzlePiece,
 	faMobileAlt,
@@ -210,6 +211,7 @@ library.add(
 	faKey,
 	faBan,
 	faCogs,
+	faCrown,
 	faUnlockAlt,
 	faPuzzlePiece,
 	faMobileAlt,
diff --git a/src/client/themes/dark.json5 b/src/client/themes/dark.json5
index 8e0c726b4..0665d5990 100644
--- a/src/client/themes/dark.json5
+++ b/src/client/themes/dark.json5
@@ -235,5 +235,8 @@
 
 		pageBlockBorder: 'rgba(255, 255, 255, 0.1)',
 		pageBlockBorderHover: 'rgba(255, 255, 255, 0.15)',
+
+		groupUserListOwnerFg: '#f15f71',
+		groupUserListOwnerBg: '#5d282e'
 	},
 }
diff --git a/src/client/themes/light.json5 b/src/client/themes/light.json5
index 1fff18176..cbe456ca5 100644
--- a/src/client/themes/light.json5
+++ b/src/client/themes/light.json5
@@ -235,5 +235,8 @@
 
 		pageBlockBorder: 'rgba(0, 0, 0, 0.1)',
 		pageBlockBorderHover: 'rgba(0, 0, 0, 0.15)',
+
+		groupUserListOwnerFg: '#f15f71',
+		groupUserListOwnerBg: '#ffdfdf'
 	},
 }
diff --git a/src/models/repositories/user-group.ts b/src/models/repositories/user-group.ts
index 8bb1ae833..dbbe8bf84 100644
--- a/src/models/repositories/user-group.ts
+++ b/src/models/repositories/user-group.ts
@@ -21,6 +21,7 @@ export class UserGroupRepository extends Repository<UserGroup> {
 			id: userGroup.id,
 			createdAt: userGroup.createdAt.toISOString(),
 			name: userGroup.name,
+			owner: userGroup.userId,
 			userIds: users.map(x => x.userId)
 		};
 	}
@@ -48,6 +49,11 @@ export const packedUserGroupSchema = {
 			optional: bool.false, nullable: bool.false,
 			description: 'The name of the UserGroup.'
 		},
+		owner: {
+			type: types.string,
+			nullable: bool.false, optional: bool.false,
+			format: 'id',
+		},
 		userIds: {
 			type: types.array,
 			nullable: bool.false, optional: bool.true,
diff --git a/src/server/api/endpoints/users/groups/transfer.ts b/src/server/api/endpoints/users/groups/transfer.ts
new file mode 100644
index 000000000..3baa182ab
--- /dev/null
+++ b/src/server/api/endpoints/users/groups/transfer.ts
@@ -0,0 +1,86 @@
+import $ from 'cafy';
+import { ID } from '../../../../../misc/cafy-id';
+import define from '../../../define';
+import { ApiError } from '../../../error';
+import { getUser } from '../../../common/getters';
+import { UserGroups, UserGroupJoinings } from '../../../../../models';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定したユーザーグループを指定したユーザーグループ内のユーザーに譲渡します。',
+		'en-US': 'Transfer user group ownership to another user in group.'
+	},
+
+	tags: ['groups', 'users'],
+
+	requireCredential: true,
+
+	kind: 'write:user-groups',
+
+	params: {
+		groupId: {
+			validator: $.type(ID),
+		},
+
+		userId: {
+			validator: $.type(ID),
+			desc: {
+				'ja-JP': '対象のユーザーのID',
+				'en-US': 'Target user ID'
+			}
+		},
+	},
+
+	errors: {
+		noSuchGroup: {
+			message: 'No such group.',
+			code: 'NO_SUCH_GROUP',
+			id: '8e31d36b-2f88-4ccd-a438-e2d78a9162db'
+		},
+
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '711f7ebb-bbb9-4dfa-b540-b27809fed5e9'
+		},
+
+		noSuchGroupMember: {
+			message: 'No such group member.',
+			code: 'NO_SUCH_GROUP_MEMBER',
+			id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4'
+		},
+	}
+};
+
+export default define(meta, async (ps, me) => {
+	// Fetch the group
+	const userGroup = await UserGroups.findOne({
+		id: ps.groupId,
+		userId: me.id,
+	});
+
+	if (userGroup == null) {
+		throw new ApiError(meta.errors.noSuchGroup);
+	}
+
+	// Fetch the user
+	const user = await getUser(ps.userId).catch(e => {
+		if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+		throw e;
+	});
+
+	const joining = await UserGroupJoinings.findOne({
+		userGroupId: userGroup.id,
+		userId: user.id
+	});
+
+	if (!joining) {
+		throw new ApiError(meta.errors.noSuchGroupMember);
+	}
+
+	await UserGroups.update(userGroup.id, {
+		userId: ps.userId
+	});
+
+	return await UserGroups.pack(userGroup.id);
+});
diff --git a/src/server/api/endpoints/users/groups/update.ts b/src/server/api/endpoints/users/groups/update.ts
new file mode 100644
index 000000000..ad9a1faa2
--- /dev/null
+++ b/src/server/api/endpoints/users/groups/update.ts
@@ -0,0 +1,62 @@
+import $ from 'cafy';
+import { ID } from '../../../../../misc/cafy-id';
+import define from '../../../define';
+import { ApiError } from '../../../error';
+import { UserGroups } from '../../../../../models';
+
+export const meta = {
+	desc: {
+		'ja-JP': '指定したユーザーグループを更新します。',
+		'en-US': 'Update a user group'
+	},
+
+	tags: ['groups'],
+
+	requireCredential: true,
+
+	kind: 'write:user-groups',
+
+	params: {
+		groupId: {
+			validator: $.type(ID),
+			desc: {
+				'ja-JP': '対象となるユーザーグループのID',
+				'en-US': 'ID of target user group'
+			}
+		},
+
+		name: {
+			validator: $.str.range(1, 100),
+			desc: {
+				'ja-JP': 'このユーザーグループの名前',
+				'en-US': 'name of this user group'
+			}
+		}
+	},
+
+	errors: {
+		noSuchGroup: {
+			message: 'No such group.',
+			code: 'NO_SUCH_GROUP',
+			id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6'
+		},
+	}
+};
+
+export default define(meta, async (ps, me) => {
+	// Fetch the group
+	const userGroup = await UserGroups.findOne({
+		id: ps.groupId,
+		userId: me.id
+	});
+
+	if (userGroup == null) {
+		throw new ApiError(meta.errors.noSuchGroup);
+	}
+
+	await UserGroups.update(userGroup.id, {
+		name: ps.name
+	});
+
+	return await UserGroups.pack(userGroup.id);
+});