diff --git a/CHANGELOG.md b/CHANGELOG.md
index d679a4c01f..137fb51f66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -80,6 +80,7 @@ You should also include the user name that made the change.
 - ユーザーリストおよびユーザーリスト内のユーザーの作成可能数を設定可能に @syuilo
 - ハードワードミュートの最大文字数を設定可能に @syuilo
 - Webhookの作成可能数を設定可能に @syuilo
+- ノートをピン留めできる数を設定可能に @syuilo
 - Server: signToActivityPubGet is set to true by default @syuilo
 - Server: improve syslog performance @syuilo
 - Server: Use undici instead of node-fetch and got @tamaina
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 15a33932a5..67464e943d 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -962,6 +962,7 @@ _role:
     canInvite: "インスタンス招待コードの発行"
     canManageCustomEmojis: "カスタム絵文字の管理"
     driveCapacity: "ドライブ容量"
+    pinMax: "ノートのピン留めの最大数"
     antennaMax: "アンテナの作成可能数"
     wordMuteMax: "ワードミュートの最大文字数"
     webhookMax: "Webhookの作成可能数"
diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts
index f8997574a7..bc038e17a7 100644
--- a/packages/backend/src/core/NotePiningService.ts
+++ b/packages/backend/src/core/NotePiningService.ts
@@ -12,6 +12,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { bindThis } from '@/decorators.js';
+import { RoleService } from '@/core/RoleService.js';
 
 @Injectable()
 export class NotePiningService {
@@ -30,6 +31,7 @@ export class NotePiningService {
 
 		private userEntityService: UserEntityService,
 		private idService: IdService,
+		private roleService: RoleService,
 		private relayService: RelayService,
 		private apDeliverManagerService: ApDeliverManagerService,
 		private apRendererService: ApRendererService,
@@ -55,7 +57,7 @@ export class NotePiningService {
 
 		const pinings = await this.userNotePiningsRepository.findBy({ userId: user.id });
 
-		if (pinings.length >= 5) {
+		if (pinings.length >= (await this.roleService.getUserRoleOptions(user.id)).pinLimit) {
 			throw new IdentifiableError('15a018eb-58e5-4da1-93be-330fcc5e4e1a', 'You can not pin notes any more.');
 		}
 
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 0ddcb44ca1..984aef727f 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -20,6 +20,7 @@ export type RoleOptions = {
 	canInvite: boolean;
 	canManageCustomEmojis: boolean;
 	driveCapacityMb: number;
+	pinLimit: number;
 	antennaLimit: number;
 	wordMuteLimit: number;
 	webhookLimit: number;
@@ -36,6 +37,7 @@ export const DEFAULT_ROLE: RoleOptions = {
 	canInvite: false,
 	canManageCustomEmojis: false,
 	driveCapacityMb: 100,
+	pinLimit: 5,
 	antennaLimit: 5,
 	wordMuteLimit: 200,
 	webhookLimit: 3,
@@ -211,6 +213,7 @@ export class RoleService implements OnApplicationShutdown {
 			canInvite: getOptionValues('canInvite').some(x => x === true),
 			canManageCustomEmojis: getOptionValues('canManageCustomEmojis').some(x => x === true),
 			driveCapacityMb: Math.max(...getOptionValues('driveCapacityMb')),
+			pinLimit: Math.max(...getOptionValues('pinLimit')),
 			antennaLimit: Math.max(...getOptionValues('antennaLimit')),
 			wordMuteLimit: Math.max(...getOptionValues('wordMuteLimit')),
 			webhookLimit: Math.max(...getOptionValues('webhookLimit')),
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 001409cac8..fc5e2b9d12 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -116,6 +116,18 @@
 				</div>
 			</MkFolder>
 
+			<MkFolder>
+				<template #label>{{ i18n.ts._role._options.pinMax }}</template>
+				<template #suffix>{{ options_pinLimit_useDefault ? i18n.ts._role.useBaseValue : (options_pinLimit_value) }}</template>
+				<div class="_gaps">
+					<MkSwitch v-model="options_pinLimit_useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkInput v-model="options_pinLimit_value" :disabled="options_pinLimit_useDefault" type="number" :readonly="readonly">
+					</MkInput>
+				</div>
+			</MkFolder>
+
 			<MkFolder>
 				<template #label>{{ i18n.ts._role._options.antennaMax }}</template>
 				<template #suffix>{{ options_antennaLimit_useDefault ? i18n.ts._role.useBaseValue : (options_antennaLimit_value) }}</template>
@@ -265,6 +277,8 @@ let options_canManageCustomEmojis_useDefault = $ref(role?.options?.canManageCust
 let options_canManageCustomEmojis_value = $ref(role?.options?.canManageCustomEmojis?.value ?? false);
 let options_driveCapacityMb_useDefault = $ref(role?.options?.driveCapacityMb?.useDefault ?? true);
 let options_driveCapacityMb_value = $ref(role?.options?.driveCapacityMb?.value ?? 0);
+let options_pinLimit_useDefault = $ref(role?.options?.pinLimit?.useDefault ?? true);
+let options_pinLimit_value = $ref(role?.options?.pinLimit?.value ?? 0);
 let options_antennaLimit_useDefault = $ref(role?.options?.antennaLimit?.useDefault ?? true);
 let options_antennaLimit_value = $ref(role?.options?.antennaLimit?.value ?? 0);
 let options_wordMuteLimit_useDefault = $ref(role?.options?.wordMuteLimit?.useDefault ?? true);
@@ -294,6 +308,7 @@ function getOptions() {
 		canInvite: { useDefault: options_canInvite_useDefault, value: options_canInvite_value },
 		canManageCustomEmojis: { useDefault: options_canManageCustomEmojis_useDefault, value: options_canManageCustomEmojis_value },
 		driveCapacityMb: { useDefault: options_driveCapacityMb_useDefault, value: options_driveCapacityMb_value },
+		pinLimit: { useDefault: options_pinLimit_useDefault, value: options_pinLimit_value },
 		antennaLimit: { useDefault: options_antennaLimit_useDefault, value: options_antennaLimit_value },
 		wordMuteLimit: { useDefault: options_wordMuteLimit_useDefault, value: options_wordMuteLimit_value },
 		webhookLimit: { useDefault: options_webhookLimit_useDefault, value: options_webhookLimit_value },
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 7a2f269310..68be6bbdd6 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -56,6 +56,13 @@
 							</MkInput>
 						</MkFolder>
 
+						<MkFolder>
+							<template #label>{{ i18n.ts._role._options.pinMax }}</template>
+							<template #suffix>{{ options_pinLimit }}</template>
+							<MkInput v-model="options_pinLimit" type="number">
+							</MkInput>
+						</MkFolder>
+
 						<MkFolder>
 							<template #label>{{ i18n.ts._role._options.antennaMax }}</template>
 							<template #suffix>{{ options_antennaLimit }}</template>
@@ -144,6 +151,7 @@ let options_canPublicNote = $ref(instance.baseRole.canPublicNote);
 let options_canInvite = $ref(instance.baseRole.canInvite);
 let options_canManageCustomEmojis = $ref(instance.baseRole.canManageCustomEmojis);
 let options_driveCapacityMb = $ref(instance.baseRole.driveCapacityMb);
+let options_pinLimit = $ref(instance.baseRole.pinLimit);
 let options_antennaLimit = $ref(instance.baseRole.antennaLimit);
 let options_wordMuteLimit = $ref(instance.baseRole.wordMuteLimit);
 let options_webhookLimit = $ref(instance.baseRole.webhookLimit);
@@ -161,6 +169,7 @@ async function updateBaseRole() {
 			canInvite: options_canInvite,
 			canManageCustomEmojis: options_canManageCustomEmojis,
 			driveCapacityMb: options_driveCapacityMb,
+			pinLimit: options_pinLimit,
 			antennaLimit: options_antennaLimit,
 			wordMuteLimit: options_wordMuteLimit,
 			webhookLimit: options_webhookLimit,