Folder Sorter tool icon

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
Download Sort-DownloadsFolder.ps1Download file-organizer.shRead the sourceSee the full workflow

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:

Real terminal output
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\Downloads

On Linux or macOS (bash):

bash file-organizer.sh ~/Downloads

Options

-Path <folder>
folder to organize (default: Downloads)
-Apply
perform the moves; without it, print the plan only

What it does

Limitations

Know these before you rely on it:

Security notes — is this safe to run?

Compare the checksum of the file you downloaded against this value to be sure it was not tampered with en route:

FileSHA-256
Sort-DownloadsFolder.ps19a4b1b7a81e6a74f3991d96fc0a9534b201283eb01894a58cc6acada77083479
file-organizer.sh54eba83ec88b9ddb31d8e415d4ebc2501ed0455f3bf6b4a2ea02dec7274baaa4

Verify with Get-FileHash .\Sort-DownloadsFolder.ps1 -Algorithm SHA256 on Windows, sha256sum Sort-DownloadsFolder.ps1 on Linux or macOS.

Alternatives

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:

Sort-DownloadsFolder.ps1
<#
.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:

file-organizer.sh
#!/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

← All tools