Skip to main content
BuilderBot makes it easy to handle media files in your conversations. Learn how to send images, videos, audio files, and documents from local storage or URLs.

Sending Images

From Local Files

Send images stored in your project:
import { join } from 'path'
import { addKeyword } from '@builderbot/bot'

const imageFlow = addKeyword('photo')
    .addAnswer('Here is your image:', {
        media: join(process.cwd(), 'assets', 'sample.png')
    })
Use process.cwd() to get the current working directory and join() to create cross-platform file paths.

From URLs

Send images from the internet:
const imageFlow = addKeyword('logo')
    .addAnswer('Our company logo:', {
        media: 'https://example.com/images/logo.png'
    })

Dynamic Images

Send images based on user input or business logic:
const productFlow = addKeyword('product')
    .addAnswer('Which product? (1-5)', { capture: true })
    .addAction(async (ctx, { flowDynamic }) => {
        const productId = ctx.body
        const imageUrl = `https://api.example.com/products/${productId}/image.jpg`
        
        await flowDynamic([
            { body: `Product #${productId}`, media: imageUrl }
        ])
    })

Sending Videos

Video from URL

const videoFlow = addKeyword('video')
    .addAnswer('Check out this video:', {
        media: 'https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExYTJ0ZGdjd2syeXAwMjQ4aWdkcW04OWlqcXI3Ynh1ODkwZ25zZWZ1dCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/LCohAb657pSdHv0Q5h/giphy.mp4'
    })

Video from Local Storage

import { join } from 'path'

const tutorialFlow = addKeyword('tutorial')
    .addAnswer('Tutorial video:', {
        media: join(process.cwd(), 'videos', 'tutorial.mp4')
    })

Sending Audio Files

Audio from URL

const audioFlow = addKeyword('audio')
    .addAnswer('Listen to this:', {
        media: 'https://cdn.freesound.org/previews/728/728142_11861866-lq.mp3'
    })

Voice Messages

For voice note style messages, use audio files in supported formats (mp3, ogg, wav):
const voiceFlow = addKeyword('voice')
    .addAnswer('Voice message:', {
        media: join(process.cwd(), 'audio', 'greeting.mp3')
    })

Sending Documents

PDF Files

const pdfFlow = addKeyword('invoice')
    .addAnswer('Your invoice:', {
        media: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
    })

Other Document Types

Send any document type (docx, xlsx, txt, etc.):
const documentFlow = addKeyword('report')
    .addAction(async (ctx, { flowDynamic }) => {
        const reportPath = join(process.cwd(), 'reports', 'monthly-report.xlsx')
        
        await flowDynamic([
            { body: 'Monthly Report', media: reportPath }
        ])
    })

Sending Multiple Media Files

1

Create a media samples flow

const samplesFlow = addKeyword(['samples', 'media'])
    .addAnswer('💪 Sending multiple files...')
2

Chain multiple media messages

const samplesFlow = addKeyword(['samples', 'media'])
    .addAnswer('💪 Sending multiple files...')
    .addAnswer('Image from local:', {
        media: join(process.cwd(), 'assets', 'sample.png')
    })
    .addAnswer('Video from URL:', {
        media: 'https://media.giphy.com/media/LCohAb657pSdHv0Q5h/giphy.mp4'
    })
    .addAnswer('Audio file:', {
        media: 'https://cdn.freesound.org/previews/728/728142_11861866-lq.mp3'
    })
    .addAnswer('PDF document:', {
        media: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
    })

Receiving Media from Users

Handle media sent by users with event-based flows:

Detecting Media Messages

import { addKeyword, utils } from '@builderbot/bot'

const mediaFlow = addKeyword(utils.setEvent('MEDIA'))
    .addAnswer('Thanks for the image! Processing...', null, async (ctx, { flowDynamic }) => {
        // ctx contains the media information
        console.log('Media URL:', ctx.url)
        console.log('Media type:', ctx.type)
        
        // Download and process the media
        await processMedia(ctx.url)
        
        await flowDynamic('Image processed successfully!')
    })

Handling Different Media Types

const imageReceivedFlow = addKeyword(utils.setEvent('MEDIA'))
    .addAction(async (ctx, { flowDynamic }) => {
        if (ctx.type === 'image') {
            await flowDynamic('Processing your image...')
            // Your image processing logic
        }
    })
const documentReceivedFlow = addKeyword(utils.setEvent('DOCUMENT'))
    .addAnswer('Document received!', null, async (ctx, { flowDynamic }) => {
        console.log('Document name:', ctx.filename)
        console.log('Document URL:', ctx.url)
        
        await flowDynamic('We received your document and will review it.')
    })
const voiceNoteFlow = addKeyword(utils.setEvent('VOICE_NOTE'))
    .addAnswer('Voice note received!', null, async (ctx, { flowDynamic }) => {
        // Process voice note
        const transcription = await transcribeAudio(ctx.url)
        await flowDynamic(`You said: ${transcription}`)
    })

Sending Media via HTTP Endpoint

Send media programmatically through your API:
adapterProvider.server.post(
    '/v1/messages',
    handleCtx(async (bot, req, res) => {
        const { number, message, urlMedia } = req.body
        
        await bot.sendMessage(number, message, { 
            media: urlMedia ?? null 
        })
        
        return res.end('sent')
    })
)
Example API call:
curl -X POST http://localhost:3008/v1/messages \
  -H "Content-Type: application/json" \
  -d '{
    "number": "1234567890",
    "message": "Check this out!",
    "urlMedia": "https://example.com/image.jpg"
  }'

Using flowDynamic for Media

Send media dynamically within callbacks:
const catalogFlow = addKeyword('catalog')
    .addAction(async (ctx, { flowDynamic }) => {
        const products = await getProducts() // Your function
        
        for (const product of products) {
            await flowDynamic([
                {
                    body: `${product.name} - $${product.price}`,
                    media: product.imageUrl
                }
            ])
        }
    })

Media with Captions and Delays

Add context and pacing to media messages:
const productFlow = addKeyword('featured')
    .addAnswer('Our featured products:', { delay: 500 })
    .addAnswer('Product 1: Premium Widget', {
        media: 'https://example.com/product1.jpg',
        delay: 1000
    })
    .addAnswer('Product 2: Deluxe Gadget', {
        media: 'https://example.com/product2.jpg',
        delay: 1000
    })

Complete Media Example

import { join } from 'path'
import { createBot, createProvider, createFlow, addKeyword, utils } from '@builderbot/bot'
import { MemoryDB } from '@builderbot/bot'
import { BaileysProvider } from '@builderbot/provider-baileys'

const PORT = process.env.PORT ?? 3008

// Media samples flow
const fullSamplesFlow = addKeyword(['samples', utils.setEvent('SAMPLES')])
    .addAnswer('💪 Sending you various files...')
    .addAnswer('Image from local:', {
        media: join(process.cwd(), 'assets', 'sample.png')
    })
    .addAnswer('Video from URL:', {
        media: 'https://media.giphy.com/media/LCohAb657pSdHv0Q5h/giphy.mp4'
    })
    .addAnswer('Audio file:', {
        media: 'https://cdn.freesound.org/previews/728/728142_11861866-lq.mp3'
    })
    .addAnswer('PDF document:', {
        media: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
    })

// Handle received media
const mediaReceivedFlow = addKeyword(utils.setEvent('MEDIA'))
    .addAnswer('Thanks for the image!', null, async (ctx, { flowDynamic }) => {
        console.log('Received media:', ctx.url)
        await flowDynamic('I received your image successfully!')
    })

const main = async () => {
    const adapterFlow = createFlow([fullSamplesFlow, mediaReceivedFlow])
    const adapterProvider = createProvider(BaileysProvider)
    const adapterDB = new MemoryDB()

    const { handleCtx, httpServer } = await createBot({
        flow: adapterFlow,
        provider: adapterProvider,
        database: adapterDB,
    })

    // HTTP endpoint to send media
    adapterProvider.server.post(
        '/v1/media',
        handleCtx(async (bot, req, res) => {
            const { number, message, urlMedia } = req.body
            await bot.sendMessage(number, message, { media: urlMedia })
            return res.end('media sent')
        })
    )

    httpServer(+PORT)
}

main()

Best Practices

Keep file sizes reasonable to ensure fast delivery:
  • Images: < 5MB
  • Videos: < 16MB
  • Audio: < 16MB
  • Documents: < 100MB
Host media files on a CDN for better performance and reliability:
const media = 'https://cdn.yourservice.com/images/product.jpg'
Always wrap media sending in try-catch blocks:
.addAction(async (ctx, { flowDynamic }) => {
    try {
        await flowDynamic([{ 
            body: 'Your image', 
            media: imageUrl 
        }])
    } catch (error) {
        console.error('Failed to send media:', error)
        await flowDynamic('Sorry, failed to send the image.')
    }
})
Check that URLs are accessible before sending:
const isValidUrl = await checkMediaUrl(mediaUrl)
if (!isValidUrl) {
    await flowDynamic('Media not available')
    return
}

Supported Media Formats

TypeFormats
ImagesJPG, PNG, GIF, WebP
VideosMP4, AVI, MOV
AudioMP3, OGG, WAV, M4A
DocumentsPDF, DOC, DOCX, XLS, XLSX, TXT, ZIP

Next Steps

Build docs developers (and LLMs) love