When to Use
- Reusing assets at scale: A marketing workflow uploads a product catalogue PDF once at campaign start, captures the returned
mediaId, and references it across thousands of message/sendDocument calls — avoiding redundant uploads and reducing bandwidth.
- Dynamically generated reports: A finance workflow generates a monthly statement PDF in memory, uploads it to WhatsApp via this node, and sends the
mediaId to customers — no permanent file storage required.
- Image approval pipelines: A content moderation workflow uploads a candidate image, routes the
mediaId through a human-in-the-loop approval step, then sends only approved images to subscribers.
- Audio broadcast: A HR workflow records a voice announcement, uploads the audio file here, and distributes the
mediaId to all regional managers via message/sendAudio.
- Template header media: A retail workflow pre-uploads promotional banner images and stores their
mediaId values in a lookup table so template messages can reference them by ID without re-uploading on every send.
Configuration
Connection
| Field | Required | Description |
accessToken | Required | Permanent System User access token from Meta Business Manager. Store in BizFirst Credentials Manager and reference via {{ $credentials.whatsAppToken }}. |
phoneNumberId | Required | Numeric string ID of the WhatsApp Business phone number (e.g. 123456789012345). Media is scoped to this phone number. |
apiVersion | Optional | Meta Graph API version, e.g. v18.0. Defaults to v18.0. |
Operation
| Field | Required | Description |
fileContent | Required* | Base64-encoded binary content of the file to upload. Use this when the file is already in memory (e.g. generated by a previous node). *Either fileContent or filePath must be provided. |
filePath | Required* | Absolute path to the file on the server's filesystem. Use this when the file exists on disk. *Either filePath or fileContent must be provided. |
mimeType | Required | MIME type of the file. Must be a type supported by WhatsApp: image/jpeg, image/png, image/webp, video/mp4, video/3gpp, audio/ogg, audio/mpeg, audio/mp4, application/pdf, application/vnd.ms-powerpoint, application/msword, application/vnd.ms-excel, text/plain. |
fileName | Optional | Display name for the file (e.g. invoice_2024_01.pdf). Used as the filename visible to the recipient in document messages. Defaults to a generated name if omitted. |
File size limits enforced by WhatsApp: Images max 5 MB, videos max 16 MB, audio max 16 MB, documents max 100 MB. The node validates the size before uploading and routes to the error port if the limit is exceeded.
Sample Configuration
{
"resource": "media",
"operation": "upload",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"fileContent": "{{ $input.pdfBase64 }}",
"mimeType": "application/pdf",
"fileName": "statement_{{ $input.month }}_{{ $input.year }}.pdf"
}
Validation Errors
| Error | Cause |
accessToken is required | The accessToken field is empty or missing. |
phoneNumberId is required | The phoneNumberId field is empty or missing. |
mimeType is required | The mimeType field is empty or missing. |
fileContent or filePath is required | Neither fileContent nor filePath was provided. |
file_too_large | The file exceeds the configured maximum upload size. Check the per-type limits above. |
131053 (Meta) | Unsupported media type. The MIME type is not in WhatsApp's allowed list. |
parse_error | Meta returned a non-JSON response. Usually indicates a transient API outage. |
missing_id | Upload HTTP call succeeded but Meta did not return a media ID. Retry once. |
Output
Success Port
Fires when Meta accepts the upload and returns a media ID. Store the mediaId — it is valid for 30 days and can be used in any subsequent message send operation.
| Field | Type | Description |
mediaId | string | Unique identifier for the uploaded media (e.g. 1234567890123456). Pass this to message/sendImage, message/sendDocument, etc. |
status | string | "uploaded" on success. |
errorCode | string | Empty on success. |
errorMessage | string | Empty on success. |
rawResponse | string | Full raw JSON response from the Meta API. |
Error Port
Fires when the upload is rejected or the connection fails. Always connect the error port to a logging node in production workflows.
| Field | Type | Description |
status | string | "failed", "error", or "timeout". |
errorCode | string | Meta error code or BizFirst internal code such as file_too_large. |
errorMessage | string | Human-readable description of the failure. |
rawResponse | string | Raw API error JSON for diagnostics. May be null on network failures. |
Sample Output
{
"mediaId": "1234567890123456",
"status": "uploaded",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"id\":\"1234567890123456\"}"
}
Expression Reference
| Value | Expression | Notes |
| Media ID | {{ $output.uploadMedia.mediaId }} | Pass to message send operations as the media reference. Store persistently if reuse across workflow runs is needed. |
| Status | {{ $output.uploadMedia.status }} | "uploaded" on success. Use in an IfCondition node to branch on outcome. |
| Error code | {{ $output.uploadMedia.errorCode }} | Non-null on error port. Check for "file_too_large" and handle with a resize step. |
| Raw response | {{ $output.uploadMedia.rawResponse }} | Full JSON string returned by Meta. Parse with JsonTransform if additional fields are needed. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Credential storage | Always store the access token in BizFirst Credentials Manager. Never paste the raw token into node configuration. |
| Media ID lifetime | Uploaded media IDs are valid for 30 days. Do not hard-code IDs in workflow configuration — always upload fresh or use a lookup table with expiry tracking. |
| File size validation | Validate file size in a Function node before reaching this node to avoid wasting a round-trip. Use the limits: images 5 MB, video/audio 16 MB, documents 100 MB. |
| MIME type accuracy | The mimeType field must match the actual file content. WhatsApp validates MIME type server-side; a mismatch will cause a 131053 error. |
| Avoid duplicate uploads | If the same file will be sent to many recipients, upload once and store the mediaId in a workflow variable or database. Do not upload on every send iteration. |
| Error port handling | Always connect the error port. File-too-large errors need to be caught and rerouted to a resize or alternative delivery path. |
| PII in media | Files containing sensitive personal data are stored on Meta's servers for 30 days. Avoid uploading documents that contain unredacted PII unless required by the use case. |
Examples
Upload a Generated Invoice PDF
{
"resource": "media",
"operation": "upload",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"fileContent": "{{ $input.invoicePdfBase64 }}",
"mimeType": "application/pdf",
"fileName": "invoice_{{ $input.invoiceNumber }}.pdf"
}
A billing workflow generates a PDF invoice in a previous Function node, encodes it as base64, and passes it here. The returned mediaId is then passed to message/sendDocument to deliver the invoice to the customer's WhatsApp number.
Upload a Product Banner Image
{
"resource": "media",
"operation": "upload",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"filePath": "/app/assets/campaigns/summer_sale_banner.jpg",
"mimeType": "image/jpeg",
"fileName": "summer_sale.jpg"
}
A campaign setup workflow runs once at launch, uploads the banner from a known path on the application server, and stores the returned mediaId in a campaign settings table. Subsequent message-send nodes look up the ID from the table rather than uploading the image on every send.
Conditional Upload with Size Check
// In an upstream Function node:
// if ($input.fileSizeBytes > 5242880) throw "Image exceeds 5 MB WhatsApp limit";
{
"resource": "media",
"operation": "upload",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"fileContent": "{{ $input.imageBase64 }}",
"mimeType": "image/jpeg",
"fileName": "profile_{{ $input.userId }}.jpg"
}
The upstream Function node checks the decoded byte count before routing to this node. If the image is too large, it routes to a resize node first. This pattern prevents wasted API calls and provides a clean user-facing error message.