Folder Sorter
Sort and organize your Downloads folder — preview first, nothing deleted.
- Platforms
- Windows, Linux, macOS
- Needs
- Nothing to install. PowerShell is built into Windows; the bash version needs only a terminal.
- Files
- Sort-DownloadsFolder.ps1 (PowerShell) · file-organizer.sh (bash)
- Licence
- MIT — read, modify, redistribute
- Verified
- Ran on our test machine on 2026-10-17
- Last reviewed
- 2026-10-17
The problem it solves
The Downloads folder is a mix of invoices, screenshots, installers and media. Folder Sorter sorts files into sensible subfolders - and shows you the plan before moving a single byte.
Example output
Here is what the script printed when we ran it on a test computer:
PREVIEW ONLY - nothing will be moved. Re-run with -Apply to perform these moves. (this tool only ever moves files; it never deletes or overwrites) Audio/ (1 files) song.mp3 -> Audio/ Documents/ (2 files) invoice.pdf -> Documents/ notes.txt -> Documents/ Images/ (1 files) beach.jpg -> Images/ Installers/ (1 files) setup.msi -> Installers/ Preview complete. Re-run with -Apply to perform these moves.
Usage
Download the file, then run it from a terminal. On Windows (PowerShell):
powershell -NoProfile -ExecutionPolicy Bypass -File .\Sort-DownloadsFolder.ps1 -Path $HOME\DownloadsOn Linux or macOS (bash):
bash file-organizer.sh ~/DownloadsOptions
-Path <folder>- folder to organize (default: Downloads)
-Apply- perform the moves; without it, print the plan only
What it does
- Groups files by extension into Documents, Images, Video, Audio, Archives, Code, Installers and Other
- Preview by default: without -Apply it prints exactly what would move where
- Never overwrites: a name collision is reported and skipped
- Ignores hidden files and files without an extension
Limitations
Know these before you rely on it:
- Type-based sorting only: no date rules, no content rules, no watch mode - that is what Hazel-class tools are for
- A compound extension such as file.tar.gz is classified by the final extension (.gz) into Archives
- It only organises the top level of the folder you name, not subfolders
- The extension lists are fixed in the source; edit them if your workflow differs
Security notes — is this safe to run?
- This tool moves files, so treat it as a modifying operation: run it once without -Apply and read the plan
- Back up anything important before the first run on a real folder
- It refuses to overwrite an existing name, and it never deletes - but a wrong folder choice is still your responsibility
- Run it on ordinary user folders, never on system directories
Compare the checksum of the file you downloaded against this value to be sure it was not tampered with en route:
| File | SHA-256 |
|---|---|
Sort-DownloadsFolder.ps1 | 9a4b1b7a81e6a74f3991d96fc0a9534b201283eb01894a58cc6acada77083479 |
file-organizer.sh | 54eba83ec88b9ddb31d8e415d4ebc2501ed0455f3bf6b4a2ea02dec7274baaa4 |
Verify with Get-FileHash .\Sort-DownloadsFolder.ps1 -Algorithm SHA256 on Windows, sha256sum Sort-DownloadsFolder.ps1 on Linux or macOS.
Alternatives
- DropIt — Windows, rule-based sorting with a GUI
- Hazel — macOS, watch folders and rich rules (paid)
- Explorer / Finder sort — manual but zero-risk for a one-off tidy
- Advanced Renamer — Windows, batch rename and move with preview
Other tools on this site
- System Report — Check your PC specs: CPU, RAM, disk space and network — one free script, no install.
- Disk Usage — C drive full? Find which folder is using the most disk space on Windows, Mac or Linux.
- Large Files — Find the largest files on your PC — forgotten downloads, old backups, duplicate videos.
In context
This tool is one of the steps in the c drive full? free up disk space on windows 10/11 workflow — the guide explains what order to run things in and how to interpret the results.
Source
The complete PowerShell source, so you can audit it before running:
<#
.SYNOPSIS
Sort-DownloadsFolder - sorts a messy folder into subfolders by file type.
.DESCRIPTION
SAFE BY DEFAULT: prints a preview of what would move and changes nothing
until you pass -Apply. Never deletes anything. Never overwrites: a name
collision is reported and skipped. Ignores hidden files and files with no
extension. Moves top-level files only, never into subfolders recursively.
.EXAMPLE
powershell -ExecutionPolicy Bypass -File .\Sort-DownloadsFolder.ps1 -Path $HOME\Downloads
.EXAMPLE
powershell -ExecutionPolicy Bypass -File .\Sort-DownloadsFolder.ps1 -Path $HOME\Downloads -Apply
#>
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
param(
[string]$Path = (Join-Path $HOME 'Downloads'),
[switch]$Apply
)
$groups = @{
'Documents' = @('.pdf','.doc','.docx','.odt','.rtf','.txt','.md','.xls','.xlsx','.ods','.csv','.ppt','.pptx','.odp')
'Images' = @('.jpg','.jpeg','.png','.gif','.webp','.bmp','.svg','.heic','.raw','.cr2','.nef')
'Video' = @('.mp4','.mkv','.avi','.mov','.wmv','.flv','.webm','.m4v')
'Audio' = @('.mp3','.wav','.flac','.aac','.ogg','.m4a','.wma')
'Archives' = @('.zip','.rar','.7z','.tar','.gz','.bz2','.xz','.iso')
'Code' = @('.js','.ts','.py','.ps1','.sh','.html','.css','.json','.xml','.c','.cpp','.java','.go','.rs','.rb')
'Installers'= @('.exe','.msi','.dmg','.deb','.rpm','.appimage')
}
function Get-Group([string]$ext) {
foreach ($g in $groups.Keys) { if ($groups[$g] -contains $ext.ToLower()) { return $g } }
return 'Other'
}
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
Write-Error "Not a folder: $Path"
exit 1
}
if ($Apply) { Write-Host "APPLYING moves in: $Path" }
else { Write-Host "PREVIEW ONLY - nothing will be moved. Re-run with -Apply to perform these moves." }
Write-Host '(this tool only ever moves files; it never deletes or overwrites)'
Write-Host ''
$files = Get-ChildItem -LiteralPath $Path -File -Force -ErrorAction SilentlyContinue |
Where-Object { -not $_.Name.StartsWith('.') -and $_.Extension -ne '' }
if (-not $files) {
Write-Host 'Nothing to organize (no regular files with an extension found).'
exit 0
}
$plan = @{}
foreach ($f in $files) {
$g = Get-Group $f.Extension
if (-not $plan.ContainsKey($g)) { $plan[$g] = @() }
$plan[$g] += $f
}
foreach ($g in ($plan.Keys | Sort-Object)) {
Write-Host ("{0}/ ({1} files)" -f $g, $plan[$g].Count)
foreach ($f in $plan[$g]) {
$destDir = Join-Path $Path $g
$dest = Join-Path $destDir $f.Name
if ($Apply) {
if (-not (Test-Path -LiteralPath $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}
if (Test-Path -LiteralPath $dest) {
Write-Host (" skipped {0} (a file with that name already exists)" -f $f.Name)
continue
}
if ($PSCmdlet.ShouldProcess($f.FullName, "Move to $dest")) {
Move-Item -LiteralPath $f.FullName -Destination $dest -ErrorAction Stop
Write-Host (" moved {0}" -f $f.Name)
}
} else {
Write-Host (" {0} -> {1}/" -f $f.Name, $g)
}
}
}
Write-Host ''
if ($Apply) { Write-Host 'Done. Review the folders and delete anything you no longer need - manually.' }
else { Write-Host 'Preview complete. Re-run with -Apply to perform these moves.' }The bash version:
#!/usr/bin/env bash
# file-organizer.sh - sorts a messy folder into subfolders by file type.
# SAFE BY DEFAULT: prints a preview and changes nothing until -apply.
# Never deletes anything. Never overwrites: a name collision is skipped.
# Ignores hidden files and files with no extension. Top-level only.
# Run: bash file-organizer.sh [path]
# bash file-organizer.sh [path] -apply
set -u
PATH_TO_SORT="${1:-$HOME/Downloads}"
APPLY="${2:-}"
if [ ! -d "$PATH_TO_SORT" ]; then
echo "Not a folder: $PATH_TO_SORT" >&2
exit 1
fi
group_for() {
case "$1" in
.pdf|.doc|.docx|.odt|.rtf|.txt|.md|.xls|.xlsx|.ods|.csv|.ppt|.pptx|.odp) echo "Documents" ;;
.jpg|.jpeg|.png|.gif|.webp|.bmp|.svg|.heic|.raw|.cr2|.nef) echo "Images" ;;
.mp4|.mkv|.avi|.mov|.wmv|.flv|.webm|.m4v) echo "Video" ;;
.mp3|.wav|.flac|.aac|.ogg|.m4a|.wma) echo "Audio" ;;
.zip|.rar|.7z|.tar|.gz|.bz2|.xz|.iso) echo "Archives" ;;
.js|.ts|.py|.ps1|.sh|.html|.css|.json|.xml|.c|.cpp|.java|.go|.rs|.rb) echo "Code" ;;
.exe|.msi|.dmg|.deb|.rpm|.appimage) echo "Installers" ;;
*) echo "Other" ;;
esac
}
if [ "$APPLY" = "-apply" ]; then
echo "APPLYING moves in: $PATH_TO_SORT"
else
echo "PREVIEW ONLY - nothing will be moved. Re-run with -apply to perform these moves."
fi
echo "(this tool only ever moves files; it never deletes or overwrites)"
echo ""
# Collect top-level regular files with an extension, skipping dotfiles.
files=$(find "$PATH_TO_SORT" -maxdepth 1 -type f ! -name '.*' 2>/dev/null)
if [ -z "$files" ]; then
echo "Nothing to organize (no regular files with an extension found)."
exit 0
fi
for g in Documents Images Video Audio Archives Code Installers Other; do
count=0
lines=""
while IFS= read -r f; do
[ -z "$f" ] && continue
base=$(basename "$f")
case "$base" in
*.*) ;;
*) continue ;;
esac
ext=".${base##*.}"
[ "$(group_for "$ext")" = "$g" ] || continue
count=$((count + 1))
lines="$lines$f
"
done <<EOF
$files
EOF
[ "$count" -eq 0 ] && continue
echo "$g/ ($count files)"
echo "$lines" | while IFS= read -r f; do
[ -z "$f" ] && continue
base=$(basename "$f")
if [ "$APPLY" = "-apply" ]; then
dest_dir="$PATH_TO_SORT/$g"
mkdir -p "$dest_dir"
dest="$dest_dir/$base"
if [ -e "$dest" ]; then
echo " skipped $base (a file with that name already exists)"
elif mv "$f" "$dest" 2>/dev/null; then
echo " moved $base"
else
echo " FAILED $base (could not move)"
fi
else
echo " $base -> $g/"
fi
done
done
echo ""
if [ "$APPLY" = "-apply" ]; then
echo "Done. Review the folders and delete anything you no longer need - manually."
else
echo "Preview complete. Re-run with -apply to perform these moves."
fi