diff --git a/CHANGELOG.md b/CHANGELOG.md
index be47a3db27..711d9db62a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,8 @@
 ## 13.x.x (unreleased)
 
 ### General
--
+- チャンネルをお気に入りに登録できるように
+- チャンネルにノートをピン留めできるように
 
 ### Client
 - 検索ページでURLを入力した際に照会したときと同等の挙動をするように
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 67f0634346..a9c54810ac 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -984,6 +984,7 @@ enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
 enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
 showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 largeNoteReactions: "ノートのリアクションを大きく表示"
+noteIdOrUrl: "ノートIDまたはURL"
 
 _achievements:
   earnedAt: "獲得日時"
diff --git a/packages/backend/migration/1680228513388-channelFavorite.js b/packages/backend/migration/1680228513388-channelFavorite.js
new file mode 100644
index 0000000000..afc676959a
--- /dev/null
+++ b/packages/backend/migration/1680228513388-channelFavorite.js
@@ -0,0 +1,21 @@
+export class channelFavorite1680228513388 {
+    name = 'channelFavorite1680228513388'
+
+    async up(queryRunner) {
+        await queryRunner.query(`CREATE TABLE "channel_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "channelId" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, CONSTRAINT "PK_59bddfd54d48689a298d41af00c" PRIMARY KEY ("id")); COMMENT ON COLUMN "channel_favorite"."createdAt" IS 'The created date of the ChannelFavorite.'`);
+        await queryRunner.query(`CREATE INDEX "IDX_735a5544f9249d412255f47f95" ON "channel_favorite" ("createdAt") `);
+        await queryRunner.query(`CREATE INDEX "IDX_d3ca0db011b75ac2a940a2337d" ON "channel_favorite" ("channelId") `);
+        await queryRunner.query(`CREATE INDEX "IDX_8302bd27226605ece14842fb25" ON "channel_favorite" ("userId") `);
+        await queryRunner.query(`ALTER TABLE "channel_favorite" ADD CONSTRAINT "FK_d3ca0db011b75ac2a940a2337d2" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE "channel_favorite" ADD CONSTRAINT "FK_8302bd27226605ece14842fb25a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "channel_favorite" DROP CONSTRAINT "FK_8302bd27226605ece14842fb25a"`);
+        await queryRunner.query(`ALTER TABLE "channel_favorite" DROP CONSTRAINT "FK_d3ca0db011b75ac2a940a2337d2"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_8302bd27226605ece14842fb25"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_d3ca0db011b75ac2a940a2337d"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_735a5544f9249d412255f47f95"`);
+        await queryRunner.query(`DROP TABLE "channel_favorite"`);
+    }
+}
diff --git a/packages/backend/migration/1680238118084-channelNotePining.js b/packages/backend/migration/1680238118084-channelNotePining.js
new file mode 100644
index 0000000000..126eae87ea
--- /dev/null
+++ b/packages/backend/migration/1680238118084-channelNotePining.js
@@ -0,0 +1,11 @@
+export class channelNotePining1680238118084 {
+    name = 'channelNotePining1680238118084'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "channel" ADD "pinnedNoteIds" character varying(128) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "pinnedNoteIds"`);
+    }
+}
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 6048492f09..72e9b25544 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -1,13 +1,14 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import type { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js';
+import type { ChannelFavoritesRepository, ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository, NotesRepository } from '@/models/index.js';
 import type { Packed } from '@/misc/json-schema.js';
 import type { } from '@/models/entities/Blocking.js';
 import type { User } from '@/models/entities/User.js';
 import type { Channel } from '@/models/entities/Channel.js';
 import { bindThis } from '@/decorators.js';
-import { UserEntityService } from './UserEntityService.js';
 import { DriveFileEntityService } from './DriveFileEntityService.js';
+import { NoteEntityService } from './NoteEntityService.js';
+import { In } from 'typeorm';
 
 @Injectable()
 export class ChannelEntityService {
@@ -18,13 +19,19 @@ export class ChannelEntityService {
 		@Inject(DI.channelFollowingsRepository)
 		private channelFollowingsRepository: ChannelFollowingsRepository,
 
+		@Inject(DI.channelFavoritesRepository)
+		private channelFavoritesRepository: ChannelFavoritesRepository,
+
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
 		@Inject(DI.noteUnreadsRepository)
 		private noteUnreadsRepository: NoteUnreadsRepository,
 
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
 
-		private userEntityService: UserEntityService,
+		private noteEntityService: NoteEntityService,
 		private driveFileEntityService: DriveFileEntityService,
 	) {
 	}
@@ -33,6 +40,7 @@ export class ChannelEntityService {
 	public async pack(
 		src: Channel['id'] | Channel,
 		me?: { id: User['id'] } | null | undefined,
+		detailed?: boolean,
 	): Promise<Packed<'Channel'>> {
 		const channel = typeof src === 'object' ? src : await this.channelsRepository.findOneByOrFail({ id: src });
 		const meId = me ? me.id : null;
@@ -46,6 +54,17 @@ export class ChannelEntityService {
 			followeeId: channel.id,
 		}) : null;
 
+		const favorite = meId ? await this.channelFavoritesRepository.findOneBy({
+			userId: meId,
+			channelId: channel.id,
+		}) : null;
+
+		const pinnedNotes = channel.pinnedNoteIds.length > 0 ? await this.notesRepository.find({
+			where: {
+				id: In(channel.pinnedNoteIds),
+			},
+		}) : [];
+
 		return {
 			id: channel.id,
 			createdAt: channel.createdAt.toISOString(),
@@ -54,13 +73,19 @@ export class ChannelEntityService {
 			description: channel.description,
 			userId: channel.userId,
 			bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
+			pinnedNoteIds: channel.pinnedNoteIds,
 			usersCount: channel.usersCount,
 			notesCount: channel.notesCount,
 
 			...(me ? {
 				isFollowing: following != null,
+				isFavorited: favorite != null,
 				hasUnreadNote,
 			} : {}),
+
+			...(detailed ? {
+				pinnedNotes: await this.noteEntityService.packMany(pinnedNotes, me),
+			} : {}),
 		};
 	}
 }
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 0879735b1d..4f475a03ad 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -61,7 +61,7 @@ export const DI = {
 	mutedNotesRepository: Symbol('mutedNotesRepository'),
 	channelsRepository: Symbol('channelsRepository'),
 	channelFollowingsRepository: Symbol('channelFollowingsRepository'),
-	channelNotePiningsRepository: Symbol('channelNotePiningsRepository'),
+	channelFavoritesRepository: Symbol('channelFavoritesRepository'),
 	registryItemsRepository: Symbol('registryItemsRepository'),
 	webhooksRepository: Symbol('webhooksRepository'),
 	adsRepository: Symbol('adsRepository'),
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index d00c8813c7..da7faf9ffb 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -340,9 +340,9 @@ const $channelFollowingsRepository: Provider = {
 	inject: [DI.db],
 };
 
-const $channelNotePiningsRepository: Provider = {
-	provide: DI.channelNotePiningsRepository,
-	useFactory: (db: DataSource) => db.getRepository(ChannelNotePining),
+const $channelFavoritesRepository: Provider = {
+	provide: DI.channelFavoritesRepository,
+	useFactory: (db: DataSource) => db.getRepository(ChannelFavorite),
 	inject: [DI.db],
 };
 
@@ -460,7 +460,7 @@ const $roleAssignmentsRepository: Provider = {
 		$mutedNotesRepository,
 		$channelsRepository,
 		$channelFollowingsRepository,
-		$channelNotePiningsRepository,
+		$channelFavoritesRepository,
 		$registryItemsRepository,
 		$webhooksRepository,
 		$adsRepository,
@@ -528,7 +528,7 @@ const $roleAssignmentsRepository: Provider = {
 		$mutedNotesRepository,
 		$channelsRepository,
 		$channelFollowingsRepository,
-		$channelNotePiningsRepository,
+		$channelFavoritesRepository,
 		$registryItemsRepository,
 		$webhooksRepository,
 		$adsRepository,
diff --git a/packages/backend/src/models/entities/Channel.ts b/packages/backend/src/models/entities/Channel.ts
index a6e32d54f7..2d346fdf9d 100644
--- a/packages/backend/src/models/entities/Channel.ts
+++ b/packages/backend/src/models/entities/Channel.ts
@@ -59,6 +59,11 @@ export class Channel {
 	@JoinColumn()
 	public banner: DriveFile | null;
 
+	@Column('varchar', {
+		array: true, length: 128, default: '{}',
+	})
+	public pinnedNoteIds: string[];
+
 	@Index()
 	@Column('integer', {
 		default: 0,
diff --git a/packages/backend/src/models/entities/ChannelNotePining.ts b/packages/backend/src/models/entities/ChannelFavorite.ts
similarity index 59%
rename from packages/backend/src/models/entities/ChannelNotePining.ts
rename to packages/backend/src/models/entities/ChannelFavorite.ts
index ab5796626a..cfb2c892cf 100644
--- a/packages/backend/src/models/entities/ChannelNotePining.ts
+++ b/packages/backend/src/models/entities/ChannelFavorite.ts
@@ -1,21 +1,24 @@
 import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
 import { id } from '../id.js';
-import { Note } from './Note.js';
+import { User } from './User.js';
 import { Channel } from './Channel.js';
 
 @Entity()
-@Index(['channelId', 'noteId'], { unique: true })
-export class ChannelNotePining {
+@Index(['userId', 'channelId'], { unique: true })
+export class ChannelFavorite {
 	@PrimaryColumn(id())
 	public id: string;
 
+	@Index()
 	@Column('timestamp with time zone', {
-		comment: 'The created date of the ChannelNotePining.',
+		comment: 'The created date of the ChannelFavorite.',
 	})
 	public createdAt: Date;
 
 	@Index()
-	@Column(id())
+	@Column({
+		...id(),
+	})
 	public channelId: Channel['id'];
 
 	@ManyToOne(type => Channel, {
@@ -24,12 +27,15 @@ export class ChannelNotePining {
 	@JoinColumn()
 	public channel: Channel | null;
 
-	@Column(id())
-	public noteId: Note['id'];
+	@Index()
+	@Column({
+		...id(),
+	})
+	public userId: User['id'];
 
-	@ManyToOne(type => Note, {
+	@ManyToOne(type => User, {
 		onDelete: 'CASCADE',
 	})
 	@JoinColumn()
-	public note: Note | null;
+	public user: User | null;
 }
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index 17083d7a01..79bd014cea 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -10,7 +10,7 @@ import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js'
 import { AuthSession } from '@/models/entities/AuthSession.js';
 import { Blocking } from '@/models/entities/Blocking.js';
 import { ChannelFollowing } from '@/models/entities/ChannelFollowing.js';
-import { ChannelNotePining } from '@/models/entities/ChannelNotePining.js';
+import { ChannelFavorite } from '@/models/entities/ChannelFavorite.js';
 import { Clip } from '@/models/entities/Clip.js';
 import { ClipNote } from '@/models/entities/ClipNote.js';
 import { ClipFavorite } from '@/models/entities/ClipFavorite.js';
@@ -79,7 +79,7 @@ export {
 	AuthSession,
 	Blocking,
 	ChannelFollowing,
-	ChannelNotePining,
+	ChannelFavorite,
 	Clip,
 	ClipNote,
 	ClipFavorite,
@@ -147,7 +147,7 @@ export type AttestationChallengesRepository = Repository<AttestationChallenge>;
 export type AuthSessionsRepository = Repository<AuthSession>;
 export type BlockingsRepository = Repository<Blocking>;
 export type ChannelFollowingsRepository = Repository<ChannelFollowing>;
-export type ChannelNotePiningsRepository = Repository<ChannelNotePining>;
+export type ChannelFavoritesRepository = Repository<ChannelFavorite>;
 export type ClipsRepository = Repository<Clip>;
 export type ClipNotesRepository = Repository<ClipNote>;
 export type ClipFavoritesRepository = Repository<ClipFavorite>;
diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts
index 7f4f2a48b8..745b39a6ba 100644
--- a/packages/backend/src/models/json-schema/channel.ts
+++ b/packages/backend/src/models/json-schema/channel.ts
@@ -42,10 +42,22 @@ export const packedChannelSchema = {
 			type: 'boolean',
 			optional: true, nullable: false,
 		},
+		isFavorited: {
+			type: 'boolean',
+			optional: true, nullable: false,
+		},
 		userId: {
 			type: 'string',
 			nullable: true, optional: false,
 			format: 'id',
 		},
+		pinnedNoteIds: {
+			type: 'array',
+			nullable: false, optional: false,
+			items: {
+				type: 'string',
+				format: 'id',
+			},
+		},
 	},
 } as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index d5428805d1..cbe3814a24 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -18,7 +18,7 @@ import { AttestationChallenge } from '@/models/entities/AttestationChallenge.js'
 import { AuthSession } from '@/models/entities/AuthSession.js';
 import { Blocking } from '@/models/entities/Blocking.js';
 import { ChannelFollowing } from '@/models/entities/ChannelFollowing.js';
-import { ChannelNotePining } from '@/models/entities/ChannelNotePining.js';
+import { ChannelFavorite } from '@/models/entities/ChannelFavorite.js';
 import { Clip } from '@/models/entities/Clip.js';
 import { ClipNote } from '@/models/entities/ClipNote.js';
 import { ClipFavorite } from '@/models/entities/ClipFavorite.js';
@@ -175,7 +175,7 @@ export const entities = [
 	MutedNote,
 	Channel,
 	ChannelFollowing,
-	ChannelNotePining,
+	ChannelFavorite,
 	RegistryItem,
 	Ad,
 	PasswordResetRequest,
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 835e884193..f39643abeb 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -95,6 +95,9 @@ import * as ep___channels_show from './endpoints/channels/show.js';
 import * as ep___channels_timeline from './endpoints/channels/timeline.js';
 import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
 import * as ep___channels_update from './endpoints/channels/update.js';
+import * as ep___channels_favorite from './endpoints/channels/favorite.js';
+import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
+import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
 import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
 import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
 import * as ep___charts_drive from './endpoints/charts/drive.js';
@@ -424,6 +427,9 @@ const $channels_show: Provider = { provide: 'ep:channels/show', useClass: ep___c
 const $channels_timeline: Provider = { provide: 'ep:channels/timeline', useClass: ep___channels_timeline.default };
 const $channels_unfollow: Provider = { provide: 'ep:channels/unfollow', useClass: ep___channels_unfollow.default };
 const $channels_update: Provider = { provide: 'ep:channels/update', useClass: ep___channels_update.default };
+const $channels_favorite: Provider = { provide: 'ep:channels/favorite', useClass: ep___channels_favorite.default };
+const $channels_unfavorite: Provider = { provide: 'ep:channels/unfavorite', useClass: ep___channels_unfavorite.default };
+const $channels_myFavorites: Provider = { provide: 'ep:channels/my-favorites', useClass: ep___channels_myFavorites.default };
 const $charts_activeUsers: Provider = { provide: 'ep:charts/active-users', useClass: ep___charts_activeUsers.default };
 const $charts_apRequest: Provider = { provide: 'ep:charts/ap-request', useClass: ep___charts_apRequest.default };
 const $charts_drive: Provider = { provide: 'ep:charts/drive', useClass: ep___charts_drive.default };
@@ -757,6 +763,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$channels_timeline,
 		$channels_unfollow,
 		$channels_update,
+		$channels_favorite,
+		$channels_unfavorite,
+		$channels_myFavorites,
 		$charts_activeUsers,
 		$charts_apRequest,
 		$charts_drive,
@@ -1084,6 +1093,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$channels_timeline,
 		$channels_unfollow,
 		$channels_update,
+		$channels_favorite,
+		$channels_unfavorite,
+		$channels_myFavorites,
 		$charts_activeUsers,
 		$charts_apRequest,
 		$charts_drive,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index f6fc79fc70..16b20c1a4d 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -95,6 +95,9 @@ import * as ep___channels_show from './endpoints/channels/show.js';
 import * as ep___channels_timeline from './endpoints/channels/timeline.js';
 import * as ep___channels_unfollow from './endpoints/channels/unfollow.js';
 import * as ep___channels_update from './endpoints/channels/update.js';
+import * as ep___channels_favorite from './endpoints/channels/favorite.js';
+import * as ep___channels_unfavorite from './endpoints/channels/unfavorite.js';
+import * as ep___channels_myFavorites from './endpoints/channels/my-favorites.js';
 import * as ep___charts_activeUsers from './endpoints/charts/active-users.js';
 import * as ep___charts_apRequest from './endpoints/charts/ap-request.js';
 import * as ep___charts_drive from './endpoints/charts/drive.js';
@@ -422,6 +425,9 @@ const eps = [
 	['channels/timeline', ep___channels_timeline],
 	['channels/unfollow', ep___channels_unfollow],
 	['channels/update', ep___channels_update],
+	['channels/favorite', ep___channels_favorite],
+	['channels/unfavorite', ep___channels_unfavorite],
+	['channels/my-favorites', ep___channels_myFavorites],
 	['charts/active-users', ep___charts_activeUsers],
 	['charts/ap-request', ep___charts_apRequest],
 	['charts/drive', ep___charts_drive],
diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts
new file mode 100644
index 0000000000..f52b45ccf3
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts
@@ -0,0 +1,61 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	tags: ['channels'],
+
+	requireCredential: true,
+
+	kind: 'write:channels',
+
+	errors: {
+		noSuchChannel: {
+			message: 'No such channel.',
+			code: 'NO_SUCH_CHANNEL',
+			id: '4938f5f3-6167-4c04-9149-6607b7542861',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['channelId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.channelsRepository)
+		private channelsRepository: ChannelsRepository,
+
+		@Inject(DI.channelFavoritesRepository)
+		private channelFavoritesRepository: ChannelFavoritesRepository,
+
+		private idService: IdService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const channel = await this.channelsRepository.findOneBy({
+				id: ps.channelId,
+			});
+
+			if (channel == null) {
+				throw new ApiError(meta.errors.noSuchChannel);
+			}
+
+			await this.channelFavoritesRepository.insert({
+				id: this.idService.genId(),
+				createdAt: new Date(),
+				userId: me.id,
+				channelId: channel.id,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/my-favorites.ts b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
new file mode 100644
index 0000000000..60525ed060
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
@@ -0,0 +1,54 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ChannelFavoritesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+	tags: ['channels', 'account'],
+
+	requireCredential: true,
+
+	kind: 'read:channels',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'Channel',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+	},
+	required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.channelFavoritesRepository)
+		private channelFavoritesRepository: ChannelFavoritesRepository,
+
+		private channelEntityService: ChannelEntityService,
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const query = this.channelFavoritesRepository.createQueryBuilder('favorite')
+				.andWhere('favorite.userId = :meId', { meId: me.id })
+				.leftJoinAndSelect('favorite.channel', 'channel');
+
+			const favorites = await query
+				.getMany();
+
+			return await Promise.all(favorites.map(x => this.channelEntityService.pack(x.channel!, me)));
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts
index 8718615db2..070d14631e 100644
--- a/packages/backend/src/server/api/endpoints/channels/show.ts
+++ b/packages/backend/src/server/api/endpoints/channels/show.ts
@@ -51,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				throw new ApiError(meta.errors.noSuchChannel);
 			}
 
-			return await this.channelEntityService.pack(channel, me);
+			return await this.channelEntityService.pack(channel, me, true);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
new file mode 100644
index 0000000000..0c3f6c4855
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
@@ -0,0 +1,56 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ChannelFavoritesRepository, ChannelsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	tags: ['channels'],
+
+	requireCredential: true,
+
+	kind: 'write:channels',
+
+	errors: {
+		noSuchChannel: {
+			message: 'No such channel.',
+			code: 'NO_SUCH_CHANNEL',
+			id: '353c68dd-131a-476c-aa99-88a345e83668',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		channelId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['channelId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.channelsRepository)
+		private channelsRepository: ChannelsRepository,
+
+		@Inject(DI.channelFavoritesRepository)
+		private channelFavoritesRepository: ChannelFavoritesRepository,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const channel = await this.channelsRepository.findOneBy({
+				id: ps.channelId,
+			});
+
+			if (channel == null) {
+				throw new ApiError(meta.errors.noSuchChannel);
+			}
+
+			await this.channelFavoritesRepository.delete({
+				userId: me.id,
+				channelId: channel.id,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index a86cc2565a..084b3f919e 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -3,8 +3,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js';
 import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
 import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../error.js';
 import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '../../error.js';
 
 export const meta = {
 	tags: ['channels'],
@@ -47,6 +47,12 @@ export const paramDef = {
 		name: { type: 'string', minLength: 1, maxLength: 128 },
 		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
 		bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+		pinnedNoteIds: {
+			type: 'array',
+			items: {
+				type: 'string', format: 'misskey:id',
+			},
+		},
 	},
 	required: ['channelId'],
 } as const;
@@ -64,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private channelEntityService: ChannelEntityService,
 
 		private roleService: RoleService,
-		) {
+	) {
 		super(meta, paramDef, async (ps, me) => {
 			const channel = await this.channelsRepository.findOneBy({
 				id: ps.channelId,
@@ -97,6 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.channelsRepository.update(channel.id, {
 				...(ps.name !== undefined ? { name: ps.name } : {}),
 				...(ps.description !== undefined ? { description: ps.description } : {}),
+				...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
 				...(banner ? { bannerId: banner.id } : {}),
 			});
 
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 72c6e55df1..eb9793fcc1 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -169,6 +169,7 @@ const props = defineProps<{
 }>();
 
 const inChannel = inject('inChannel', null);
+const currentClip = inject<Ref<misskey.entities.Clip> | null>('currentClip', null);
 
 let note = $ref(deepClone(props.note));
 
@@ -370,8 +371,6 @@ function undoReact(note): void {
 	});
 }
 
-const currentClipPage = inject<Ref<misskey.entities.Clip> | null>('currentClipPage', null);
-
 function onContextmenu(ev: MouseEvent): void {
 	const isLink = (el: HTMLElement) => {
 		if (el.tagName === 'A') return true;
@@ -386,18 +385,18 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClipPage }), ev).then(focus);
+		os.contextMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }), ev).then(focus);
 	}
 }
 
 function menu(viaKeyboard = false): void {
-	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClipPage }), menuButton.value, {
+	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value }), menuButton.value, {
 		viaKeyboard,
 	}).then(focus);
 }
 
 async function clip() {
-	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClipPage }), clipButton.value).then(focus);
+	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
 }
 
 function showRenoteMenu(viaKeyboard = false): void {
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index f9952e4245..a4e949c898 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -19,7 +19,7 @@
 				:ad="true"
 				:class="$style.notes"
 			>
-				<XNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
+				<MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
 			</MkDateSeparatedList>
 		</div>
 	</template>
@@ -28,7 +28,7 @@
 
 <script lang="ts" setup>
 import { shallowRef } from 'vue';
-import XNote from '@/components/MkNote.vue';
+import MkNote from '@/components/MkNote.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n';
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 93b1c37055..874f1f90ea 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -9,7 +9,7 @@
 
 	<template #default="{ items: notifications }">
 		<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :no-gap="true">
-			<XNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
+			<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
 			<XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/>
 		</MkDateSeparatedList>
 	</template>
@@ -21,7 +21,7 @@ import { onUnmounted, onMounted, computed, shallowRef } from 'vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import XNotification from '@/components/MkNotification.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
-import XNote from '@/components/MkNote.vue';
+import MkNote from '@/components/MkNote.vue';
 import { stream } from '@/stream';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index b1800f3af7..a3f2c39d81 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -52,7 +52,7 @@
 		<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
 		<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"/>
+		<MkNotePreview 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>
@@ -87,7 +87,7 @@ 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 MkNotePreview from '@/components/MkNotePreview.vue';
 import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
 import MkPollEditor from '@/components/MkPollEditor.vue';
 import { host, url } from '@/config';
diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue
index 7d5c484a1b..8c65dabf08 100644
--- a/packages/frontend/src/components/page/page.note.vue
+++ b/packages/frontend/src/components/page/page.note.vue
@@ -1,21 +1,21 @@
 <template>
 <div class="voxdxuby">
-	<XNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/>
-	<XNoteDetailed v-if="note && block.detailed" :key="note.id + ':detail'" v-model:note="note"/>
+	<MkNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/>
+	<MkNoteDetailed v-if="note && block.detailed" :key="note.id + ':detail'" v-model:note="note"/>
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
-import XNote from '@/components/MkNote.vue';
-import XNoteDetailed from '@/components/MkNoteDetailed.vue';
+import MkNote from '@/components/MkNote.vue';
+import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import * as os from '@/os';
 import { NoteBlock } from '@/scripts/hpml/block';
 
 export default defineComponent({
 	components: {
-		XNote,
-		XNoteDetailed,
+		MkNote,
+		MkNoteDetailed,
 	},
 	props: {
 		block: {
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 38c5b1e082..667caab966 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="700">
-		<div class="_gaps_m">
+		<div v-if="channel" class="_gaps_m">
 			<MkInput v-model="name">
 				<template #label>{{ i18n.ts.name }}</template>
 			</MkInput>
@@ -11,13 +11,37 @@
 				<template #label>{{ i18n.ts.description }}</template>
 			</MkTextarea>
 
-			<div class="banner">
+			<div>
 				<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="ti ti-plus"></i> {{ i18n.ts._channel.setBanner }}</MkButton>
 				<div v-else-if="bannerUrl">
 					<img :src="bannerUrl" style="width: 100%;"/>
 					<MkButton @click="removeBannerImage()"><i class="ti ti-trash"></i> {{ i18n.ts._channel.removeBanner }}</MkButton>
 				</div>
 			</div>
+
+			<MkFolder :default-open="true">
+				<template #label>{{ i18n.ts.pinnedNotes }}</template>
+				
+				<div class="_gaps">
+					<MkButton primary rounded @click="addPinnedNote()"><i class="ti ti-plus"></i></MkButton>
+
+					<Sortable 
+						v-model="pinnedNotes"
+						item-key="id"
+						:handle="'.' + $style.pinnedNoteHandle"
+						:animation="150"
+					>
+						<template #item="{element,index}">
+							<div :class="$style.pinnedNote">
+								<button class="_button" :class="$style.pinnedNoteHandle"><i class="ti ti-menu"></i></button>
+								{{ element.id }}
+								<button class="_button" :class="$style.pinnedNoteRemove" @click="removePinnedNote(index)"><i class="ti ti-x"></i></button>
+							</div>
+						</template>
+					</Sortable>
+				</div>
+			</MkFolder>
+
 			<div>
 				<MkButton primary @click="save()"><i class="ti ti-device-floppy"></i> {{ channelId ? i18n.ts.save : i18n.ts.create }}</MkButton>
 			</div>
@@ -27,7 +51,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, ref, watch, defineAsyncComponent } from 'vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -36,6 +60,9 @@ import * as os from '@/os';
 import { useRouter } from '@/router';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
+import MkFolder from '@/components/MkFolder.vue';
+
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
 const router = useRouter();
 
@@ -48,6 +75,7 @@ let name = $ref(null);
 let description = $ref(null);
 let bannerUrl = $ref<string | null>(null);
 let bannerId = $ref<string | null>(null);
+const pinnedNotes = ref([]);
 
 watch(() => bannerId, async () => {
 	if (bannerId == null) {
@@ -70,15 +98,36 @@ async function fetchChannel() {
 	description = channel.description;
 	bannerId = channel.bannerId;
 	bannerUrl = channel.bannerUrl;
+	pinnedNotes.value = channel.pinnedNoteIds.map(id => ({
+		id,
+	}));
 }
 
 fetchChannel();
 
+async function addPinnedNote() {
+	const { canceled, result: value } = await os.inputText({
+		title: i18n.ts.noteIdOrUrl,
+	});
+	if (canceled) return;
+	const note = await os.apiWithDialog('notes/show', {
+		noteId: value.includes('/') ? value.split('/').pop() : value,
+	});
+	pinnedNotes.value = [{
+		id: note.id,
+	}, ...pinnedNotes.value];
+}
+
+function removePinnedNote(index: number) {
+	pinnedNotes.value.splice(index, 1);
+}
+
 function save() {
 	const params = {
 		name: name,
 		description: description,
 		bannerId: bannerId,
+		pinnedNoteIds: pinnedNotes.value.map(x => x.id),
 	};
 
 	if (props.channelId) {
@@ -117,6 +166,32 @@ definePageMetadata(computed(() => props.channelId ? {
 }));
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" module>
+.pinnedNote {
+	position: relative;
+	display: block;
+	line-height: 2.85rem;
+	text-overflow: ellipsis;
+	overflow: hidden;
+	white-space: nowrap;
+	color: var(--navFg);
+}
 
+.pinnedNoteRemove {
+	position: absolute;
+	z-index: 10000;
+	width: 32px;
+	height: 32px;
+	color: #ff2a2a;
+	right: 8px;
+	opacity: 0.8;
+}
+
+.pinnedNoteHandle {
+	cursor: move;
+	width: 32px;
+	height: 32px;
+	margin: 0 8px;
+	opacity: 0.5;
+}
 </style>
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 76f11faab8..47ca8003ad 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -16,6 +16,16 @@
 					<Mfm :text="channel.description" :is-note="false" :i="$i"/>
 				</div>
 			</div>
+
+			<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
+			<MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-star"></i></MkButton>
+
+			<MkFoldableSection>
+				<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
+				<div v-if="channel.pinnedNotes.length > 0" class="_gaps">
+					<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
+				</div>
+			</MkFoldableSection>
 		</div>
 		<div v-if="channel && tab === 'timeline'" class="_gaps">
 			<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
@@ -54,6 +64,8 @@ import MkNotes from '@/components/MkNotes.vue';
 import { url } from '@/config';
 import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store';
+import MkNote from '@/components/MkNote.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 
 const router = useRouter();
 
@@ -63,6 +75,7 @@ const props = defineProps<{
 
 let tab = $ref('timeline');
 let channel = $ref(null);
+let favorited = $ref(false);
 const featuredPagination = $computed(() => ({
 	endpoint: 'notes/featured' as const,
 	limit: 10,
@@ -76,6 +89,7 @@ watch(() => props.channelId, async () => {
 	channel = await os.api('channels/show', {
 		channelId: props.channelId,
 	});
+	favorited = channel.isFavorited;
 }, { immediate: true });
 
 function edit() {
@@ -90,6 +104,27 @@ function openPostForm() {
 	});
 }
 
+function favorite() {
+	os.apiWithDialog('channels/favorite', {
+		channelId: channel.id,
+	}).then(() => {
+		favorited = true;
+	});
+}
+
+async function unfavorite() {
+	const confirm = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.unfavoriteConfirm,
+	});
+	if (confirm.canceled) return;
+	os.apiWithDialog('channels/unfavorite', {
+		channelId: channel.id,
+	}).then(() => {
+		favorited = false;
+	});
+}
+
 const headerActions = $computed(() => {
 	if (channel && channel.userId) {
 		const share = {
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 3550c7f436..fd1d2d03cf 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -2,17 +2,22 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="700">
-		<div v-if="tab === 'featured'" class="grwlizim featured">
+		<div v-if="tab === 'featured'">
 			<MkPagination v-slot="{items}" :pagination="featuredPagination">
 				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
 			</MkPagination>
 		</div>
-		<div v-else-if="tab === 'following'" class="grwlizim following">
+		<div v-else-if="tab === 'favorites'">
+			<MkPagination v-slot="{items}" :pagination="favoritesPagination">
+				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+			</MkPagination>
+		</div>
+		<div v-else-if="tab === 'following'">
 			<MkPagination v-slot="{items}" :pagination="followingPagination">
 				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
 			</MkPagination>
 		</div>
-		<div v-else-if="tab === 'owned'" class="grwlizim owned">
+		<div v-else-if="tab === 'owned'">
 			<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
 			<MkPagination v-slot="{items}" :pagination="ownedPagination">
 				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
@@ -39,13 +44,17 @@ const featuredPagination = {
 	endpoint: 'channels/featured' as const,
 	noPaging: true,
 };
+const favoritesPagination = {
+	endpoint: 'channels/my-favorites' as const,
+	limit: 100,
+};
 const followingPagination = {
 	endpoint: 'channels/followed' as const,
-	limit: 5,
+	limit: 10,
 };
 const ownedPagination = {
 	endpoint: 'channels/owned' as const,
-	limit: 5,
+	limit: 10,
 };
 
 function create() {
@@ -62,10 +71,14 @@ const headerTabs = $computed(() => [{
 	key: 'featured',
 	title: i18n.ts._channel.featured,
 	icon: 'ti ti-comet',
+}, {
+	key: 'favorites',
+	title: i18n.ts.favorites,
+	icon: 'ti ti-star',
 }, {
 	key: 'following',
 	title: i18n.ts._channel.following,
-	icon: 'ti ti-heart',
+	icon: 'ti ti-eye',
 }, {
 	key: 'owned',
 	title: i18n.ts._channel.owned,
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 2b64de088a..e3ac3f4c9b 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -57,7 +57,7 @@ watch(() => props.clipId, async () => {
 	immediate: true,
 }); 
 
-provide('currentClipPage', $$(clip));
+provide('currentClip', $$(clip));
 
 function favorite() {
 	os.apiWithDialog('clips/favorite', {
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index 07dd768499..0dc9b9dc8f 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -12,7 +12,7 @@
 
 			<template #default="{ items }">
 				<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
-					<XNote :key="item.id" :note="item.note" :class="$style.note"/>
+					<MkNote :key="item.id" :note="item.note" :class="$style.note"/>
 				</MkDateSeparatedList>
 			</template>
 		</MkPagination>
@@ -22,7 +22,7 @@
 
 <script lang="ts" setup>
 import MkPagination from '@/components/MkPagination.vue';
-import XNote from '@/components/MkNote.vue';
+import MkNote from '@/components/MkNote.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 45efe655fb..3e5e1ffe9d 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -13,7 +13,7 @@
 						<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton>
 						<div class="note _margin _gaps_s">
 							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
-							<XNoteDetailed :key="note.id" v-model:note="note" class="note"/>
+							<MkNoteDetailed :key="note.id" v-model:note="note" class="note"/>
 						</div>
 						<div v-if="clips && clips.length > 0" class="clips _margin">
 							<div class="title">{{ i18n.ts.clip }}</div>
@@ -41,7 +41,7 @@
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
 import * as misskey from 'misskey-js';
-import XNoteDetailed from '@/components/MkNoteDetailed.vue';
+import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import MkNotes from '@/components/MkNotes.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
index d8a7eb85aa..cc39d2c412 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
@@ -10,8 +10,8 @@
 		</MkInput>
 		<MkSwitch v-model="props.modelValue.detailed"><span>{{ $ts._pages.blocks._note.detailed }}</span></MkSwitch>
 
-		<XNote v-if="note && !props.modelValue.detailed" :key="note.id + ':normal'" v-model:note="note" style="margin-bottom: 16px;"/>
-		<XNoteDetailed v-if="note && props.modelValue.detailed" :key="note.id + ':detail'" v-model:note="note" style="margin-bottom: 16px;"/>
+		<MkNote v-if="note && !props.modelValue.detailed" :key="note.id + ':normal'" v-model:note="note" style="margin-bottom: 16px;"/>
+		<MkNoteDetailed v-if="note && props.modelValue.detailed" :key="note.id + ':detail'" v-model:note="note" style="margin-bottom: 16px;"/>
 	</section>
 </XContainer>
 </template>
@@ -22,8 +22,8 @@ import { watch } from 'vue';
 import XContainer from '../page-editor.container.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
-import XNote from '@/components/MkNote.vue';
-import XNoteDetailed from '@/components/MkNoteDetailed.vue';
+import MkNote from '@/components/MkNote.vue';
+import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import * as os from '@/os';
 
 const props = defineProps<{
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index d982a76d03..62b0b4551a 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -83,7 +83,7 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> {
 }
 
 async function chooseChannel(ev: MouseEvent): Promise<void> {
-	const channels = await os.api('channels/followed', {
+	const channels = await os.api('channels/my-favorites', {
 		limit: 100,
 	});
 	const items = channels.map(channel => ({
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 7efaaebf5d..c45c0600a7 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -93,7 +93,7 @@
 
 			<div class="contents _gaps">
 				<div v-if="user.pinnedNotes.length > 0" class="_gaps">
-					<XNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
+					<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
 				</div>
 				<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
 				<template v-if="narrow">
@@ -115,7 +115,7 @@
 import { defineAsyncComponent, computed, onMounted, onUnmounted } from 'vue';
 import calcAge from 's-age';
 import * as misskey from 'misskey-js';
-import XNote from '@/components/MkNote.vue';
+import MkNote from '@/components/MkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkOmit from '@/components/MkOmit.vue';
diff --git a/packages/frontend/src/pages/welcome.entrance.b.vue b/packages/frontend/src/pages/welcome.entrance.b.vue
index 8230adaf1f..7c7dfcc850 100644
--- a/packages/frontend/src/pages/welcome.entrance.b.vue
+++ b/packages/frontend/src/pages/welcome.entrance.b.vue
@@ -38,12 +38,12 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import { toUnicode } from 'punycode/';
+import XTimeline from './welcome.timeline.vue';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
 import MkButton from '@/components/MkButton.vue';
-import XNote from '@/components/MkNote.vue';
+import MkNote from '@/components/MkNote.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
-import XTimeline from './welcome.timeline.vue';
 import { host, instanceName } from '@/config';
 import * as os from '@/os';
 import number from '@/filters/number';
@@ -51,7 +51,7 @@ import number from '@/filters/number';
 export default defineComponent({
 	components: {
 		MkButton,
-		XNote,
+		MkNote,
 		XTimeline,
 		MkFeaturedPhotos,
 	},
@@ -118,7 +118,7 @@ export default defineComponent({
 				text: this.$ts.help,
 				icon: 'ti ti-question-circle',
 				action: () => {
-					window.open(`https://misskey-hub.net/help.md`, '_blank');
+					window.open('https://misskey-hub.net/help.md', '_blank');
 				},
 			}], ev.currentTarget ?? ev.target);
 		},
diff --git a/packages/frontend/src/pages/welcome.entrance.c.vue b/packages/frontend/src/pages/welcome.entrance.c.vue
index d2d07bb1f0..f566d1a56a 100644
--- a/packages/frontend/src/pages/welcome.entrance.c.vue
+++ b/packages/frontend/src/pages/welcome.entrance.c.vue
@@ -61,7 +61,7 @@ import { toUnicode } from 'punycode/';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
 import MkButton from '@/components/MkButton.vue';
-import XNote from '@/components/MkNote.vue';
+import MkNote from '@/components/MkNote.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
 import XTimeline from './welcome.timeline.vue';
 import { host, instanceName } from '@/config';
@@ -71,7 +71,7 @@ import number from '@/filters/number';
 export default defineComponent({
 	components: {
 		MkButton,
-		XNote,
+		MkNote,
 		MkFeaturedPhotos,
 		XTimeline,
 	},
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 00f2523bf9..d91f0b0eb6 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -15,7 +15,7 @@ import { clipsCache } from '@/cache';
 export async function getNoteClipMenu(props: {
 	note: misskey.entities.Note;
 	isDeleted: Ref<boolean>;
-	currentClipPage?: Ref<misskey.entities.Clip>;
+	currentClip?: misskey.entities.Clip;
 }) {
 	const isRenote = (
 		props.note.renote != null &&
@@ -42,7 +42,7 @@ export async function getNoteClipMenu(props: {
 						});
 						if (!confirm.canceled) {
 							os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
-							if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true;
+							if (props.currentClip?.id === clip.id) props.isDeleted.value = true;
 						}
 					} else {
 						os.alert({
@@ -92,7 +92,7 @@ export function getNoteMenu(props: {
 	translation: Ref<any>;
 	translating: Ref<boolean>;
 	isDeleted: Ref<boolean>;
-	currentClipPage?: Ref<misskey.entities.Clip>;
+	currentClip?: misskey.entities.Clip;
 }) {
 	const isRenote = (
 		props.note.renote != null &&
@@ -176,7 +176,7 @@ export function getNoteMenu(props: {
 	}
 
 	async function unclip(): Promise<void> {
-		os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id });
+		os.apiWithDialog('clips/remove-note', { clipId: props.currentClip.id, noteId: appearNote.id });
 		props.isDeleted.value = true;
 	}
 
@@ -230,7 +230,7 @@ export function getNoteMenu(props: {
 
 		menu = [
 			...(
-				props.currentClipPage?.value.userId === $i.id ? [{
+				props.currentClip?.userId === $i.id ? [{
 					icon: 'ti ti-backspace',
 					text: i18n.ts.unclip,
 					danger: true,
@@ -294,7 +294,7 @@ export function getNoteMenu(props: {
 				text: i18n.ts.muteThread,
 				action: () => toggleThreadMute(true),
 			}),
-			appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
+			appearNote.userId === $i.id ? ($i.pinnedNoteIds ?? []).includes(appearNote.id) ? {
 				icon: 'ti ti-pinned-off',
 				text: i18n.ts.unpin,
 				action: () => togglePin(false),
diff --git a/packages/misskey-js/.eslintrc.cjs b/packages/misskey-js/.eslintrc.cjs
new file mode 100644
index 0000000000..e2e31e9e33
--- /dev/null
+++ b/packages/misskey-js/.eslintrc.cjs
@@ -0,0 +1,9 @@
+module.exports = {
+	parserOptions: {
+		tsconfigRootDir: __dirname,
+		project: ['./tsconfig.json'],
+	},
+	extends: [
+		'../shared/.eslintrc.js',
+	],
+};
diff --git a/packages/misskey-js/.eslintrc.js b/packages/misskey-js/.eslintrc.js
deleted file mode 100644
index 426894947b..0000000000
--- a/packages/misskey-js/.eslintrc.js
+++ /dev/null
@@ -1,57 +0,0 @@
-module.exports = {
-	root: true,
-	parser: '@typescript-eslint/parser',
-	parserOptions: {
-		tsconfigRootDir: __dirname,
-		project: ['./tsconfig.json'],
-	},
-	plugins: [
-		'@typescript-eslint',
-	],
-	extends: [
-		'eslint:recommended',
-		'plugin:@typescript-eslint/recommended',
-	],
-	rules: {
-		'indent': ['error', 'tab', {
-			'SwitchCase': 1,
-			'MemberExpression': 'off',
-			'flatTernaryExpressions': true,
-			'ArrayExpression': 'first',
-			'ObjectExpression': 'first',
-		}],
-		'eol-last': ['error', 'always'],
-		'semi': ['error', 'always'],
-		'quotes': ['error', 'single'],
-		'comma-dangle': ['error', 'always-multiline'],
-		'keyword-spacing': ['error', {
-			'before': true,
-			'after': true,
-		}],
-		'key-spacing': ['error', {
-			'beforeColon': false,
-			'afterColon': true,
-		}],
-		'space-infix-ops': ['error'],
-		'space-before-blocks': ['error', 'always'],
-		'object-curly-spacing': ['error', 'always'],
-		'nonblock-statement-body-position': ['error', 'beside'],
-		'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
-		'no-multiple-empty-lines': ['error', { 'max': 1 }],
-		'no-multi-spaces': ['error'],
-		'no-var': ['error'],
-		'prefer-arrow-callback': ['error'],
-		'no-throw-literal': ['error'],
-		'no-param-reassign': ['warn'],
-		'no-constant-condition': ['warn'],
-		'no-empty-pattern': ['warn'],
-		'@typescript-eslint/no-unnecessary-condition': ['error'],
-		'@typescript-eslint/no-inferrable-types': ['warn'],
-		'@typescript-eslint/no-non-null-assertion': ['warn'],
-		'@typescript-eslint/explicit-function-return-type': ['warn'],
-		'@typescript-eslint/no-misused-promises': ['error', {
-			'checksVoidReturn': false,
-		}],
-		'@typescript-eslint/consistent-type-imports': 'error',
-	},
-};
diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts
index 17de317d19..974cb35ace 100644
--- a/packages/misskey-js/src/api.ts
+++ b/packages/misskey-js/src/api.ts
@@ -49,7 +49,7 @@ export class APIClient {
 		this.credential = opts.credential;
 		// ネイティブ関数をそのまま変数に代入して使おうとするとChromiumではIllegal invocationエラーが発生するため、
 		// 環境で実装されているfetchを使う場合は無名関数でラップして使用する
-		this.fetch = opts.fetch || ((...args) => fetch(...args));
+		this.fetch = opts.fetch ?? ((...args) => fetch(...args));
 	}
 
 	public request<E extends keyof Endpoints, P extends Endpoints[E]['req']>(
diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts
index 9dd764952e..d97c2182fd 100644
--- a/packages/misskey-js/src/streaming.ts
+++ b/packages/misskey-js/src/streaming.ts
@@ -36,7 +36,7 @@ export default class Stream extends EventEmitter<StreamEvents> {
 		WebSocket?: any;
 	}) {
 		super();
-		options = options || { };
+		options = options ?? { };
 
 		const query = urlQuery({
 			i: user?.token,