diff --git a/.config/ci.yml b/.config/ci.yml
index 19ffe18d2cc924ca1ee748343bf2dbc3f0af98a5..d20ede8d3575dff504363d7bd2b10d5c94da879a 100644
--- a/.config/ci.yml
+++ b/.config/ci.yml
@@ -167,8 +167,18 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
-# Amount of characters that can be used when writing notes (maximum: 100000, minimum: 1)
-maxNoteLength: 3000
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
 
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml
index 91dce3515585c80c4ac9e4cfa1555c986170bb03..d8013a1c95050cfd19430c7a53af874a2496a339 100644
--- a/.config/cypress-devcontainer.yml
+++ b/.config/cypress-devcontainer.yml
@@ -179,6 +179,19 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
+
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
 
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index 3a344e3089f25ea61655b92e6d72dc550d667f89..5fac3dc41e29dfa112c53f109583f7e369cb531c 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -250,8 +250,18 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
-# Amount of characters that can be used when writing notes (maximum: 100000, minimum: 1)
-maxNoteLength: 3000
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
 
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
diff --git a/.config/example.yml b/.config/example.yml
index b9086479ea418e2863516e100d87a6a75ac5c173..0062b6670c0f7b65a36f247410f205b6dd5e158b 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -261,8 +261,18 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
-# Amount of characters that can be used when writing notes (maximum: 100000, minimum: 1)
-maxNoteLength: 3000
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
 
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
diff --git a/packages/backend/migration/1728348353115-soft-limit-drive-comment.js b/packages/backend/migration/1728348353115-soft-limit-drive-comment.js
new file mode 100644
index 0000000000000000000000000000000000000000..4eb04432c268faf67268e4a0280e08e8b0cab5ac
--- /dev/null
+++ b/packages/backend/migration/1728348353115-soft-limit-drive-comment.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SoftLimitDriveComment1728348353115 {
+    name = 'SoftLimitDriveComment1728348353115'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE text`);
+		}
+
+    async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(100000)`);
+		}
+}
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index c9411326a9cb55909caa8c27936a413fa35a66bc..3dc49c7eb6c41f14f30b4fe30b0e13bf446c8bf5 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -73,6 +73,11 @@ type Source = {
 
 	maxFileSize?: number;
 	maxNoteLength?: number;
+	maxCwLength?: number;
+	maxRemoteCwLength?: number;
+	maxRemoteNoteLength?: number;
+	maxAltTextLength?: number;
+	maxRemoteAltTextLength?: number;
 
 	clusterLimit?: number;
 
@@ -149,6 +154,11 @@ export type Config = {
 	allowedPrivateNetworks: string[] | undefined;
 	maxFileSize: number;
 	maxNoteLength: number;
+	maxRemoteNoteLength: number;
+	maxCwLength: number;
+	maxRemoteCwLength: number;
+	maxAltTextLength: number;
+	maxRemoteAltTextLength: number;
 	clusterLimit: number | undefined;
 	id: string;
 	outgoingAddress: string | undefined;
@@ -301,6 +311,11 @@ export function loadConfig(): Config {
 		allowedPrivateNetworks: config.allowedPrivateNetworks,
 		maxFileSize: config.maxFileSize ?? 262144000,
 		maxNoteLength: config.maxNoteLength ?? 3000,
+		maxRemoteNoteLength: config.maxRemoteNoteLength ?? 100000,
+		maxCwLength: config.maxCwLength ?? 500,
+		maxRemoteCwLength: config.maxRemoteCwLength ?? 5000,
+		maxAltTextLength: config.maxAltTextLength ?? 20000,
+		maxRemoteAltTextLength: config.maxRemoteAltTextLength ?? 100000,
 		clusterLimit: config.clusterLimit,
 		outgoingAddress: config.outgoingAddress,
 		outgoingAddressFamily: config.outgoingAddressFamily,
@@ -475,7 +490,7 @@ function applyEnvOverrides(config: Source) {
 	_apply_top(['sentryForBackend', 'enableNodeProfiling']);
 	_apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]);
 	_apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]);
-	_apply_top([['maxFileSize', 'maxNoteLength', 'pidFile']]);
+	_apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile']]);
 	_apply_top(['import', ['downloadTimeout', 'maxFileSize']]);
 	_apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature']]);
 }
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index 7cc22a5421d264be90c4c694c00f4c83ea1884b9..adb0a63ad7a8f11a130474db014b4427241c1d71 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -3,30 +3,11 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-export const MAX_NOTE_TEXT_LENGTH = 3000;
-
 export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
 export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
 
 export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
 
-//#region hard limits
-// If you change DB_* values, you must also change the DB schema.
-
-/**
- * Maximum note text length that can be stored in DB.
- * Content Warnings are included in this limit.
- * Surrogate pairs count as one
- */
-export const DB_MAX_NOTE_TEXT_LENGTH = 100000;
-
-/**
- * Maximum image description length that can be stored in DB.
- * Surrogate pairs count as one
- */
-export const DB_MAX_IMAGE_COMMENT_LENGTH = 100000;
-//#endregion
-
 // ブラウザで直接表示することを許可するファイルの種類のリスト
 // ここに含まれないものは application/octet-stream としてレスポンスされる
 // SVGはXSSを生むので許可しない
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 2a38ed80b713e18daad49426df768aad14db30de..1bc4599a602200a1bede36d9381aed4e04400188 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -45,7 +45,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
 import { NoteReadService } from '@/core/NoteReadService.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { bindThis } from '@/decorators.js';
-import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { RoleService } from '@/core/RoleService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { FeaturedService } from '@/core/FeaturedService.js';
@@ -335,9 +334,13 @@ export class NoteCreateService implements OnApplicationShutdown {
 			data.localOnly = true;
 		}
 
+		const maxTextLength = user.host == null
+			? this.config.maxNoteLength
+			: this.config.maxRemoteNoteLength;
+
 		if (data.text) {
-			if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
-				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
+			if (data.text.length > maxTextLength) {
+				data.text = data.text.slice(0, maxTextLength);
 			}
 			data.text = data.text.trim();
 			if (data.text === '') {
@@ -347,9 +350,13 @@ export class NoteCreateService implements OnApplicationShutdown {
 			data.text = null;
 		}
 
+		const maxCwLength = user.host == null
+			? this.config.maxCwLength
+			: this.config.maxRemoteCwLength;
+
 		if (data.cw) {
-			if (data.cw.length > DB_MAX_NOTE_TEXT_LENGTH) {
-				data.cw = data.cw.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
+			if (data.cw.length > maxCwLength) {
+				data.cw = data.cw.slice(0, maxCwLength);
 			}
 			data.cw = data.cw.trim();
 			if (data.cw === '') {
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index df45595da982d210bcd894e863c3961c83248179..d31958e5d4abd3ac2ce5757d2e5b94377b94d009 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -39,7 +39,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
 import { NoteReadService } from '@/core/NoteReadService.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { bindThis } from '@/decorators.js';
-import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { RoleService } from '@/core/RoleService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
@@ -365,9 +364,13 @@ export class NoteEditService implements OnApplicationShutdown {
 			data.localOnly = true;
 		}
 
+		const maxTextLength = user.host == null
+			? this.config.maxNoteLength
+			: this.config.maxRemoteNoteLength;
+
 		if (data.text) {
-			if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
-				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
+			if (data.text.length > maxTextLength) {
+				data.text = data.text.slice(0, maxTextLength);
 			}
 			data.text = data.text.trim();
 			if (data.text === '') {
@@ -377,9 +380,13 @@ export class NoteEditService implements OnApplicationShutdown {
 			data.text = null;
 		}
 
+		const maxCwLength = user.host == null
+			? this.config.maxCwLength
+			: this.config.maxRemoteCwLength;
+
 		if (data.cw) {
-			if (data.cw.length > DB_MAX_NOTE_TEXT_LENGTH) {
-				data.cw = data.cw.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
+			if (data.cw.length > maxCwLength) {
+				data.cw = data.cw.slice(0, maxCwLength);
 			}
 			data.cw = data.cw.trim();
 			if (data.cw === '') {
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index ba9f41ca24af5ec793fb19740b0f7156ef1685d5..259889d94526a7882e64bedaf2862eb01e75799a 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -9,12 +9,12 @@ import type { DriveFilesRepository, MiMeta } from '@/models/_.js';
 import type { MiRemoteUser } from '@/models/User.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import { truncate } from '@/misc/truncate.js';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 import { DriveService } from '@/core/DriveService.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { checkHttps } from '@/misc/check-https.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
+import type { Config } from '@/config.js';
 import { ApResolverService } from '../ApResolverService.js';
 import { ApLoggerService } from '../ApLoggerService.js';
 import { isDocument, type IObject } from '../type.js';
@@ -29,6 +29,8 @@ export class ApImageService {
 
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
+		@Inject(DI.config)
+		private config: Config,
 
 		private apResolverService: ApResolverService,
 		private driveService: DriveService,
@@ -83,7 +85,7 @@ export class ApImageService {
 			uri: image.url,
 			sensitive: !!(image.sensitive),
 			isLink: !shouldBeCached,
-			comment: truncate(image.name ?? undefined, DB_MAX_IMAGE_COMMENT_LENGTH),
+			comment: truncate(image.name ?? undefined, this.config.maxRemoteAltTextLength),
 		});
 		if (!file.isLink || file.url === image.url) return file;
 
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index 61655c9652d425820383a75b15166119ce59ebc7..1c463fb0c9d9edd3f89fa92e0258ddf0ab5fd123 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -110,6 +110,11 @@ export class MetaEntityService {
 			backgroundImageUrl: instance.backgroundImageUrl,
 			logoImageUrl: instance.logoImageUrl,
 			maxNoteTextLength: this.config.maxNoteLength,
+			maxRemoteNoteTextLength: this.config.maxRemoteNoteLength,
+			maxCwLength: this.config.maxCwLength,
+			maxRemoteCwLength: this.config.maxRemoteCwLength,
+			maxAltTextLength: this.config.maxAltTextLength,
+			maxRemoteAltTextLength: this.config.maxRemoteAltTextLength,
 			defaultLightTheme,
 			defaultDarkTheme,
 			defaultLike: instance.defaultLike,
diff --git a/packages/backend/src/models/DriveFile.ts b/packages/backend/src/models/DriveFile.ts
index 7de4be4f965d915bb04fcf21f568ff168929f8e3..12d7b31e005c8f70cfc48886ee8762ba75eb39fc 100644
--- a/packages/backend/src/models/DriveFile.ts
+++ b/packages/backend/src/models/DriveFile.ts
@@ -4,7 +4,6 @@
  */
 
 import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 import { id } from './util/id.js';
 import { MiUser } from './User.js';
 import { MiDriveFolder } from './DriveFolder.js';
@@ -61,8 +60,7 @@ export class MiDriveFile {
 	})
 	public size: number;
 
-	@Column('varchar', {
-		length: DB_MAX_IMAGE_COMMENT_LENGTH,
+	@Column('text', {
 		nullable: true,
 		comment: 'The comment of the DriveFile.',
 	})
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index 15e87648ff6ad9c89d0f1194d3ed341afcf1e114..92aff24b4bb02203b1a8ad5921f83e1ff7355aa0 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -168,6 +168,26 @@ export const packedMetaLiteSchema = {
 			type: 'number',
 			optional: false, nullable: false,
 		},
+		maxRemoteNoteTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxCwLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxRemoteCwLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxAltTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxRemoteAltTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
 		ads: {
 			type: 'array',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index bc8d3c0411d00418958ebb96c776daa286db6706..9d33658756f78ddc03280281df5a7702915ca03c 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -122,6 +122,11 @@ export class NodeinfoServerService {
 					enableMcaptcha: meta.enableMcaptcha,
 					enableTurnstile: meta.enableTurnstile,
 					maxNoteTextLength: this.config.maxNoteLength,
+					maxRemoteNoteTextLength: this.config.maxRemoteNoteLength,
+					maxCwLength: this.config.maxCwLength,
+					maxRemoteCwLength: this.config.maxRemoteCwLength,
+					maxAltTextLength: this.config.maxAltTextLength,
+					maxRemoteAltTextLength: this.config.maxRemoteAltTextLength,
 					enableEmail: meta.enableEmail,
 					enableServiceWorker: meta.enableServiceWorker,
 					proxyAccountName: proxyAccount ? proxyAccount.username : null,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index 74eb4dded7cea2307d3c7d99ca9907ad1e63da55..f67ff6ddc4b03b65ad1e304ea4c554143a757618 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -5,11 +5,11 @@
 
 import ms from 'ms';
 import { Inject, Injectable } from '@nestjs/common';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { DriveService } from '@/core/DriveService.js';
+import type { Config } from '@/config.js';
 import { ApiError } from '../../../error.js';
 import { MiMeta } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
@@ -56,6 +56,12 @@ export const meta = {
 			code: 'NO_FREE_SPACE',
 			id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
 		},
+
+		commentTooLong: {
+			message: 'Cannot upload the file because the comment exceeds the instance limit.',
+			code: 'COMMENT_TOO_LONG',
+			id: '333652d9-0826-40f5-a2c3-e2bedcbb9fe5',
+		},
 	},
 } as const;
 
@@ -64,7 +70,7 @@ export const paramDef = {
 	properties: {
 		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		name: { type: 'string', nullable: true, default: null },
-		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, default: null },
+		comment: { type: 'string', nullable: true, default: null },
 		isSensitive: { type: 'boolean', default: false },
 		force: { type: 'boolean', default: false },
 	},
@@ -77,6 +83,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.meta)
 		private serverSettings: MiMeta,
 
+		@Inject(DI.config)
+		private config: Config,
+
 		private driveFileEntityService: DriveFileEntityService,
 		private driveService: DriveService,
 	) {
@@ -94,6 +103,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
+			if (ps.comment && ps.comment.length > this.config.maxAltTextLength) {
+				throw new ApiError(meta.errors.commentTooLong);
+			}
+
 			try {
 				// Create file
 				const driveFile = await this.driveService.addFile({
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index 554101812694c8016bf5c67017c3916364b89fdd..1501abf9ced35d61fb5192e636045361f7c2ad51 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -9,8 +9,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { DriveService } from '@/core/DriveService.js';
+import type { Config } from '@/config.js';
 import { ApiError } from '../../../error.js';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -51,6 +51,12 @@ export const meta = {
 			code: 'RESTRICTED_BY_ROLE',
 			id: '7f59dccb-f465-75ab-5cf4-3ce44e3282f7',
 		},
+
+		commentTooLong: {
+			message: 'Cannot upload the file because the comment exceeds the instance limit.',
+			code: 'COMMENT_TOO_LONG',
+			id: '333652d9-0826-40f5-a2c3-e2bedcbb9fe5',
+		},
 	},
 	res: {
 		type: 'object',
@@ -66,7 +72,7 @@ export const paramDef = {
 		folderId: { type: 'string', format: 'misskey:id', nullable: true },
 		name: { type: 'string' },
 		isSensitive: { type: 'boolean' },
-		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH },
+		comment: { type: 'string', nullable: true },
 	},
 	required: ['fileId'],
 } as const;
@@ -76,6 +82,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	constructor(
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
+		@Inject(DI.config)
+		private config: Config,
 
 		private driveService: DriveService,
 		private roleService: RoleService,
@@ -90,6 +98,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.accessDenied);
 			}
 
+			if (ps.comment && ps.comment.length > this.config.maxAltTextLength) {
+				throw new ApiError(meta.errors.commentTooLong);
+			}
+
 			let packedFile;
 
 			try {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index 49d2e78d08bc24a36635ac35d00ed27aaf36dc5e..e20d482e7046243cf1da6de923c653083581054a 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -4,12 +4,14 @@
  */
 
 import ms from 'ms';
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { DriveService } from '@/core/DriveService.js';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
+import { ApiError } from '@/server/api/error.js';
+import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -26,6 +28,14 @@ export const meta = {
 	prohibitMoved: true,
 
 	kind: 'write:drive',
+
+	errors: {
+		commentTooLong: {
+			message: 'Cannot upload the file because the comment exceeds the instance limit.',
+			code: 'COMMENT_TOO_LONG',
+			id: '333652d9-0826-40f5-a2c3-e2bedcbb9fe5',
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -34,7 +44,7 @@ export const paramDef = {
 		url: { type: 'string' },
 		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		isSensitive: { type: 'boolean', default: false },
-		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, default: null },
+		comment: { type: 'string', nullable: true, default: null },
 		marker: { type: 'string', nullable: true, default: null },
 		force: { type: 'boolean', default: false },
 	},
@@ -44,11 +54,18 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
 		private driveFileEntityService: DriveFileEntityService,
 		private driveService: DriveService,
 		private globalEventService: GlobalEventService,
 	) {
 		super(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => {
+			if (ps.comment && ps.comment.length > this.config.maxAltTextLength) {
+				throw new ApiError(meta.errors.commentTooLong);
+			}
+
 			this.driveService.uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => {
 				this.driveFileEntityService.pack(file, { self: true }).then(packedFile => {
 					this.globalEventService.publishMainStream(user.id, 'urlUploadFinished', {
diff --git a/packages/backend/src/server/api/endpoints/notes/create.test.ts b/packages/backend/src/server/api/endpoints/notes/create.test.ts
index f3d887bb200af9f3c8d00272c280d53b0b63fd0a..18d80e867ba222ddfaf806c6b8b30920136e5219 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.test.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.test.ts
@@ -5,15 +5,12 @@
 
 process.env.NODE_ENV = 'test';
 
-import { readFile } from 'node:fs/promises';
-import { fileURLToPath } from 'node:url';
-import { dirname } from 'node:path';
 import { describe, test, expect } from '@jest/globals';
+import { loadConfig } from '@/config.js';
 import { getValidator } from '../../../../../test/prelude/get-api-validator.js';
 import { paramDef } from './create.js';
 
-const _filename = fileURLToPath(import.meta.url);
-const _dirname = dirname(_filename);
+const config = loadConfig();
 
 const VALID = true;
 const INVALID = false;
@@ -21,7 +18,12 @@ const INVALID = false;
 describe('api:notes/create', () => {
 	describe('validation', () => {
 		const v = getValidator(paramDef);
-		const tooLong = readFile(_dirname + '/../../../../../test/resources/misskey.svg', 'utf-8');
+		const tooLong = (limit: number) => {
+			const arr: string[] = [''];
+			arr.length = limit + 1;
+			arr.fill('a');
+			return arr.join('');
+		};
 
 		test('reject empty', () => {
 			const valid = v({ });
@@ -71,8 +73,8 @@ describe('api:notes/create', () => {
 					.toBe(INVALID);
 			});
 
-			test('over 500 characters cw', async () => {
-				expect(v({ text: 'Body', cw: await tooLong }))
+			test('over max characters cw', async () => {
+				expect(v({ text: '', cw: tooLong(config.maxNoteLength) }))
 					.toBe(INVALID);
 			});
 		});
@@ -220,7 +222,7 @@ describe('api:notes/create', () => {
 			});
 
 			test('reject poll with too long choice', async () => {
-				expect(v({ poll: { choices: [await tooLong, '2'] } }))
+				expect(v({ poll: { choices: [tooLong(config.maxNoteLength), '2'] } }))
 					.toBe(INVALID);
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 412491afaafee60f9ed1c7cba1947ba6e203ed14..d1cf0123dc306950678468a644f2294ee7188fa7 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -90,6 +90,12 @@ export const meta = {
 			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
 		},
 
+		maxCwLength: {
+			message: 'You tried posting a content warning which is too long.',
+			code: 'MAX_CW_LENGTH',
+			id: '7004c478-bda3-4b4f-acb2-4316398c9d52',
+		},
+
 		cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
 			message: 'You cannot reply to a specified visibility note with extended visibility.',
 			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
@@ -147,7 +153,7 @@ export const paramDef = {
 		visibleUserIds: { type: 'array', uniqueItems: true, items: {
 			type: 'string', format: 'misskey:id',
 		} },
-		cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
+		cw: { type: 'string', nullable: true, minLength: 1 },
 		localOnly: { type: 'boolean', default: false },
 		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
 		noExtractMentions: { type: 'boolean', default: false },
@@ -250,10 +256,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteCreateService: NoteCreateService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const contentLength = (ps.text?.length ?? 0) + (ps.cw?.length ?? 0);
-			if (contentLength > this.config.maxNoteLength) {
+			if (ps.text && ps.text.length > this.config.maxNoteLength) {
 				throw new ApiError(meta.errors.maxLength);
 			}
+			if (ps.cw && ps.cw.length > this.config.maxCwLength) {
+				throw new ApiError(meta.errors.maxCwLength);
+			}
 
 			let visibleUsers: MiUser[] = [];
 			if (ps.visibleUserIds) {
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index b9be145cafbdf4823703cd6581d5a011d9b8d605..dc94c78e7570cd01cc11c4a5b59e55eb2c8d9476 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -86,6 +86,12 @@ export const meta = {
 			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
 		},
 
+		maxCwLength: {
+			message: 'You tried posting a content warning which is too long.',
+			code: 'MAX_CW_LENGTH',
+			id: '7004c478-bda3-4b4f-acb2-4316398c9d52',
+		},
+
 		cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
 			message: 'You cannot reply to a specified visibility note with extended visibility.',
 			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
@@ -197,7 +203,7 @@ export const paramDef = {
 				format: 'misskey:id',
 			},
 		},
-		cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
+		cw: { type: 'string', nullable: true, minLength: 1 },
 		localOnly: { type: 'boolean', default: false },
 		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
 		noExtractMentions: { type: 'boolean', default: false },
@@ -297,10 +303,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteEditService: NoteEditService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const contentLength = (ps.text?.length ?? 0) + (ps.cw?.length ?? 0);
-			if (contentLength > this.config.maxNoteLength) {
+			if (ps.text && ps.text.length > this.config.maxNoteLength) {
 				throw new ApiError(meta.errors.maxLength);
 			}
+			if (ps.cw && ps.cw.length > this.config.maxCwLength) {
+				throw new ApiError(meta.errors.maxCwLength);
+			}
 
 			let visibleUsers: MiUser[] = [];
 			if (ps.visibleUserIds) {
diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts
index 79d2c62a241fb0b826b20c188ab877bb78a12c56..c9833b85d7c07d733c27f94751001a655b54d645 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts
@@ -54,7 +54,7 @@ export async function getInstance(
 			},
 			polls: {
 				max_options: 10,
-				max_characters_per_option: 50,
+				max_characters_per_option: 150,
 				min_expiration: 50,
 				max_expiration: 2629746,
 			},
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 5937eb9b4924a7961d0b545dc466a15c5079fc38..7d91b604268e3ae248e6eb98f3a10cecc5c81c15 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -9,10 +9,12 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import { MiNote } from '@/models/Note.js';
-import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { api, castAsError, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
+// Important: this must match the value of maxNoteLength in .config/ci.yml!
+const MAX_NOTE_TEXT_LENGTH = 3000;
+
 describe('Note', () => {
 	let Notes: Repository<MiNote>;
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 4a03234e9037d81b480910bc3de6476c17b3386a..4a29b27ac4dfb383cccd4f20a58bb79dc467c209 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -65,7 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</div>
 	<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
-	<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
+	<div v-show="useCw" :class="$style.cwFrame">
+		<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
+		<div v-if="maxCwLength - cwLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: cwLength > maxCwLength }]">{{ maxCwLength - cwLength }}</div>
+	</div>
 	<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
 		<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
 		<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text dir="auto" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
@@ -247,13 +250,16 @@ const submitText = computed((): string => {
 });
 
 const textLength = computed((): number => {
-	return (text.value + imeText.value).length + (cw.value?.length ?? 0);
+	return (text.value + imeText.value).length;
 });
 
 const maxTextLength = computed((): number => {
 	return instance ? instance.maxNoteTextLength : 1000;
 });
 
+const cwLength = computed(() => cw.value?.length ?? 0);
+const maxCwLength = computed(() => instance.maxCwLength);
+
 const canPost = computed((): boolean => {
 	return !props.mock && !posting.value && !posted.value &&
 		(
@@ -264,6 +270,7 @@ const canPost = computed((): boolean => {
 			quoteId.value != null
 		) &&
 		(textLength.value <= maxTextLength.value) &&
+		(cwLength.value <= maxCwLength.value) &&
 		(!poll.value || poll.value.choices.length >= 2);
 });
 
@@ -1316,10 +1323,13 @@ defineExpose({
 	}
 }
 
-.cw {
+.cwFrame {
 	z-index: 1;
 	padding-bottom: 8px;
 	border-bottom: solid 0.5px var(--divider);
+
+	width: 100%;
+	position: relative;
 }
 
 .hashtags {
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 072c5c761d31092b1bc1e423caa46635a977c53d..12fa73cbc07264ae2c7a06e92513cc0ffd293e95 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -5153,6 +5153,11 @@ export type components = {
       iconUrl: string | null;
       sidebarLogoUrl: string | null;
       maxNoteTextLength: number;
+      maxRemoteNoteTextLength: number;
+      maxCwLength: number;
+      maxRemoteCwLength: number;
+      maxAltTextLength: number;
+      maxRemoteAltTextLength: number;
       ads: {
           /**
            * Format: id