Analyze files by type, date and size, then move or copy them into automatically categorized folders — all from the terminal, with a colorful summary table.
A professional Node.js CLI tool built with commander, chalk and the
node:fs/promises + Stream API — asynchronous, memory-friendly and fully
tested with Jest.
- 📂 Categorize by file type — 60+ extension mappings out of the box
(
Documents,Images,Videos,Audio,Code,Archives, …) - 📅 Categorize by date —
YYYY-MM,YYYY-MM-DDorYYYYbuckets (uses the file birth time, falling back to the modification time) - ⚖️ Categorize by size — configurable tiers (
small,medium,large,huge) - 🔀 Combined dimensions —
--by-type --by-date --by-sizeproduces nested folders likeImages/2026-08/Small (<1 MB) - 🚚 Move or copy (
--mode copyleaves your originals untouched) - 🔍
--dry-run— preview the whole plan without touching a single byte - 🧩 Conflict strategies —
keep,rename(adds(1)), oroverwrite - ⚙️ JSON config file for reusable setups
- 🎨 Colored terminal table with per-category counts and sizes
- ⚡ Bounded parallelism so thousands of files finish fast
- 📦 Stream-based transfers keep memory bounded for huge files
Requirements: Node.js ≥ 18.17.
# global install (recommended)
npm install -g file-organizer-cli
# or run without installing
npx file-organizer-cli --help
# or install locally and run via npm scripts
npm install --save-dev file-organizer-clifile-organizer ~/Downloads --dry-run # preview what *would* happen
file-organizer ~/Downloads --by-type # move into category folders
file-organizer ~/Downloads --mode copy -o /tmp/stagingRunning without flags categorizes by file type (the default dimension).
Tip: Always start with a
--dry-runto review the plan. In interactive terminals,movemode also asks for confirmation before touching any file.
Usage: file-organizer [options] <directory>
Analyze a directory by file type, date and size, then move or copy files into
automatically categorized folders with a colored summary table.
Arguments:
directory directory to analyze and organize
Options:
-v, --version output the current version
-m, --mode <mode> operation mode: move or copy (default: "move")
-c, --conflict <strategy> conflict handling: keep, rename or overwrite (default: "rename")
-o, --output <dir> root folder where categorized folders are created
-t, --by-type categorize by file type (default when no dimension is set)
-d, --by-date categorize by modification date
-s, --by-size categorize by size tier
--date-format <format> date bucket format: YYYY-MM, YYYY-MM-DD or YYYY (default: "YYYY-MM")
-r, --recursive recurse into sub-directories
--max-depth <n> maximum recursion depth (with --recursive)
--include-hidden process dot-files as well
--exclude <pattern> glob pattern(s) to skip, may be repeated
--config <path> path to a JSON configuration file
--concurrency <n> number of parallel file operations (default: 8)
--dry-run preview the plan without touching the filesystem
--verbose print every file operation
--json <path> write a JSON report of the run to this path
-h, --help display help for command
Move loose files into type folders (with a confirmation prompt):
file-organizer ~/Downloads --by-type --recursivePreview everything first:
file-organizer ~/Downloads --by-type --by-date --by-size --dry-runCopy — originals stay in place:
file-organizer ~/Documents --mode copy --output /tmp/organizedSkip files:
file-organizer ~/Downloads --exclude '*.tmp' --exclude '/Pictures/**' --recursiveSee exclude patterns for the exact matcher syntax.
Write a machine-readable report:
file-organizer ~/Downloads --json report.jsonPresented here abbreviated; see src/core/config.ts for the full list of
60+ mappings.
| Extension | Folder |
|---|---|
pdf doc docx odt txt rtf md |
Documents |
xls xlsx ods csv |
Spreadsheets |
jpg jpeg png gif svg webp psd ai heic |
Images |
mp4 mkv avi mov webm m4v |
Videos |
mp3 wav flac ogg m4a aac opus |
Audio |
zip rar 7z tar gz bz2 iso |
Archives |
js ts tsx py rb java c cpp go rs sh json |
Code |
exe msi deb rpm apk appimage bin |
Executables |
ttf otf woff woff2 eot |
Fonts |
| everything else | Other |
Dates use the modification time bucket when birth time is unavailable, and sizes use the thresholds below (configurable, in bytes):
| Tier | Default range |
|---|---|
Small |
0 – < 1 MB |
Medium |
1 MB – < 100 MB |
Large |
100 MB – < 1 GB |
Huge |
≥ 1 GB |
--by-type --by-date --by-size nests the folders:
Images/2026-08/Small (<1 MB)/photo.jpg
Documents/2026-08/Medium (<100 MB)/thesis.pdf
You can keep reusable options in a JSON file and load it with --config.
CLI flags always win over the config file.
{
"mode": "move",
"conflict": "rename",
"recursive": true,
"byType": true,
"byDate": true,
"dateFormat": "YYYY-MM",
"exclude": ["**/.git/**", "node_modules/**", "*.tmp"],
"concurrency": 8,
"sizeThresholds": {
"smallMaxBytes": 1048576,
"mediumMaxBytes": 104857600,
"largeMaxBytes": 1073741824
},
"extensionMap": {
"psd": "Design",
"fig": "Design",
"sketch": "Design"
}
}Run it with:
file-organizer ~/Pictures --config ./file-organizer.config.json--exclude uses a small, documented glob dialect:
| Syntax | Meaning | Example |
|---|---|---|
* |
any run of chars except / |
*.tmp |
** |
any number of directory levels | **/node_modules/** |
? |
one char except / |
file?.txt |
{a,b} |
alternation | *.{png,jpg} |
leading / |
anchors to the scan root | /notes.txt (only at root) |
Patterns without a leading / match at any depth (gitignore-style).
--json <path> writes the full result of the run:
{
"dir": "/home/you/Downloads",
"scanned": 124,
"actions": {
"moved": 118,
"copied": 0,
"skipped": 0,
"skipped-hidden": 4,
"conflict": 0,
"error": 2
},
"operations": [ ... ],
"summary": [ ... ]
}The core is exposed as a small typed API:
import { scanDirectory, categorizeFile, organize, resolveOptions } from 'file-organizer-cli';
const options = resolveOptions({ dir: './Downloads', byType: true, dryRun: true });
const { entries, errors } = await scanDirectory(options.dir, options);
const result = await organize(entries, errors, options);
console.log(result.actions); // { moved: 32, copied: 0, ... }npm install
npm test # 49 unit tests using jest + mock-fs
npm run test:coverage # coverage report (≈90% target)
npm run lint # eslint (typescript-eslint)
npm run format:check # prettier formatting
npm run typecheck # tsc --noEmit
npm run build # produce dist/All checks run in the CI workflow: Node 18/20/22, type-check, lint, format and tests.
file-organizer-cli/
├─ src/
│ ├─ cli.ts # commander entry point
│ ├─ commands/organize.ts # orchestration wrapper (CLI + API)
│ ├─ core/
│ │ ├─ scanner.ts # async directory walker (fs/promises)
│ │ ├─ categorizer.ts # type / date / size buckets
│ │ ├─ organizer.ts # plan + execution, streams, concurrency
│ │ └─ config.ts # defaults, extension map, config merging
│ ├─ util/ # pattern matching, tables, logger, sizes
│ └─ index.ts # public API
├─ tests/ # Jest unit tests (mock-fs)
├─ examples/ # sample config + demo folder
└─ .github/workflows/ci.yml
Contributions are welcome! See CONTRIBUTING.md.
MIT © 2026 — see LICENSE.