diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d103c8f5e24ce2fa5989c36dad96f011c133d40..025ed45dd547ca733072cc7460b19957da1968e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,9 @@
 -->
 
 ## 2023.10.1
+### General
+- Enhance: ローカルタイムライン、ソーシャルタイムラインで返信を含むかどうか設定可能に
+
 ### Server
 - Fix: フォローしているユーザーからの自分の投稿への返信がタイムラインに含まれない問題を修正
 
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 5227ea7323de669af6660e5f7cd6396d281fd6ff..64d2880ba161e18874a143f116c7ef0b5f6efd57 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -907,6 +907,10 @@ export class NoteCreateService implements OnApplicationShutdown {
 			// 自分自身以外への返信
 			if (note.replyId && note.replyUserId !== note.userId) {
 				this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+
+				if (note.visibility === 'public' && note.userHost == null) {
+					this.redisTimelineService.push('localTimelineWithReplies', note.id, 300, r);
+				}
 			} else {
 				this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
 				if (note.fileIds.length > 0) {
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 1b77285d47ccbf95c6aa95df2d6c0cecf58951a0..8ac5f1b038dd41c36cb46b2b7ccb2aaadea32378 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -55,6 +55,7 @@ export const paramDef = {
 		includeLocalRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
+		withReplies: { type: 'boolean', default: false },
 	},
 	required: [],
 } as const;
@@ -94,12 +95,29 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				this.cacheService.userBlockedCache.fetch(me.id),
 			]);
 
-			const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
-				ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
-				ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
-			], untilId, sinceId);
+			let noteIds: string[];
+
+			if (ps.withFiles) {
+				const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
+					`homeTimelineWithFiles:${me.id}`,
+					'localTimelineWithFiles',
+				], untilId, sinceId);
+				noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
+			} else if (ps.withReplies) {
+				const [htlNoteIds, ltlNoteIds, ltlReplyNoteIds] = await this.redisTimelineService.getMulti([
+					`homeTimeline:${me.id}`,
+					'localTimeline',
+					'localTimelineWithReplies',
+				], untilId, sinceId);
+				noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds, ...ltlReplyNoteIds]));
+			} else {
+				const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
+					`homeTimeline:${me.id}`,
+					'localTimeline',
+				], untilId, sinceId);
+				noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
+			}
 
-			let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
 			noteIds.sort((a, b) => a > b ? -1 : 1);
 			noteIds = noteIds.slice(0, ps.limit);
 
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 2357f32d5e9bb6f02aa624f581d6630c231aafa2..55b5d4738660fcd1b818475529c0baa3820e8320 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -45,6 +45,7 @@ export const paramDef = {
 	properties: {
 		withFiles: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
+		withReplies: { type: 'boolean', default: false },
 		excludeNsfw: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -90,7 +91,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				this.cacheService.userBlockedCache.fetch(me.id),
 			]) : [new Set<string>(), new Set<string>(), new Set<string>()];
 
-			let noteIds = await this.redisTimelineService.get(ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', untilId, sinceId);
+			let noteIds: string[];
+
+			if (ps.withFiles) {
+				noteIds = await this.redisTimelineService.get('localTimelineWithFiles', untilId, sinceId);
+			} else if (ps.withReplies) {
+				const [nonReplyNoteIds, replyNoteIds] = await this.redisTimelineService.getMulti([
+					'localTimeline',
+					'localTimelineWithReplies',
+				], untilId, sinceId);
+				noteIds = Array.from(new Set([...nonReplyNoteIds, ...replyNoteIds]));
+				noteIds.sort((a, b) => a > b ? -1 : 1);
+			} else {
+				noteIds = await this.redisTimelineService.get('localTimeline', untilId, sinceId);
+			}
+
 			noteIds = noteIds.slice(0, ps.limit);
 
 			if (noteIds.length === 0) {
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index d5f5d54e46e01ab16bdcedb4c0657fa95faf680f..adedca51520b151cc16d7b24dc6a8cc000b0db9c 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -19,6 +19,7 @@ class HybridTimelineChannel extends Channel {
 	public static shouldShare = false;
 	public static requireCredential = true;
 	private withRenotes: boolean;
+	private withReplies: boolean;
 	private withFiles: boolean;
 
 	constructor(
@@ -39,6 +40,7 @@ class HybridTimelineChannel extends Channel {
 		if (!policies.ltlAvailable) return;
 
 		this.withRenotes = params.withRenotes ?? true;
+		this.withReplies = params.withReplies ?? false;
 		this.withFiles = params.withFiles ?? false;
 
 		// Subscribe events
@@ -87,7 +89,7 @@ class HybridTimelineChannel extends Channel {
 		if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
 
 		// 関係ない返信は除外
-		if (note.reply && !this.following[note.userId]?.withReplies) {
+		if (note.reply && !this.following[note.userId]?.withReplies && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index 94c22f8915afeb7b0a42c4cf0e4994905d885cce..69aa366f002e1773a37a4414f0b36a747845b609 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -18,6 +18,7 @@ class LocalTimelineChannel extends Channel {
 	public static shouldShare = false;
 	public static requireCredential = false;
 	private withRenotes: boolean;
+	private withReplies: boolean;
 	private withFiles: boolean;
 
 	constructor(
@@ -38,6 +39,7 @@ class LocalTimelineChannel extends Channel {
 		if (!policies.ltlAvailable) return;
 
 		this.withRenotes = params.withRenotes ?? true;
+		this.withReplies = params.withReplies ?? false;
 		this.withFiles = params.withFiles ?? false;
 
 		// Subscribe events
@@ -66,7 +68,7 @@ class LocalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && this.user && !this.following[note.userId]?.withReplies) {
+		if (note.reply && this.user && !this.following[note.userId]?.withReplies && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index 25143c2714e89cb46ba2b5aa7bd1240b5abb5963..a5d7a79fa5ce133504e105ce4735cb8fe61f01d6 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -512,6 +512,20 @@ describe('Timelines', () => {
 			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
 		});
 
+		test.concurrent('他人の他人への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+		});
+
 		test.concurrent('チャンネル投稿が含まれない', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup()]);
 
@@ -623,6 +637,20 @@ describe('Timelines', () => {
 		});
 		*/
 
+		test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', { withReplies: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+		});
+
 		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup()]);
 
@@ -694,6 +722,20 @@ describe('Timelines', () => {
 			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
 		});
 
+		test.concurrent('他人の他人への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', { }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+		});
+
 		test.concurrent('リモートユーザーのノートが含まれない', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
 
@@ -734,6 +776,20 @@ describe('Timelines', () => {
 			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
 		});
 
+		test.concurrent('[withReplies: true] 他人の他人への返信が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', { withReplies: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+		});
+
 		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup()]);
 
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 45dedd5042c1b0a9e9efb1ce4b25ed8ec6e94af3..cdd72febd1dc8f45b70726d8a046f99307d899e2 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -24,9 +24,11 @@ const props = withDefaults(defineProps<{
 	role?: string;
 	sound?: boolean;
 	withRenotes?: boolean;
+	withReplies?: boolean;
 	onlyFiles?: boolean;
 }>(), {
 	withRenotes: true,
+	withReplies: false,
 	onlyFiles: false,
 });
 
@@ -90,10 +92,12 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/local-timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('localTimeline', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -101,10 +105,12 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/hybrid-timeline';
 	query = {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('hybridTimeline', {
 		withRenotes: props.withRenotes,
+		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index b8deb779524146995c79624ecc1e0bcd88f3a685..c88be2c839a5cbef54c145ae97e5bafc51e46288 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -15,10 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div :class="$style.tl">
 				<MkTimeline
 					ref="tlComponent"
-					:key="src + withRenotes + onlyFiles"
+					:key="src + withRenotes + withReplies + onlyFiles"
 					:src="src.split(':')[0]"
 					:list="src.split(':')[1]"
 					:withRenotes="withRenotes"
+					:withReplies="withReplies"
 					:onlyFiles="onlyFiles"
 					:sound="true"
 					@queue="queueUpdated"
@@ -61,6 +62,7 @@ let queue = $ref(0);
 let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
 const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
 const withRenotes = $ref(true);
+const withReplies = $ref(false);
 const onlyFiles = $ref(false);
 
 watch($$(src), () => queue = 0);
@@ -142,7 +144,11 @@ const headerActions = $computed(() => [{
 			text: i18n.ts.showRenotes,
 			icon: 'ti ti-repeat',
 			ref: $$(withRenotes),
-		}, {
+		}, src === 'local' || src === 'social' ? {
+			type: 'switch',
+			text: i18n.ts.showRepliesToOthersInTimeline,
+			ref: $$(withReplies),
+		} : undefined, {
 			type: 'switch',
 			text: i18n.ts.fileAttachedOnly,
 			icon: 'ti ti-photo',
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index b2a44ac96b12aae611b5e196adeae93613ffe651..49fdf4d314659bd1a367a36479b9bd89da90aa97 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -31,6 +31,7 @@ export type Column = {
 	excludeTypes?: typeof notificationTypes[number][];
 	tl?: 'home' | 'local' | 'social' | 'global';
 	withRenotes?: boolean;
+	withReplies?: boolean;
 	onlyFiles?: boolean;
 };
 
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 847752247e982ee029c8cc142a36f9a9d0e6b106..bab93622f04654b6291031c3d69431ec95210a22 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -23,9 +23,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkTimeline
 		v-else-if="column.tl"
 		ref="timeline"
-		:key="column.tl + withRenotes + onlyFiles"
+		:key="column.tl + withRenotes + withReplies + onlyFiles"
 		:src="column.tl"
 		:withRenotes="withRenotes"
+		:withReplies="withReplies"
 		:onlyFiles="onlyFiles"
 	/>
 </XColumn>
@@ -51,6 +52,7 @@ let disabled = $ref(false);
 const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
 const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
 const withRenotes = $ref(props.column.withRenotes ?? true);
+const withReplies = $ref(props.column.withReplies ?? false);
 const onlyFiles = $ref(props.column.onlyFiles ?? false);
 
 watch($$(withRenotes), v => {
@@ -107,7 +109,11 @@ const menu = [{
 	type: 'switch',
 	text: i18n.ts.showRenotes,
 	ref: $$(withRenotes),
-}, {
+}, props.column.tl === 'local' || props.column.tl === 'social' ? {
+	type: 'switch',
+	text: i18n.ts.showRepliesToOthersInTimeline,
+	ref: $$(withReplies),
+} : undefined, {
 	type: 'switch',
 	text: i18n.ts.fileAttachedOnly,
 	ref: $$(onlyFiles),