How to Upload a File in Python Tkinter

To upload a file in Python Tkinter, call filedialog.askopenfilename() from a button, then copy the chosen file to an uploads folder with shutil.copy2() or send it to a server with requests.post(). The dialog only returns a path:

path = filedialog.askopenfilename()
if path:
    shutil.copy2(path, "uploads")

My test setup was Python 3.12.5 (Tk 8.6), Pillow 12.3.0 and requests 2.34.2 on Windows 11. The windows below are real; a test hook picks the file so each run is repeatable.

You’ll build a basic upload button, add type and size checks, upload several files into a table, preview an uploaded image, and send a file to a web server.

Build a Tkinter file upload button

This is the complete pattern: a button opens the file dialog, the chosen file is copied into an uploads folder, and a label reports what happened. An empty string means the user cancelled:

import shutil
import tkinter as tk
from pathlib import Path
from tkinter import filedialog

UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)

def upload_file():
    path = filedialog.askopenfilename(title="Choose a file to upload")
    if not path:                                  # the user pressed Cancel
        status.config(text="No file selected")
        return
    src = Path(path)
    shutil.copy2(src, UPLOAD_DIR / src.name)      # copy keeps the original in place
    size_kb = src.stat().st_size / 1024
    status.config(text=f"Uploaded {src.name} ({size_kb:.1f} KB) to {UPLOAD_DIR}/")

root = tk.Tk()
root.title("File Upload")
root.geometry("420x140")
tk.Button(root, text="Upload File", width=16, command=upload_file).pack(pady=20)
status = tk.Label(root, text="No file uploaded yet")
status.pack()
root.mainloop()
Tkinter file upload window before and after uploading a CSV file, with a label showing the file name and size
Before the click, the label says nothing is uploaded; after it, the label names sales_q3_2026.csv with its size in KB.

copy2() also keeps the file’s timestamps. Every option the dialog itself takes, like initialdir and save dialogs, is in my Tkinter filedialog guide.

How do I limit the file types and size a Tkinter upload accepts?

The filetypes option only filters what the dialog shows, and users can still switch to “All files”. So check the extension and size again in code before copying:

import shutil
import tkinter as tk
from pathlib import Path
from tkinter import filedialog

UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
ALLOWED = {".csv", ".xlsx", ".pdf"}
MAX_MB = 5

def show(message, color):
    status.config(text=message, fg=color)

def upload_file():
    path = filedialog.askopenfilename(
        title="Choose a report",
        filetypes=[("Reports", "*.csv *.xlsx *.pdf"), ("All files", "*.*")],
    )
    if not path:
        return
    src = Path(path)
    size_mb = src.stat().st_size / 1_048_576
    if src.suffix.lower() not in ALLOWED:
        show(f"{src.name}: {src.suffix} files aren't allowed", "red")
    elif size_mb > MAX_MB:
        show(f"{src.name} is {size_mb:.1f} MB; the limit is {MAX_MB} MB", "red")
    elif (UPLOAD_DIR / src.name).exists():
        show(f"{src.name} was already uploaded", "darkorange")
    else:
        shutil.copy2(src, UPLOAD_DIR / src.name)
        show(f"Uploaded {src.name}", "green")

root = tk.Tk()
root.title("Upload a Report")
root.geometry("440x140")
tk.Button(root, text="Upload Report", width=16, command=upload_file).pack(pady=20)
status = tk.Label(root, text="CSV, XLSX or PDF, up to 5 MB")
status.pack()
root.mainloop()
Tkinter upload window showing a red error that a 6.1 MB CSV file is over the 5 MB upload limit
Picking a 6.1 MB export shows a red message that the limit is 5 MB, and nothing is copied.

In my run, the .exe was rejected first, the PDF went through in green, and picking that same PDF again gave the orange “already uploaded” message. For the size check itself, see getting a file size in Python.

I prefer a status label to a pop-up because it doesn’t block the user. If you want a pop-up anyway, use the dialogs from the Tkinter messagebox tutorial.

Upload multiple files in Tkinter

askopenfilenames() (with an s) returns a tuple of paths, so the user can Ctrl+click several files. A ttk.Treeview makes a tidy upload list:

import shutil
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, ttk

UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)

def upload_files():
    paths = filedialog.askopenfilenames(title="Choose files to upload")
    for path in paths:                            # empty tuple if the user cancels
        src = Path(path)
        shutil.copy2(src, UPLOAD_DIR / src.name)
        tree.insert("", "end", values=(src.name, f"{src.stat().st_size / 1024:,.1f} KB", "Uploaded"))
    summary.config(text=f"{len(paths)} file(s) uploaded to {UPLOAD_DIR}/")

root = tk.Tk()
root.title("Upload Files")
root.geometry("480x230")
ttk.Button(root, text="Choose Files...", command=upload_files).pack(pady=10)
tree = ttk.Treeview(root, columns=("name", "size", "status"), show="headings", height=4)
for col, text, width in (("name", "File", 200), ("size", "Size", 110), ("status", "Status", 100)):
    tree.heading(col, text=text)
    tree.column(col, width=width, anchor="w")
tree.pack(padx=10)
summary = ttk.Label(root, text="")
summary.pack(pady=6)
root.mainloop()
Tkinter window with a Treeview listing three uploaded files with their names, sizes in KB and Uploaded status
The CSV, the PDF and the PNG each get a row with their size, and the summary line counts three uploads.

Cancel returns an empty tuple, so the loop simply doesn’t run. More column, sorting and scrollbar options are in the Tkinter Treeview tutorial.

Upload an image and show a preview in Tkinter

For pictures, users expect to see what they uploaded. Pillow opens the file, makes a thumbnail, and ImageTk.PhotoImage puts it in a label:

import shutil
import tkinter as tk
from pathlib import Path
from tkinter import filedialog
from PIL import Image, ImageTk

UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)

def upload_image():
    path = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg *.gif")])
    if not path:
        return
    src = Path(path)
    with Image.open(src) as img:
        width, height = img.size
        img.thumbnail((320, 220))                 # preview only; the full file is copied
        photo = ImageTk.PhotoImage(img)
    preview.config(image=photo)
    preview.image = photo                         # keep a reference or the image disappears
    shutil.copy2(src, UPLOAD_DIR / src.name)
    caption.config(text=f"Uploaded {src.name} ({width} x {height} px)")

root = tk.Tk()
root.title("Upload an Image")
root.geometry("380x330")
tk.Button(root, text="Upload Image", command=upload_image).pack(pady=10)
preview = tk.Label(root)
preview.pack()
caption = tk.Label(root, text="")
caption.pack(pady=6)
root.mainloop()
Tkinter window showing a thumbnail preview of an uploaded storefront PNG image with its pixel size
The storefront picture appears as a thumbnail, and the caption gives the original 900 x 600 px size.

Don’t skip preview.image = photo. Without a saved reference, Python frees the image and the label goes blank. Other ways to show pictures are in adding images in Tkinter.

Upload a file from Tkinter to a web server

If “upload” means sending the file to a website or API, use requests.post() with files=. To keep the example self-contained, it starts a tiny local server that reports what it received:

import email
import json
import threading
from email import policy
from http.server import BaseHTTPRequestHandler, HTTPServer

import requests

class UploadHandler(BaseHTTPRequestHandler):     # stand-in for your real upload endpoint
    def do_POST(self):
        body = self.rfile.read(int(self.headers["Content-Length"]))
        raw = f"Content-Type: {self.headers['Content-Type']}\r\n\r\n".encode() + body
        msg = email.message_from_bytes(raw, policy=policy.default)
        files = [{"field": p.get_param("name", header="content-disposition"),
                  "filename": p.get_filename(), "bytes": len(p.get_payload(decode=True))}
                 for p in msg.iter_parts() if p.get_filename()]
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps({"saved": files}).encode())

    def log_message(self, *args):                # keep the console quiet
        pass

server = HTTPServer(("127.0.0.1", 8765), UploadHandler)
threading.Thread(target=server.serve_forever, daemon=True).start()

def upload_to_server(path, url="http://127.0.0.1:8765/upload"):
    with open(path, "rb") as f:
        response = requests.post(url, files={"file": (path, f, "text/csv")},
                                 data={"uploaded_by": "emily.carter"}, timeout=30)
    response.raise_for_status()
    return response.status_code, response.json()

status, reply = upload_to_server("sales_q3_2026.csv")
print("HTTP", status)
print(json.dumps(reply, indent=2))
server.shutdown()

Output:

HTTP 200
{
  "saved": [
    {
      "field": "file",
      "filename": "sales_q3_2026.csv",
      "bytes": 965
    }
  ]
}
Command Prompt output of Python requests posting a CSV file as multipart form data to a local upload server
The server answers HTTP 200 and lists the field name, the file name and the bytes it received.

In your app, replace the URL with your real endpoint and call upload_to_server(path) from the button’s function. For big files, run it in a threading.Thread so the window doesn’t freeze.

While a long upload runs, a Tkinter progress bar tells the user it’s still working. Update widgets from the main thread with root.after().

Tkinter upload methods compared

TaskUseReturns / does
Pick one filefiledialog.askopenfilename()A path string, '' on Cancel
Pick several filesfiledialog.askopenfilenames()A tuple of paths, () on Cancel
Upload to a foldershutil.copy2(src, dest)Copies data and timestamps
Upload to a serverrequests.post(url, files=...)Multipart form upload
Preview an imageImageTk.PhotoImage(img)Image for a Label

To keep building file features into a Tkinter app, read these:

Frequently asked questions

How do I upload a file in Python Tkinter?

Get a path with filedialog.askopenfilename(), then copy it with shutil.copy2() or send it with requests.post(url, files=...).

What does askopenfilename return if the user cancels?

An empty string, so check if not path: before using it.

How do I upload multiple files in Tkinter?

Use filedialog.askopenfilenames(), which returns a tuple of paths, and loop over it.

How do I restrict the upload to certain file types?

Pass filetypes=[("Reports", "*.csv *.pdf")], and check Path(path).suffix in code too.

Why does my uploaded image not show in Tkinter?

The PhotoImage was garbage collected. Keep a reference, such as label.image = photo.

Where is tkinter.filedialog documented?

See tkinter.filedialog in the Python docs.