diff --git a/locales/en-US.yml b/locales/en-US.yml
index 2e6cb32e31..e0c9c551e9 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -164,6 +164,8 @@ flagAsBot: "Mark this account as a bot"
 flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Misskey's internal systems to treat this account as a bot."
 flagAsCat: "Mark this account as a cat"
 flagAsCatDescription: "Enable this option to mark this account as a cat."
+flagSpeakAsCat: "Speak as a cat"
+flagSpeakAsCatDescription: "Your posts will get nyanified when in cat mode"
 flagShowTimelineReplies: "Show replies in timeline"
 flagShowTimelineRepliesDescription: "Shows replies of users to notes of other users in the timeline if turned on."
 autoAcceptFollowed: "Automatically approve follow requests from users you're following"
diff --git a/packages/backend/migration/1696386694000-speakAsCat.js b/packages/backend/migration/1696386694000-speakAsCat.js
new file mode 100644
index 0000000000..d68b9401bf
--- /dev/null
+++ b/packages/backend/migration/1696386694000-speakAsCat.js
@@ -0,0 +1,12 @@
+export class SpeakAsCat1696386694000 {
+	name = "SpeakAsCat1696386694000";
+
+	async up(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "user" ADD "speakAsCat" boolean NOT NULL DEFAULT true`);
+		await queryRunner.query(`COMMENT ON COLUMN "user"."speakAsCat" IS 'Whether to speak as a cat if chosen.'`);
+	}
+
+	async down(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "speakAsCat"`);
+	}
+}
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 43e4ddbf32..155aee39a9 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -501,6 +501,7 @@ export class ApRendererService {
 			discoverable: user.isExplorable,
 			publicKey: this.renderKey(user, keypair, '#main-key'),
 			isCat: user.isCat,
+			speakAsCat: user.speakAsCat,
 			attachment: attachment.length ? attachment : undefined,
 		};
 
@@ -646,6 +647,9 @@ export class ApRendererService {
 					'_misskey_reaction': 'misskey:_misskey_reaction',
 					'_misskey_votes': 'misskey:_misskey_votes',
 					'isCat': 'misskey:isCat',
+					// Firefish
+					firefish: "https://joinfirefish.org/ns#",
+					speakAsCat: "firefish:speakAsCat",
 					// vcard
 					vcard: 'http://www.w3.org/2006/vcard/ns#',
 				},
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 2469583e5a..a68e38cd66 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -326,6 +326,7 @@ export class ApPersonService implements OnModuleInit {
 					tags,
 					isBot,
 					isCat: (person as any).isCat === true,
+					speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true,
 					emojis,
 				})) as MiRemoteUser;
 
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 026f84bb39..7ba7bce41d 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -364,7 +364,7 @@ export class NoteEntityService implements OnModuleInit {
 			} : {}),
 		});
 
-		if (packed.user.isCat && packed.text) {
+		if (packed.user.speakAsCat && packed.text) {
 			const tokens = packed.text ? mfm.parse(packed.text) : [];
 			function nyaizeNode(node: mfm.MfmNode) {
 				if (node.type === 'quote') return;
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index cdd1182f6d..714459d76b 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -352,6 +352,7 @@ export class UserEntityService implements OnModuleInit {
 			createdAt: user.createdAt.toISOString(),
 			isBot: user.isBot ?? falsy,
 			isCat: user.isCat ?? falsy,
+			speakAsCat: user.speakAsCat ?? falsy,
 			instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
 				name: instance.name,
 				softwareName: instance.softwareName,
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 8f0122a90c..d1dd159192 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -174,6 +174,12 @@ export class MiUser {
 	})
 	public isCat: boolean;
 
+	@Column('boolean', {
+		default: false,
+		comment: 'Whether the User speaks in nya.',
+	})
+	public speakAsCat: boolean;
+
 	@Column('boolean', {
 		default: false,
 		comment: 'Whether the User is the root.',
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 79b14bb65f..a8fb34acb1 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -55,6 +55,10 @@ export const packedUserLiteSchema = {
 			type: 'boolean',
 			nullable: false, optional: true,
 		},
+		speakAsCat: {
+			type: 'boolean',
+			nullable: false, optional: true,
+		},
 		onlineStatus: {
 			type: 'string',
 			format: 'url',
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index e5bf27d227..93897b9c8f 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -156,6 +156,7 @@ export const paramDef = {
 		preventAiLearning: { type: 'boolean' },
 		isBot: { type: 'boolean' },
 		isCat: { type: 'boolean' },
+		speakAsCat: { type: 'boolean' },
 		injectFeaturedNote: { type: 'boolean' },
 		receiveAnnouncementEmail: { type: 'boolean' },
 		alwaysMarkNsfw: { type: 'boolean' },
@@ -259,6 +260,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
 			if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
 			if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
+			if (typeof ps.speakAsCat === 'boolean') updates.speakAsCat = ps.speakAsCat;
 			if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
 			if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
 			if (typeof ps.alwaysMarkNsfw === 'boolean') {
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 0f5d5f7344..93fbdaaa32 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -70,6 +70,7 @@ describe('ユーザー', () => {
 			avatarBlurhash: user.avatarBlurhash,
 			isBot: user.isBot,
 			isCat: user.isCat,
+			speakAsCat: user.speakAsCat,
 			instance: user.instance,
 			emojis: user.emojis,
 			onlineStatus: user.onlineStatus,
@@ -350,6 +351,7 @@ describe('ユーザー', () => {
 		assert.strictEqual(response.avatarBlurhash, null);
 		assert.strictEqual(response.isBot, false);
 		assert.strictEqual(response.isCat, false);
+		assert.strictEqual(response.speakAsCat, false);
 		assert.strictEqual(response.instance, undefined);
 		assert.deepStrictEqual(response.emojis, {});
 		assert.strictEqual(response.onlineStatus, 'unknown');
@@ -481,6 +483,8 @@ describe('ユーザー', () => {
 		{ parameters: (): object => ({ isBot: false }) },
 		{ parameters: (): object => ({ isCat: true }) },
 		{ parameters: (): object => ({ isCat: false }) },
+		{ parameters: (): object => ({ speakAsCat: true }) },
+		{ parameters: (): object => ({ speakAsCat: false }) },
 		{ parameters: (): object => ({ injectFeaturedNote: true }) },
 		{ parameters: (): object => ({ injectFeaturedNote: false }) },
 		{ parameters: (): object => ({ receiveAnnouncementEmail: true }) },
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index 811c243926..47f9a53dad 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -99,6 +99,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi
 		isBlocking: false,
 		isBot: false,
 		isCat: false,
+		speakAsCat: false,
 		isFollowed: false,
 		isFollowing: false,
 		isLocked: false,
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index b6377d8b9b..fc406efae8 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -93,6 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 		<div class="_gaps_m">
 			<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
+			<MkSwitch v-model="profile.speakAsCat">{{ i18n.ts.flagSpeakAsCat }}<template #caption>{{ i18n.ts.flagSpeakAsCatDescription }}</template></MkSwitch>
 			<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
 		</div>
 	</MkFolder>
@@ -141,6 +142,7 @@ const profile = reactive({
 	lang: $i.lang,
 	isBot: $i.isBot,
 	isCat: $i.isCat,
+	speakAsCat: $i.speakAsCat,
 });
 
 watch(() => profile, () => {
@@ -190,6 +192,7 @@ function save() {
 		lang: profile.lang || null,
 		isBot: !!profile.isBot,
 		isCat: !!profile.isCat,
+		speakAsCat: !!profile.speakAsCat,
 	});
 	claimAchievement('profileFilled');
 	if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
diff --git a/packages/megalodon/src/misskey/entities/userDetail.ts b/packages/megalodon/src/misskey/entities/userDetail.ts
index bf0e3c2c29..0a59278605 100644
--- a/packages/megalodon/src/misskey/entities/userDetail.ts
+++ b/packages/megalodon/src/misskey/entities/userDetail.ts
@@ -13,6 +13,7 @@ namespace MisskeyEntity {
     isModerator: boolean
     isBot: boolean
     isCat: boolean
+    speakAsCat: boolean
     emojis: Array<Emoji> | { [key: string]: string }
     createdAt: string
     bannerUrl: string
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index f0fc47c207..5fb80c6a3f 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1541,6 +1541,7 @@ export type Endpoints = {
             noCrawle?: boolean;
             isBot?: boolean;
             isCat?: boolean;
+            speakAsCat?: boolean;
             injectFeaturedNote?: boolean;
             receiveAnnouncementEmail?: boolean;
             alwaysMarkNsfw?: boolean;
@@ -2915,6 +2916,7 @@ type UserDetailed = UserLite & {
     isBlocking: boolean;
     isBot: boolean;
     isCat: boolean;
+    speakAsCat: boolean;
     isFollowed: boolean;
     isFollowing: boolean;
     isLocked: boolean;
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index 7a8b6872dc..b3837369ec 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -426,6 +426,7 @@ export type Endpoints = {
 		noCrawle?: boolean;
 		isBot?: boolean;
 		isCat?: boolean;
+		speakAsCat?: boolean;
 		injectFeaturedNote?: boolean;
 		receiveAnnouncementEmail?: boolean;
 		alwaysMarkNsfw?: boolean;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 301d4bc3ac..59df4582de 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -50,6 +50,7 @@ export type UserDetailed = UserLite & {
 	isBlocking: boolean;
 	isBot: boolean;
 	isCat: boolean;
+	speakAsCat: boolean;
 	isFollowed: boolean;
 	isFollowing: boolean;
 	isLocked: boolean;