A cluttered Downloads folder is the universal sign that your computer needs help. Instead of dragging files into folders by hand, you can write a short Python script that reads every file in a directory, identifies its type by extension, creates the right subfolder if it does not already exist, and moves the file there automatically. This tutorial builds that script from scratch using only the Python standard library.
The project uses four tools from the standard library: os.listdir() to get the names of files in a directory, os.path.splitext() to separate a filename from its extension, os.makedirs() to create destination folders, and shutil.move() to relocate each file. A Python dictionary ties everything together by mapping extensions to folder names. By the end of this article, you will have a working organizer and a solid understanding of the modules behind it.
Listing Files and Reading Extensions
The os module gives Python access to operating system functions like listing directory contents and working with file paths. The os.listdir() function returns a list of every file and folder name inside a given directory. If you pass no argument, it lists the current working directory.
import os
# List everything in the Downloads folder
items = os.listdir("/Users/you/Downloads")
print(items)
# ['report.pdf', 'photo.jpg', 'notes.txt', 'archive.zip', 'project']
The list includes both files and subdirectories. To filter out folders and work only with files, use os.path.isfile(). This function returns True when the path points to a file and False when it points to a directory or does not exist.
Extracting the Extension with os.path.splitext()
Once you have a filename, you need its extension so you can decide where it belongs. os.path.splitext() splits a filename into two parts: the base name and the extension (including the leading dot).
import os
name, ext = os.path.splitext("report.pdf")
print(name) # report
print(ext) # .pdf
# Works with compound extensions too
name2, ext2 = os.path.splitext("backup.tar.gz")
print(ext2) # .gz (only the last extension)
os.path.splitext() only splits on the last dot. A file named backup.tar.gz produces the extension .gz, not .tar.gz. For the vast majority of file-organizing tasks, the last extension is all you need.
Converting the extension to lowercase with .lower() is an important habit. Some operating systems produce extensions like .JPG or .Pdf, and a case mismatch would cause your dictionary lookup to fail.
_, ext = os.path.splitext("Photo.JPG")
ext_lower = ext.lower()
print(ext_lower) # .jpg
Build the line that splits a filename into its base name and extension:
Mapping Extensions to Folders with a Dictionary
A dictionary is the cleanest way to link file extensions to their target folders. Each key is an extension string, and the corresponding value is the folder name where files with that extension should go.
extension_map = {
# Images
".jpg": "Images", ".jpeg": "Images", ".png": "Images",
".gif": "Images", ".svg": "Images", ".webp": "Images",
# Documents
".pdf": "Documents", ".docx": "Documents",
".txt": "Documents", ".xlsx": "Documents", ".csv": "Documents",
# Audio
".mp3": "Audio", ".wav": "Audio", ".flac": "Audio",
# Video
".mp4": "Video", ".mov": "Video", ".mkv": "Video",
# Archives
".zip": "Archives", ".tar": "Archives",
".gz": "Archives", ".rar": "Archives",
# Code
".py": "Code", ".js": "Code", ".html": "Code", ".css": "Code",
}
Looking up a folder name is a single dictionary access: extension_map[ext_lower]. If the extension is not in the dictionary, you need a fallback. The dictionary .get() method accepts a second argument that is returned when the key is missing, which makes it perfect for a default "Other" folder.
folder = extension_map.get(ext_lower, "Other")
print(folder) # "Documents" for .pdf, "Other" for .xyz
Creating Folders with os.makedirs()
Before moving a file, its destination folder needs to exist. os.makedirs() creates a directory and all missing parent directories in the path. The exist_ok=True parameter tells Python to do nothing if the folder is already there instead of raising a FileExistsError.
import os
os.makedirs("Downloads/Images", exist_ok=True)
# Creates the folder if missing; does nothing if it already exists
Moving Files with shutil.move()
The shutil module provides high-level file operations. shutil.move() takes a source path and a destination path and moves the file. Unlike os.rename(), shutil.move() works across different filesystems and drives, which makes it the safer choice for a general-purpose organizer.
import shutil
shutil.move("Downloads/report.pdf", "Downloads/Documents/report.pdf")
- Purpose
- Returns a list of all file and folder names in a directory
- Module
os(standard library)- Key detail
- Returns names only, not full paths. Combine with
os.path.join()to build full paths.
- Purpose
- Splits a filename into (base, extension) including the dot
- Module
os.path(standard library)- Key detail
- Only splits on the last dot.
"archive.tar.gz"produces".gz".
- Purpose
- Creates a directory and all intermediate parent directories
- Module
os(standard library)- Key detail
- Pass
exist_ok=Trueto avoidFileExistsErrorwhen the folder already exists.
- Purpose
- Moves a file or directory to a new location
- Module
shutil(standard library)- Key detail
- Works across different filesystems and drives, unlike
os.rename().
os.listdir() returns names, not full paths. If you pass a directory like "/Users/you/Downloads" to os.listdir(), the returned list contains "report.pdf", not "/Users/you/Downloads/report.pdf". Use os.path.join(directory, filename) to build the full path before calling shutil.move().
This file organizer snippet should move a file into an Images folder. One line has a bug that will crash the script if the Images folder does not exist yet. Find it.
os.mkdir(dest_folder) with os.makedirs(dest_folder, exist_ok=True). Using plain os.mkdir() raises a FileExistsError if the Images folder already exists (for example, on the second run). Adding exist_ok=True tells Python to skip folder creation when the folder is already there.The Complete File Organizer Script
Here is the full program. It takes a single variable -- target_dir -- and sorts every file inside it into subfolders based on extension. Unrecognized extensions go into an "Other" folder. You can copy this script, change target_dir to your own Downloads path, and run it immediately.
import os
import shutil
# ── Configuration ──
target_dir = "/Users/you/Downloads"
extension_map = {
".jpg": "Images", ".jpeg": "Images", ".png": "Images",
".gif": "Images", ".svg": "Images", ".webp": "Images",
".pdf": "Documents", ".docx": "Documents",
".txt": "Documents", ".xlsx": "Documents", ".csv": "Documents",
".mp3": "Audio", ".wav": "Audio", ".flac": "Audio",
".mp4": "Video", ".mov": "Video", ".mkv": "Video",
".zip": "Archives", ".tar": "Archives",
".gz": "Archives", ".rar": "Archives",
".py": "Code", ".js": "Code", ".html": "Code", ".css": "Code",
}
# ── Organize ──
for item in os.listdir(target_dir):
source_path = os.path.join(target_dir, item)
# Skip directories — only process files
if not os.path.isfile(source_path):
continue
# Get the extension and normalize to lowercase
_, ext = os.path.splitext(item)
ext = ext.lower()
# Look up the destination folder (default to "Other")
folder_name = extension_map.get(ext, "Other")
dest_folder = os.path.join(target_dir, folder_name)
# Create the folder if it does not exist
os.makedirs(dest_folder, exist_ok=True)
# Move the file
dest_path = os.path.join(dest_folder, item)
shutil.move(source_path, dest_path)
print(f"Moved: {item} -> {folder_name}/")
Running this on a messy Downloads folder produces output like:
Moved: report.pdf -> Documents/
Moved: vacation.jpg -> Images/
Moved: song.mp3 -> Audio/
Moved: project.zip -> Archives/
Moved: notes.txt -> Documents/
Moved: mystery.xyz -> Other/
Test on a copy of your folder first. Create a temporary directory, drop a few test files in it, point target_dir at that folder, and run the script. Once you are confident the behavior is correct, switch to your real Downloads folder. shutil.move() cannot be undone with Ctrl+Z.
How to Build an Automatic File Organizer in Python
Follow these four steps to create the complete organizer from scratch.
Import os and shutil
Add
import osandimport shutilat the top of your script. These standard library modules provide everything you need for listing files, reading extensions, creating directories, and moving files.Define the extension-to-folder mapping
Create a dictionary where each key is a file extension string (like
".pdf") and each value is the folder name that extension belongs to (like"Documents"). Use the dictionary's.get()method with a default value of"Other"for unrecognized extensions.Loop through files and sort them
Use
os.listdir()to get every item in the target directory. For each item, confirm it is a file withos.path.isfile(), extract its extension withos.path.splitext(), look up the destination folder in your dictionary, create the folder withos.makedirs(exist_ok=True)if it does not exist, and move the file withshutil.move().Run and verify the results
Execute the script from a terminal pointing at a test folder. Confirm that each file has been moved into the correct subfolder and that no files were left behind. Once verified, point the script at your real Downloads folder.
"pathlib provides an object-oriented interface for managing file paths." — Python Documentation, pathlib module
Python Learning Summary Points
os.listdir()returns a list of filenames in a directory, andos.path.isfile()filters that list to only files.os.path.splitext()separates a filename into its base name and extension, giving you the key piece of data needed to decide where each file belongs.- A dictionary maps file extensions to folder names with a single lookup. The
.get()method with a default value handles unknown extensions gracefully, routing them to a catch-all "Other" folder without any extra conditional logic. os.makedirs(exist_ok=True)creates the destination folder and all intermediate parent directories without erroring when the folder already exists.shutil.move()then relocates each file to its new home, working reliably across different filesystems and drives.
The full script is under 30 lines. From here, you can extend it by scheduling it with cron or Task Scheduler, using the watchdog library to organize files in real time, or adding logging to track what was moved and when.
Frequently Asked Questions
os.listdir() returns a list of strings containing the names of every file and folder inside a given directory. If no path is provided, it lists the contents of the current working directory. It does not include the special entries . and ...
os.path.splitext() splits a filename into two parts: the base name and the file extension. For example, os.path.splitext('report.pdf') returns the tuple ('report', '.pdf'). The extension always includes the leading dot.
os.mkdir() creates a single directory and raises an error if the parent directory does not exist. os.makedirs() creates all intermediate directories in the path. Passing exist_ok=True to os.makedirs() prevents an error if the target folder already exists.
shutil.move() moves a file or directory from a source path to a destination path. It works across different drives and filesystems, unlike os.rename() which can only rename within the same filesystem. The destination can be a directory path, in which case the file keeps its original name.
On POSIX systems like Linux and macOS, shutil.move() silently overwrites an existing file at the destination. On Windows, it raises an error if a file with the same name already exists. To handle this safely, check whether the destination file exists before calling shutil.move().
A dictionary provides a clean, maintainable lookup table that maps each file extension to its target folder name. Adding or removing extension-to-folder mappings requires changing only the dictionary, not the logic of the program. This separation of data from code is a fundamental software design practice.
In the file organizer built in this tutorial, files with extensions not listed in the mapping dictionary are moved to a catch-all folder called "Other". This prevents unrecognized files from being left behind and keeps the source directory clean.
Yes. Both os and shutil are part of the Python standard library and ship with every Python installation. No pip install is necessary. You access them with import os and import shutil at the top of your script.
On macOS and Linux, you can schedule the script to run at regular intervals using cron. On Windows, you can use Task Scheduler. A third option is to use the watchdog library, which monitors a directory for changes in real time and triggers your organizer script whenever a new file appears.
os.path.isfile() checks whether a given path points to an existing file (not a directory). In a file organizer, it is used inside the loop to skip subdirectories and process only files.