Slack Base64 Image Not Working? The no_file_data Fix

You have a Base64 string, you post it to Slack, and back comes { "ok": false, "error": "no_file_data" }. Nothing is wrong with your Base64. Slack simply does not accept Base64 as an upload format — it wants binary bytes in a multipart request. This page is the shortest path from "I have a Base64 string" to "the image is in the channel", with the exact code for Node.js and Python.

What Slack actually returns

The confusing part is that no_file_data sounds like an empty payload, so people go hunting for a truncation bug. There isn't one. Slack parses the request as multipart/form-data, looks for a file part containing binary content, finds a text field holding a 40,000-character string instead, and reports that it never received file data.

Two smaller hints usually accompany it: the error appears immediately (a network or size problem would hang first), and the same string renders fine in a browser if you paste it into a viewer — proof the encoding is intact.

Why Slack rejects a Base64 string

Base64 is a transport encoding: a way to move binary through channels that only accept text, such as JSON. Slack's file APIs are not one of those channels. They accept a real file — bytes with a filename — and expect the multipart body a browser would send if you had used a file input.

So the job is a conversion, not a workaround. Take the text, turn it back into bytes, attach those bytes as a file. Once you frame it that way, the fix is mechanical.

The fix, in one line

Every solution below starts the same way: strip the data URI prefix and everything before the comma.

const base64Only = dataUri.substring(dataUri.indexOf(',') + 1);

If your string starts with data:image/png;base64,, you want only what follows the comma. Send the prefix and Slack decodes garbage; send the whole thing as a form field and you get no_file_data.

Node.js — modern files.getUploadURLExternal flow

Slack's current recommended path is three calls. It avoids the deprecated files.upload endpoint and handles files larger than a few megabytes cleanly.

import { WebClient } from '@slack/web-api';

const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

/**
 * Post a Base64 image (with or without data URI prefix) to a channel.
 * @param {string} dataUri  e.g. "data:image/png;base64,iVBORw0..."
 * @param {string} channel  channel ID, e.g. "C01234567"
 */
export async function postBase64Image(dataUri, channel) {
  // 1. Normalise: accept both raw Base64 and a full data URI.
  const base64Only = dataUri.includes(',')
    ? dataUri.substring(dataUri.indexOf(',') + 1)
    : dataUri;

  // 2. Text -> bytes. Do NOT post the string itself.
  const buffer = Buffer.from(base64Only, 'base64');

  // 3. Call 1: ask Slack where to put it.
  const { upload_url, file_id } = await slack.files.getUploadURLExternal({
    filename: 'image.png',
    length: buffer.length,
  });

  // 4. Call 2: POST the raw bytes to that URL (no auth header, not JSON).
  const put = await fetch(upload_url, { method: 'POST', body: buffer });
  if (!put.ok) throw new Error(`upload failed: ${put.status}`);

  // 5. Call 3: attach the uploaded file to a channel.
  return slack.files.completeUploadExternal({
    files: [{ id: file_id, title: 'image.png' }],
    channel_id: channel,
  });
}

Useful detail: Buffer.from(str, 'base64') is lenient — it ignores whitespace and newlines, so a string that has been line-wrapped at 76 characters still decodes correctly.

Node.js — legacy files.upload (still the fastest patch)

If you are patching older code and the modern flow is more refactor than you want today, this works and is what most Stack Overflow answers settle on. It does require writing a temporary file.

import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { WebClient } from '@slack/web-api';

const slack = new WebClient(process.env.SLACK_BOT_TOKEN);

export async function postBase64ImageLegacy(dataUri, channel) {
  const base64Only = dataUri.includes(',')
    ? dataUri.substring(dataUri.indexOf(',') + 1)
    : dataUri;
  const tmp = path.join(os.tmpdir(), `slack-${Date.now()}.png`);
  fs.writeFileSync(tmp, base64Only, 'base64');

  try {
    return await slack.files.upload({
      channels: channel,
      filename: 'image.png',
      filetype: 'png',
      file: fs.createReadStream(tmp),
    });
  } finally {
    fs.unlinkSync(tmp);
  }
}

Python

Python users have a cleaner option: files_upload_v2 accepts an in-memory BytesIO, so nothing ever touches disk.

import base64, io, os
from slack_sdk import WebClient

slack = WebClient(token=os.environ["SLACK_BOT_TOKEN"])

def post_base64_image(data_uri: str, channel: str) -> dict:
    # 1. Strip the data URI prefix if present.
    base64_only = data_uri.split(",", 1)[1] if "," in data_uri else data_uri

    # 2. Decode text -> bytes, in memory.
    raw = base64.b64decode(base64_only)

    # 3. Upload the bytes as a real file part.
    return slack.files_upload_v2(
        channel=channel,
        filename="image.png",
        title="image.png",
        file=io.BytesIO(raw),
    )

One difference from JavaScript worth knowing: base64.b64decode is strict. A wrapped or truncated string raises binascii.Error instead of quietly producing a short buffer — which is a feature, because it fails at the encode boundary instead of as a corrupt image in Slack.

The three ways this still goes wrong

If your Base64 came from a JSON payload

The reverse direction trips people up just as often: pulling an image out of an API response. Keys are not standardised — you will see image, data, content, file, or a nested attachments[0].data — and the string may or may not carry the data: prefix. If you got an opaque blob back and want to see what it actually is, the Base64 to Image decoder detects the format from the magic bytes rather than trusting the declared MIME type.

Frequently asked questions

Does Slack support sending Base64 images in a message?

Not directly. Base64 is not a message attachment format. Convert it to bytes, upload it through the file APIs, then reference the uploaded file in your message.

Can I avoid writing a temporary file?

Yes. Buffer.from(str, 'base64') in Node and io.BytesIO(base64.b64decode(...)) in Python both keep everything in memory — the modern upload flow above never touches disk.

Why does my image upload but show as corrupted?

Almost always a prefix that was not stripped, or a truncated string. Decode it locally first and check the magic bytes start with \x89PNG (PNG) or \xFF\xD8\xFF (JPEG) — the invalid Base64 guide walks through the diagnosis.

What is the size limit?

Slack's file limits depend on your plan. Base64 expands your payload by about 33%, so a 5 MB image becomes roughly 6.7 MB of text before you start — worth checking with the size calculator before building the string.

Does this work with Slack incoming webhooks?

No. Webhooks accept text and Block Kit only. File uploads require a bot token and the file APIs above.

Do I need special scopes?

The bot needs files:write, plus files:read if you want to verify the upload afterwards. A missing scope returns not_allowed_token_type rather than no_file_data, which is a useful way to tell the two failures apart.

Sending images to other chat platforms? Telegram's sendPhoto rejects data URIs the same way — the Telegram Bot API guide covers the multipart fix — and discord.js needs the string decoded to a Buffer first, per the discord.js Base64 guide. Building the payloads for AI vision APIs goes the opposite direction: the Image to Base64 for API page outputs the full data URI shape those endpoints want, and the homepage converter produces plain data URIs for HTML, CSS and email.