2025, Oct 21 21:00

How to Upload Plotly Charts to Cloudinary from Memory: BytesIO and Base64, No Temp Files Needed

Learn to stream Plotly figures to Cloudinary without touching the filesystem using an in-memory BytesIO buffer or base64 data. Faster, safer uploads in Python.

Streaming Plotly figures to Cloudinary without touching the filesystem is a practical way to avoid permission headaches and temporary files. The key is to pass an in-memory buffer between Plotly and Cloudinary instead of a path on disk.

Baseline: the file-on-disk flow

The typical workflow writes an image to a local file and then uploads that file:

img_path = "chart.png"
viz_obj.write_image(img_path)
cloudinary.uploader.upload(
    img_path,
    asset_folder="reports",
    public_id="latest_plot",
    overwrite=True
)

This breaks down when the environment can’t write to disk or when you want to keep everything ephemeral.

What enables a zero-disk approach

The possibility hinges on both sides accepting a file-like object. Plotly’s write_image can target a writeable object, and Cloudinary’s uploader can consume a data_stream (byte array buffer). With that compatibility, an in-memory BytesIO buffer becomes the bridge.

Direct upload with an in-memory buffer

Create an in-memory buffer, render the figure into it, rewind the pointer, and pass the buffer to Cloudinary:

import io
buffer_io = io.BytesIO()
viz_obj.write_image(buffer_io, format="png")
buffer_io.seek(0)
cloudinary.uploader.upload(
    buffer_io,
    asset_folder="reports",
    public_id="latest_plot",
    overwrite=True
)

This approach keeps the entire operation in memory and avoids any filesystem permissions issues.

Alternative: base64 data string

Cloudinary also accepts base64 data. You can render the figure into a buffer, extract the bytes, encode them, and send a data URI:

import io
import base64
mem_buf = io.BytesIO()
viz_obj.write_image(mem_buf, format="png")
raw_bytes = mem_buf.getvalue()
b64_bytes = base64.b64encode(raw_bytes)
b64_text = b64_bytes.decode()
data_uri = "data:image/png;base64," + b64_text
cloudinary.uploader.upload(
    data_uri,
    asset_folder="reports",
    public_id="latest_plot",
    overwrite=True
)

Why this matters

Using a file-like object keeps data flow simple, secure, and fast. There is no need for temporary files, no cleanup, and fewer moving parts in restricted environments. If you already work with other visualization libraries, the idea is the same: render to a buffer and hand that buffer to the uploader. For example, if you’re using Altair, use its save method in place of write_image when writing into the buffer before uploading.

Wrap-up

If your goal is to avoid writing to disk, wire Plotly’s write_image to an in-memory BytesIO and feed that buffer to Cloudinary’s uploader. When a data buffer isn’t an option in your integration, base64 is a viable alternative. Keep the flow memory-only, and you’ll bypass file permissions entirely while keeping uploads straightforward.

The article is based on a question from StackOverflow by diogenes and an answer by furas.