diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2dbc2bb1f..923e8680f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,10 +14,11 @@ You should also include the user name that made the change.
 
 ### Improvements
 - ユーザーごとにRenoteをミュートできるように
+- ノートごとに絵文字リアクションを受け取るか設定できるように
 - enhance(client): DM作成時にメンションも含むように
 
 ### Bugfixes
-- 
+-
 
 ## 13.9.2 (2023/03/06)
 
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 47e2af713b..750da3e98a 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -961,6 +961,9 @@ invitationRequiredToRegister: "現在このサーバーは招待制です。招
 emailNotSupported: "このサーバーではメール配信はサポートされていません"
 postToTheChannel: "チャンネルに投稿"
 cannotBeChangedLater: "後から変更できません。"
+reactionAcceptance: "リアクションの受け入れ"
+likeOnly: "いいねのみ"
+likeOnlyForRemote: "リモートからはいいねのみ"
 
 _achievements:
   earnedAt: "獲得日時"
diff --git a/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js b/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js
new file mode 100644
index 0000000000..f1765dd146
--- /dev/null
+++ b/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js
@@ -0,0 +1,11 @@
+export class perNoteReactionAcceptance1678164627293 {
+    name = 'perNoteReactionAcceptance1678164627293'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" ADD "reactionAcceptance" character varying(64)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "reactionAcceptance"`);
+    }
+}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 4c4261ba79..8d8535ca5b 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -125,6 +125,7 @@ type Option = {
 	files?: DriveFile[] | null;
 	poll?: IPoll | null;
 	localOnly?: boolean | null;
+	reactionAcceptance?: Note['reactionAcceptance'];
 	cw?: string | null;
 	visibility?: string;
 	visibleUsers?: MinimumUser[] | null;
@@ -346,6 +347,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 			emojis,
 			userId: user.id,
 			localOnly: data.localOnly!,
+			reactionAcceptance: data.reactionAcceptance,
 			visibility: data.visibility as any,
 			visibleUserIds: data.visibility === 'specified'
 				? data.visibleUsers
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 9fccc14ee4..3e644018d7 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -99,8 +99,12 @@ export class ReactionService {
 			throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
 		}
 	
-		// TODO: cache
-		reaction = await this.toDbReaction(reaction, user.host);
+		if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
+			reaction = '❤️';
+		} else {
+			// TODO: cache
+			reaction = await this.toDbReaction(reaction, user.host);
+		}
 	
 		const record: NoteReaction = {
 			id: this.idService.genId(),
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 4ec10df9a6..67850ad9aa 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -314,6 +314,7 @@ export class NoteEntityService implements OnModuleInit {
 			cw: note.cw,
 			visibility: note.visibility,
 			localOnly: note.localOnly ?? undefined,
+			reactionAcceptance: note.reactionAcceptance,
 			visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
 			renoteCount: note.renoteCount,
 			repliesCount: note.repliesCount,
diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 0681cdb67e..9b8af6958f 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -95,7 +95,7 @@ export interface Schema extends OfSchema {
 	readonly example?: any;
 	readonly format?: string;
 	readonly ref?: keyof typeof refs;
-	readonly enum?: ReadonlyArray<string>;
+	readonly enum?: ReadonlyArray<string | null>;
 	readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
 	readonly maxLength?: number;
 	readonly minLength?: number;
@@ -161,7 +161,7 @@ export type SchemaTypeDef<p extends Schema> =
 	p['type'] extends 'integer' ? number :
 	p['type'] extends 'number' ? number :
 	p['type'] extends 'string' ? (
-		p['enum'] extends readonly string[] ?
+		p['enum'] extends readonly (string | null)[] ?
 		p['enum'][number] :
 		p['format'] extends 'date-time' ? string : // Dateにする??
 		string
diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts
index 82d042f0ce..df508b4dca 100644
--- a/packages/backend/src/models/entities/Note.ts
+++ b/packages/backend/src/models/entities/Note.ts
@@ -87,6 +87,11 @@ export class Note {
 	})
 	public localOnly: boolean;
 
+	@Column('varchar', {
+		length: 64, nullable: true,
+	})
+	public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
+
 	@Column('smallint', {
 		default: 0,
 	})
diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts
index 72c0c62285..58ef425dcd 100644
--- a/packages/backend/src/models/schema/note.ts
+++ b/packages/backend/src/models/schema/note.ts
@@ -141,6 +141,10 @@ export const packedNoteSchema = {
 			type: 'boolean',
 			optional: true, nullable: false,
 		},
+		reactionAcceptance: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
 		reactions: {
 			type: 'object',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
index c65f0a97a0..e9330772b9 100644
--- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
@@ -148,6 +148,7 @@ function serialize(favorite: NoteFavorite & { note: Note & { user: User } }, pol
 			visibility: favorite.note.visibility,
 			visibleUserIds: favorite.note.visibleUserIds,
 			localOnly: favorite.note.localOnly,
+			reactionAcceptance: favorite.note.reactionAcceptance,
 			uri: favorite.note.uri,
 			url: favorite.note.url,
 			user: {
diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
index 3f4f16a2ec..2f74dd63cc 100644
--- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
@@ -10,10 +10,10 @@ import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
 import type { Poll } from '@/models/entities/Poll.js';
 import type { Note } from '@/models/entities/Note.js';
+import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type Bull from 'bull';
 import type { DbUserJobData } from '../types.js';
-import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class ExportNotesProcessorService {
@@ -141,5 +141,6 @@ function serialize(note: Note, poll: Poll | null = null): Record<string, unknown
 		visibility: note.visibility,
 		visibleUserIds: note.visibleUserIds,
 		localOnly: note.localOnly,
+		reactionAcceptance: note.reactionAcceptance,
 	};
 }
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 786ad103b0..69fafcb9c7 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -97,6 +97,7 @@ export const paramDef = {
 		} },
 		cw: { type: 'string', nullable: true, maxLength: 100 },
 		localOnly: { type: 'boolean', default: false },
+		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null },
 		noExtractMentions: { type: 'boolean', default: false },
 		noExtractHashtags: { type: 'boolean', default: false },
 		noExtractEmojis: { type: 'boolean', default: false },
@@ -110,7 +111,7 @@ export const paramDef = {
 			type: 'string',
 			minLength: 1,
 			maxLength: MAX_NOTE_TEXT_LENGTH,
-			nullable: false
+			nullable: false,
 		},
 		fileIds: {
 			type: 'array',
@@ -280,6 +281,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				renote,
 				cw: ps.cw,
 				localOnly: ps.localOnly,
+				reactionAcceptance: ps.reactionAcceptance,
 				visibility: ps.visibility,
 				visibleUsers,
 				channel,
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index bb1269562d..af81051a54 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -103,7 +103,8 @@
 					<i class="ti ti-ban"></i>
 				</button>
 				<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
-					<i class="ti ti-plus"></i>
+					<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+					<i v-else class="ti ti-plus"></i>
 				</button>
 				<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
@@ -329,18 +330,32 @@ function reply(viaKeyboard = false): void {
 
 function react(viaKeyboard = false): void {
 	pleaseLogin();
-	blur();
-	reactionPicker.show(reactButton.value, reaction => {
+	if (appearNote.reactionAcceptance === 'likeOnly') {
 		os.api('notes/reactions/create', {
 			noteId: appearNote.id,
-			reaction: reaction,
+			reaction: '❤️',
 		});
-		if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
-			claimAchievement('reactWithoutRead');
+		const el = reactButton.value as HTMLElement | null | undefined;
+		if (el) {
+			const rect = el.getBoundingClientRect();
+			const x = rect.left + (el.offsetWidth / 2);
+			const y = rect.top + (el.offsetHeight / 2);
+			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
-	}, () => {
-		focus();
-	});
+	} else {
+		blur();
+		reactionPicker.show(reactButton.value, reaction => {
+			os.api('notes/reactions/create', {
+				noteId: appearNote.id,
+				reaction: reaction,
+			});
+			if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
+				claimAchievement('reactWithoutRead');
+			}
+		}, () => {
+			focus();
+		});
+	}
 }
 
 function undoReact(note): void {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index f5f4a2afc1..ea72e1b517 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -108,7 +108,8 @@
 					<i class="ti ti-ban"></i>
 				</button>
 				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
-					<i class="ti ti-plus"></i>
+					<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+					<i v-else class="ti ti-plus"></i>
 				</button>
 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
@@ -323,18 +324,32 @@ function reply(viaKeyboard = false): void {
 
 function react(viaKeyboard = false): void {
 	pleaseLogin();
-	blur();
-	reactionPicker.show(reactButton.value, reaction => {
+	if (appearNote.reactionAcceptance === 'likeOnly') {
 		os.api('notes/reactions/create', {
 			noteId: appearNote.id,
-			reaction: reaction,
+			reaction: '❤️',
 		});
-		if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
-			claimAchievement('reactWithoutRead');
+		const el = reactButton.value as HTMLElement | null | undefined;
+		if (el) {
+			const rect = el.getBoundingClientRect();
+			const x = rect.left + (el.offsetWidth / 2);
+			const y = rect.top + (el.offsetHeight / 2);
+			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
-	}, () => {
-		focus();
-	});
+	} else {
+		blur();
+		reactionPicker.show(reactButton.value, reaction => {
+			os.api('notes/reactions/create', {
+				noteId: appearNote.id,
+				reaction: reaction,
+			});
+			if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
+				claimAchievement('reactWithoutRead');
+			}
+		}, () => {
+			focus();
+		});
+	}
 }
 
 function undoReact(note): void {
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index f610569f6d..ee5a9e1810 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -53,14 +53,23 @@
 		<XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
 		<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
 		<XNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
+		<div v-if="showingOptions" style="padding: 0 16px;">
+			<MkSelect v-model="reactionAcceptance" small>
+				<template #label>{{ i18n.ts.reactionAcceptance }}</template>
+				<option :value="null">{{ i18n.ts.all }}</option>
+				<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
+				<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
+			</MkSelect>
+		</div>
+		<button v-tooltip="i18n.ts.emoji" class="_button" :class="$style.emojiButton" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
 		<footer :class="$style.footer">
 			<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button>
 			<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
 			<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
 			<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
 			<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
-			<button v-tooltip="i18n.ts.emoji" class="_button" :class="$style.footerButton" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
 			<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
+			<button v-tooltip="i18n.ts.more" class="_button" :class="$style.footerButton" @click="showingOptions = !showingOptions"><i class="ti ti-dots"></i></button>
 		</footer>
 		<datalist id="hashtags">
 			<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
@@ -76,6 +85,7 @@ import * as misskey from 'misskey-js';
 import insertTextAtCursor from 'insert-text-at-cursor';
 import { toASCII } from 'punycode/';
 import * as Acct from 'misskey-js/built/acct';
+import MkSelect from './MkSelect.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import XNotePreview from '@/components/MkNotePreview.vue';
 import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
@@ -151,12 +161,14 @@ let visibleUsers = $ref([]);
 if (props.initialVisibleUsers) {
 	props.initialVisibleUsers.forEach(pushVisibleUser);
 }
+let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance);
 let autocomplete = $ref(null);
 let draghover = $ref(false);
 let quoteId = $ref(null);
 let hasNotSpecifiedMentions = $ref(false);
 let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
 let imeText = $ref('');
+let showingOptions = $ref(false);
 
 const draftKey = $computed((): string => {
 	let key = props.channel ? `channel:${props.channel.id}` : '';
@@ -614,6 +626,7 @@ async function post(ev?: MouseEvent) {
 		localOnly: localOnly,
 		visibility: visibility,
 		visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
+		reactionAcceptance,
 	};
 
 	if (withHashtags && hashtags && hashtags.trim() !== '') {
@@ -1030,6 +1043,18 @@ defineExpose({
 	}
 }
 
+.emojiButton {
+	position: absolute;
+	top: 55px;
+	right: 13px;
+	display: inline-block;
+	padding: 0;
+	margin: 0;
+	font-size: 1em;
+	width: 32px;
+	height: 32px;
+}
+
 @container (max-width: 500px) {
 	.header {
 		height: 50px;
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 41563c441f..4776a87d5b 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -64,12 +64,19 @@
 		</div>
 	</MkFolder>
 
+	<MkSelect v-model="reactionAcceptance">
+		<template #label>{{ i18n.ts.reactionAcceptance }}</template>
+		<option :value="null">{{ i18n.ts.all }}</option>
+		<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
+		<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
+	</MkSelect>
+
 	<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { reactive, watch } from 'vue';
+import { computed, reactive, watch } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -85,6 +92,9 @@ import { $i } from '@/account';
 import { langmap } from '@/scripts/langmap';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { claimAchievement } from '@/scripts/achievements';
+import { defaultStore } from '@/store';
+
+const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
 
 const profile = reactive({
 	name: $i.name,
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index d84c056538..a68386fa4f 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -81,6 +81,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
 	},
+	reactionAcceptance: {
+		where: 'account',
+		default: null,
+	},
 	mutedWords: {
 		where: 'account',
 		default: [],