Skip to content

copyMessages

Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, paid media messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field correct_option_id is known to the bot. The method is analogous to the method forwardMessages, but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of MessageId of the sent messages is returned.

Parameters

chat_idIntegerStringRequired
Unique identifier for the target chat or username of the target channel (in the format @channelusername)
message_thread_idIntegerOptional
Unique identifier for the target message thread (topic) of a forum; for forum supergroups and private chats of bots with forum topic mode enabled only
direct_messages_topic_idIntegerOptional
Identifier of the direct messages topic to which the messages will be sent; required if the messages are sent to a direct messages chat
from_chat_idIntegerStringRequired
Unique identifier for the chat where the original messages were sent (or channel username in the format @channelusername)
message_idsInteger[]Required
A JSON-serialized list of 1-100 identifiers of messages in the chat from\chat\id to copy. The identifiers must be specified in a strictly increasing order.
disable_notificationBooleanOptional
Sends the messages silently. Users will receive a notification with no sound.
protect_contentBooleanOptional
Protects the contents of the sent messages from forwarding and saving
remove_captionBooleanOptional
Pass True to copy the messages without their captions

Returns

On success, an array of MessageId objects is returned.

GramIO Usage

ts
// Copy a batch of messages from one chat to another (no attribution)
const 
results
= await
bot
.
api
.
copyMessages
({
chat_id
: -1001234567890,
from_chat_id
: "@sourcechannel",
message_ids
: [10, 11, 12, 13, 14],
});
console
.
log
(
"Copied message IDs:",
results
.
map
((
r
) =>
r
.
message_id
)
);
ts
// Copy messages silently without their captions
await 
bot
.
api
.
copyMessages
({
chat_id
: -1001234567890,
from_chat_id
: -1009876543210,
message_ids
: [20, 21, 22],
disable_notification
: true,
remove_caption
: true,
});
ts
// Paginate through message IDs and copy in batches of 100
async function 
copyMessageRange
(
fromChatId
: number,
toChatId
: number,
startId
: number,
endId
: number
) { const
batchSize
= 100;
const
allIds
=
Array
.
from
(
{
length
:
endId
-
startId
+ 1 },
(
_
,
i
) =>
startId
+
i
); for (let
i
= 0;
i
<
allIds
.
length
;
i
+=
batchSize
) {
const
batch
=
allIds
.
slice
(
i
,
i
+
batchSize
);
await
bot
.
api
.
copyMessages
({
chat_id
:
toChatId
,
from_chat_id
:
fromChatId
,
message_ids
:
batch
,
}); } } await
copyMessageRange
(-1009876543210, -1001234567890, 100, 350);
ts
// Copy messages to a forum topic thread
await 
bot
.
api
.
copyMessages
({
chat_id
: -1001234567890,
message_thread_id
: 456, // target forum topic
from_chat_id
: "@sourcechannel",
message_ids
: [30, 31, 32],
});

Errors

CodeErrorCause
400Bad Request: chat not foundchat_id or from_chat_id is invalid or inaccessible to the bot
400Bad Request: message IDs must be in strictly increasing ordermessage_ids array is not sorted in ascending order
400Bad Request: too many messagesmessage_ids contains more than 100 identifiers
403Forbidden: bot is not a member of the channel chatBot lacks access to the target channel
403Forbidden: not enough rightsBot does not have permission to post in the target chat
429Too Many Requests: retry after NRate limit exceeded — respect retry_after, use auto-retry plugin

TIP

Use GramIO's auto-retry plugin to handle 429 rate limit errors automatically when copying large message batches.

Tips & Gotchas

  • message_ids must be in strictly increasing order. If your IDs aren't sorted, sort them first with .sort((a, b) => a - b) — the API will reject unsorted arrays with a 400 error.
  • Maximum 100 IDs per call. For larger ranges, split into chunks of 100 and call copyMessages in sequence. Sending more than 100 IDs at once results in an error.
  • Uncopyable messages are silently skipped. Service messages, invoice messages, paid media, giveaway messages, and protected-content messages are skipped without error — the returned array will be shorter than your input.
  • Album grouping is preserved. If consecutive message IDs belong to a media album (group of photos/videos), they are copied as a group, maintaining the album layout.
  • No forwarding attribution. Unlike forwardMessages, copied messages have no "Forwarded from" header — ideal for relaying content without revealing the source.
  • remove_caption: true strips all captions. There's no per-message caption override in bulk mode — to set custom captions, use individual copyMessage calls.
  • Rate limits apply per chat. Telegram enforces message rate limits per target chat. When broadcasting to many chats, consider using allow_paid_broadcast (on copyMessage) or the auto-retry plugin.

See Also