DevelopmentNovember 13, 20243 min read

Image Uploader App using Python and Streamlit

Build an image uploader with Python and Streamlit — preview uploads, read dimensions and file size, and export processed images.

Galvan

Galvan

Founder & Creator

Introduction

Handling file uploads securely and showing instantaneous feedback is an essential feature of modern web apps. Streamlit offers a built-in file uploader component, but what are the "secrets" to make it look professional, prevent errors, and optimize upload limits? Ready to do more with uploaded images? The Pillow image processing tutorial adds filters and transforms.

In this article, you will learn how to handle image uploads in Streamlit, display live previews, validate file formats/sizes, and export/save the uploaded files locally to the server's disk.


> 🎬 Watch the Full Video Tutorial:

> Watch the guide on YouTube: TOP Python PRO Shares Image Uploader SECRETS Using Streamlit


Prerequisites

Install the required PIL and Streamlit modules:

bash
pip install streamlit Pillow

Step 1: Create the Image Uploader App

Create a file named image_uploader.py and write the following code:

python
import streamlit as st
from PIL import Image
import os

st.set_page_config(page_title="Image Uploader Secrets", page_icon="🖼️", layout="centered")
st.title("🖼️ Professional Image Uploader")

# Local Directory to Save Images
UPLOAD_DIR = "uploaded_images"
if not os.path.exists(UPLOAD_DIR):
    os.makedirs(UPLOAD_DIR)

st.subheader("📤 Upload Your Image File")
uploaded_file = st.file_uploader(
    "Choose an image file (PNG, JPG, JPEG):", 
    type=["png", "jpg", "jpeg"]
)

if uploaded_file is not None:
    # 1. Read file as PIL Image
    try:
        image = Image.open(uploaded_file)
        
        # 2. Check File Metadata (Size, Type)
        file_size_kb = uploaded_file.size / 1024
        width, height = image.size
        
        # 3. Columns for Preview & Info
        col1, col2 = st.columns([2, 1])
        
        with col1:
            st.image(image, caption="Live Preview", use_container_width=True)
            
        with col2:
            st.markdown("### 📊 Image Metadata")
            st.write(f"**Filename:** `{uploaded_file.name}`")
            st.write(f"**Format:** `{image.format}`")
            st.write(f"**Resolution:** {width} x {height} px")
            st.write(f"**File Size:** {file_size_kb:.2f} KB")
            
            # Warn user if image is very large (e.g. over 2MB)
            if file_size_kb > 2000:
                st.warning("⚠️ This file exceeds 2MB! Consider compressing it for production applications.")

        # 4. Save to Disk Button
        st.subheader("💾 Save File to Server")
        save_path = os.path.join(UPLOAD_DIR, uploaded_file.name)
        if st.button("Save Image Locally"):
            with open(save_path, "wb") as f:
                f.write(uploaded_file.getbuffer())
            st.success(f"✅ Image saved successfully at `{save_path}`!")
            
    except Exception as e:
        st.error(f"An error occurred while reading the image: {e}")

Step 2: Run the App

Run the Streamlit server from your terminal:

bash
streamlit run image_uploader.py

How It Works

st.file_uploader is the entire intake system: it renders a drag-and-drop zone and returns an UploadedFile object (a BytesIO subclass) — no server-side temp files to manage. Restricting types with type=["png", "jpg", "jpeg"] filters at the browser level, which is better UX than validating after upload.

Once uploaded, the app opens the image with Pillow to read format, size, and mode, and reports the byte size from len(file.getvalue()). Showing metadata next to the preview (st.image) turns a bare uploader into a genuinely useful tool.

Two habits matter here. Reset the buffer with file.seek(0) after any read that advances the cursor, or later reads see an empty stream. And for downloads, write the processed image into a fresh BytesIO and hand it to st.download_button — the same in-memory pattern the QR code generator uses.

Key Concepts Covered

* `st.file_uploader()` — Accepts drag-and-drop file uploads and returns a buffer representation.

* `PIL.Image.open()` — Validates that the uploaded file is indeed a valid image file, preventing malicious text or binary uploads from breaking the system.

* `uploaded_file.getbuffer()` — Returns a writable buffer of the upload data, which we write to disk using python's standard write-binary (wb) file mode.

What to Try Next

* Auto-resizing: Read the uploaded image and resize it to standard thumbnail dimensions (e.g. 150x150 px) before saving it.

* Cloud Uploads: Modify the file handler to upload directly to Amazon S3 or Cloudflare R2 bucket.

Common Errors & Fixes

  • Second read of the upload returns nothing — the cursor advanced; call uploaded.seek(0) before re-reading.
  • `st.image` shows a garbled/blank frame — you passed raw bytes of an unsupported format; open with Pillow first, then pass the PIL object.
  • HEIC photos from iPhones fail — browsers and Pillow (without plugins) don't decode HEIC. Convert to JPEG first, or add the pillow-heif package.
  • App slows with multiple large uploads — each rerun re-processes every file; cache metadata with @st.cache_data keyed on file name and size.
  • FAQ

    Where do uploaded files go?

    Nowhere on disk by default — UploadedFile lives in memory (or a temp spool for big files). You explicitly save only if you want persistence.

    Can I accept multiple images at once?

    Yes — st.file_uploader(accept_multiple_files=True) returns a list; loop and render each preview in an st.columns grid.

    How do I resize before download?

    Call img.thumbnail((800, 800)) or img.resize(...), then export — full transform code is in the Pillow tutorial.

    How do I reject files over a size limit?

    Check len(uploaded.getvalue()) after upload and show st.error above 5 MB — validation happens in Python, so the limit is yours to define.