Send photos and documents
Receive a Telegram photo or document, pass its file_id to another BJS command through options, and send it back with a caption or inline button.
This recipe receives a file and immediately sends it back through a second command. It passes the file ID in options, without creating a user property. You need a running test bot and a private Telegram chat with it. No external file hosting is required.
Create all three commands below before testing. Only /send-media waits for a reply; /photo and /document send the file they receive from it.
Receive a photo or document
Create /send-media with Answer Send a photo or document. and Wait for answer on. Leave Keyboard and Auto retry time in seconds empty. Its BJS handles the next reply:
// Command: /send-media
if (!user || !chat || chat.chat_type !== "private") { return; }
const photos = request && request.photo;
const photo = Array.isArray(photos) ? photos[photos.length - 1] : null;
const document = request && request.document;
if (sendMedia(photo, "/photo")) { return; }
if (sendMedia(document, "/document")) { return; }
Api.sendMessage({ text: "Please attach a photo or document." });
Bot.runCommand("/send-media");
function sendMedia(file, targetCommand) {
if (!file || typeof file.file_id !== "string" || !file.file_id) { return false; }
Bot.run({
command: targetCommand,
options: { file_id: file.file_id }
});
return true;
}The helper checks the file ID and passes it to the matching sender. Keep the helper in the same command. A picture attached as a file reaches the document branch; a picture sent as a photo reaches the photo branch. For a photo, the example uses the last supplied size.
Bot.run makes options.file_id available in the target command. A new independent message such as /photo does not automatically receive that value. If you do not need separate sender commands, you can call Api.sendPhoto or Api.sendDocument directly here with the received file ID.
Send the photo with a caption and button
Create /photo with empty Answer, Keyboard and Auto Retry interval, Wait for answer off, and this BJS:
// Command: /photo
if (!user || !chat || chat.chat_type !== "private") { return; }
const fileId = options && options.file_id;
if (typeof fileId !== "string" || !fileId) {
Api.sendMessage({ text: "Send /send-media and attach a photo first." });
return;
}
Api.sendPhoto({
photo: fileId,
caption: "Your photo",
reply_markup: {
inline_keyboard: [[
{ text: "Open Bots.Business help", url: "https://help.bots.business/" }
]]
}
});After receiving a photo, the bot sends it back with its caption and a button below it. You do not need to send /photo yourself. The URL button opens the help; it does not run a BJS callback command. For callback actions, see inline buttons.
Send the document
Create /document with empty Answer, Keyboard and Auto Retry interval, Wait for answer off:
// Command: /document
if (!user || !chat || chat.chat_type !== "private") { return; }
const fileId = options && options.file_id;
if (typeof fileId !== "string" || !fileId) {
Api.sendMessage({ text: "Send /send-media and attach a document first." });
return;
}
Api.sendDocument({
document: fileId,
caption: "Your document"
});Use photo for sendPhoto and document for sendDocument. Telegram describes the supported inputs and limits in sendPhoto and sendDocument.
Test the whole flow
Send
/photoor/documentmanually: the bot should ask you to use/send-media, because no file ID was passed.Send
/send-media, then attach a photo. Check that the bot immediately sends that photo back with its caption and help button.Send
/send-mediaagain and attach a document. Check the file and caption in the bot's reply.Repeat from another test user: each file must return to its sender. Neither flow should create
demo_photo_idordemo_document_idin user properties.Send ordinary text when asked for a file: the bot should ask again. Sending a known command, such as
/photo, cancels that wait and runs the command; withoutoptionsit should explain how to start again.After a successful send, enter
/photoor/documentin a new message. It should ask for a new attachment rather than reuse a previous file.
Choose options, parameters or stored data
For a structured handoff, use Bot.run({ command, options }) as above. The shorter Bot.runCommand(targetCommand, { file_id: fileId }) passes the same kind of object. Both receivers read options.file_id.
Appending an ID to a command name passes text in params. For example, Bot.runCommand("/document " + fileId) would require changing /document to read params instead of options.file_id. Use one contract consistently. The sender above expects options.
Calling /send-media again opens another question because its Wait for answer is on. To process a known file immediately, call /photo or /document, with waiting off.
Use a stored user property when a user must be able to request the same file in a later, independent message. That is a different flow: deliberately save the ID on receipt, then read it in the later command. The immediate flow above needs no property write or read. Passing to another command still adds a command call; keep the send in a local function if it does not need a separate handler. See choosing how long data should live.
If you used the earlier saved-file version of this recipe, update all three commands together and rename its collector /save-media to /send-media. Existing demo_photo_id and demo_document_id values are not used or deleted by this version. Remove those demo properties through Properties once no other commands need them.
Keep a reusable Telegram file_id, not file_unique_id. File IDs belong to the Telegram bot that received them; a copied project with a different bot token must obtain its own IDs. A local phone path such as /storage/... is not a Telegram upload. If you use a URL instead, Telegram must be able to fetch a supported file at that URL.
For a failed send, check Errors and the API callback guide. Receiving a file ID does not prove that a subsequent send succeeded.
Next: receive contacts, locations and group events, or collect text input.
Last updated
Was this helpful?