Capture, version, and apply macOS Finder view settings as TOML profiles.
No existing tool manages per-folder Finder views. Each project root — list vs icon, column widths, sort order — has to be hand-configured, and the settings vanish when you clone the repo on a new machine. finderconf captures a folder's view as a named, human-editable TOML profile and applies it recursively to any directory tree. Global Finder preferences (com.apple.finder.plist) are also supported through the same profile format.
# Recommended: isolated install via uv
uv tool install finderconf
# Or run without installing
uvx finderconf- Set up a reference folder. Open any folder in Finder and adjust the view exactly how you want your future folders to look: list/icon/column/gallery, column widths, sort order, arrange-by, icon size.
- Capture it as a profile. Finder has to have recorded view state for the folder first — any in-Finder view change does that. The settings don't live inside the folder itself; Finder writes them into the parent folder's
.DS_Storekeyed by the child's name, so as long as the reference folder's parent is user-writable, adjusting any view option is enough. Then:The profile lands infinderconf export ~/dev/my-repo project-root
~/.config/finderconf/profiles/project-root.toml— open it, read it, tweak it. - Preview where you'll apply it. Always dry-run before a recursive apply:
The table shows every directory that would receive the profile and whether each would write, skip (filter mismatch), or warn (iCloud/network volume).
finderconf apply project-root ~/dev --depth 1 --dry-run - Apply it. Same command without
--dry-run. The writes all land in the target's own.DS_Store(because that's the parent of every candidate), that file is backed up once to~/.local/state/finderconf/backups/before the first write, and Finder is restarted after the batch so the changes become visible immediately. - If something looks off, revert.
finderconf restore <path> --lastrolls that folder back to its most recent backup.
Prefer a guided wizard? finderconf init walks you through creating a profile without capturing from an existing folder.
| Command | Description |
|---|---|
finderconf export <path> <name> |
Capture a folder's .DS_Store into a named profile |
finderconf apply <name> <path> |
Apply a profile recursively to a directory tree |
finderconf list |
List saved profiles; --verbose shows sections and timestamps |
finderconf show <path> |
Inspect a live folder's Finder settings |
finderconf edit <name> |
Open a profile in $EDITOR; validates on exit |
finderconf set <name> <key> <value> |
Set a single profile value via dot-path (preserves comments) |
finderconf diff <name> <path> |
Colored diff between a profile and a live folder |
finderconf delete <name> |
Remove a profile; --force skips confirmation |
finderconf init |
Interactive wizard to create a new profile |
finderconf restore <path> |
Re-apply last backup or clean .DS_Store |
finderconf global export <name> |
Snapshot com.apple.finder.plist into a profile's [global] |
finderconf global apply <name> |
Write [global] section via defaults write + restart Finder |
finderconf doctor |
Check environment: iCloud paths, DS_Store lib version, backups |
Profiles live in ~/.config/finderconf/profiles/<name>.toml. All sections are optional — applying a profile only touches the domains present in it.
[meta]
name = "project-root"
description = "Rust/TS project roots"
schema = 1
[view]
# list | icon | column | gallery
mode = "list"
[icon_view]
icon_size = 64
text_size = 12
label_on_bottom = true
show_icon_preview = true
arrange_by = "name" # none|name|dateModified|dateCreated|size|kind|label
[list_view]
icon_size = 16
text_size = 13
sort_column = "dateModified"
sort_ascending = false
[[list_view.columns]]
id = "name"; visible = true; width = 300
[[list_view.columns]]
id = "dateModified"; visible = true; width = 180
[[list_view.columns]]
id = "size"; visible = true; width = 100
[window]
sidebar_width = 200
show_sidebar = true
show_toolbar = true
show_path_bar = true
show_status_bar = trueThe [global] section maps to ~/Library/Preferences/com.apple.finder.plist and is only touched by finderconf global apply <name> or finderconf apply <name> <path> --as-default.
[global]
show_hidden_files = false # AppleShowAllFiles
show_all_extensions = true # AppleShowAllExtensions
default_view_style = "list" # list|icon|column|gallery
new_window_target = "home" # home|desktop|documents|icloud|recents|custom
new_window_target_path = "" # required when custom
default_search_scope = "current" # current|this_mac|previous
remove_trash_items_after_days = 30 # 0 disables auto-cleanup
show_warning_before_emptying_trash = true
show_warning_before_removing_from_icloud = true
keep_folders_on_top_in_windows = true
keep_folders_on_top_when_sorting = true- Per-folder view settings live in the parent directory's
.DS_Store, keyed by the child's basename — that's where Finder itself reads and writes them on macOS Sonoma+.finderconfuses the maintainedds_storePython library (v1.3.2+) withplistlibfor binary plist blobs. For edge cases where the parent isn't user-writable (e.g.~/when/Users/is not writable), settings are read and written in the folder's own.DS_Storeunder filename"."as a fallback. - Global Finder preferences live in
~/Library/Preferences/com.apple.finder.plist;finderconfreads viaplistliband writes viadefaults writesubprocess to avoidcfprefsdcache-staleness issues. - Profiles are TOML files in
~/.config/finderconf/profiles/, round-tripped viatomlkitso hand-written comments survivefinderconf setedits.
A recursive apply is a five-step pipeline; every step is observable and reversible:
- Resolve + guard. The target path is resolved through symlinks and rejected if it is a system path (
/System,/Library,/usr, …) or — unless--allow-outside-homeis passed — outside$HOME. - Plan. A BFS walk collects candidate directories, respects
--depthand--filter, skips hidden folders, and flags iCloud/network/Time Machine paths as warnings (skipped without--force). - Lock. An exclusive
fcntl.flockon~/.local/state/finderconf/.lockprevents two concurrent applies from racing. - Merge writes into each parent's
.DS_Store. Every candidate's records land in its parent's.DS_Store, keyed by the candidate's basename. Before the first write touches a given parent file, that file (if present) is backed up once to~/.local/state/finderconf/backups/. The write itself reads the existing store, drops only the managed-section codes (vstl,icvp,lsvp,clmv,Flwv,bwsp) for the target basename, inserts the new records, and atomically renames (.DS_Store.tmp.<pid>→os.replace). Records for other siblings, and non-managed codes likeIloc,moDD, andvSrn, are preserved verbatim. A per-candidate failure is captured without stopping the batch. - Restart Finder.
killall Finder(Finder respawns automatically) so the new view settings are visible immediately. Skipped when--dry-runor--no-restart.
- System paths refused: targets resolving to
/,/System,/Library,/usr,/bin,/sbin,/etc,/var,/private, or/Applicationsare rejected after symlink expansion. - Path traversal guard: target must resolve inside
$HOMEunless--allow-outside-homeis passed. - Atomic writes:
.DS_Storeis written to.DS_Store.tmp.<pid>in the same directory as the target and then renamed withos.replace(); profile TOML uses the same pattern. - Record-level merge: writing a profile for a directory replaces only the managed-section records for that directory's basename in the parent's
.DS_Store. Records for sibling folders and non-managed codes (Iloc,moDD,vSrn, …) are preserved. - Backup on write (default): before the first destructive change to a given parent
.DS_Store, that file is copied to~/.local/state/finderconf/backups/. Each parent is snapshotted once per apply run, regardless of how many siblings are being written. Usefinderconf restoreto revert. Skip with--no-backup. - Volume awareness: iCloud Drive paths, non-local network mounts, and Time Machine volumes trigger a warning and require
--forceto proceed. - Dry-run honesty:
--dry-runwrites nothing, backs up nothing, does not restart Finder, and prints a Rich table of planned changes. - Concurrency: a file lock at
~/.local/state/finderconf/.lockprevents two recursive applies from racing.
- macOS Sonoma (14) or later
- Python 3.14 or later
- Finder must be running for applied settings to take effect immediately
- iCloud Drive:
.DS_Storesync behavior is inconsistent;doctorwarns but cannot guarantee correctness. - Time Machine volumes: effectively read-only for
.DS_Storewrites. - Non-local network volumes:
DSDontWriteNetworkStoresmay silently suppress writes;doctorchecks this key. - Per-file icon positions (
Ilocblobs) are not managed in v1. - Background image/color application is exposed in the schema but not written in v1 (read-only).
No Finder view settings found for <path> — Finder records view state in the parent folder's .DS_Store keyed by the child's basename, and writes it lazily. A folder with no .DS_Store nearby — or a .DS_Store that doesn't yet have a record for this specific child — triggers this error. Open the folder in Finder, change any view option (toggle a column, resize one), then retry finderconf export. For directories whose parent isn't user-writable (e.g. ~/), Finder falls back to the folder's own .DS_Store under filename "."; finderconf checks both locations.
Settings don't update after apply — Ensure Finder is running (finderconf doctor reports this). If you ran with --no-restart, kill Finder manually: killall Finder.
another finderconf apply is running — A stale lock file from a crashed run. Remove it: rm ~/.local/state/finderconf/.lock.
warn-volume on a folder I want to configure — The path resolves under iCloud Drive, a /Volumes/* mount, or a Time Machine backup. Pass --force if you really want to proceed (expect inconsistent behavior).
Refusing to write to system path — The target resolves to (or under) a protected system directory. This guard cannot be disabled.
Path is outside $HOME — The target is on a mounted volume or /tmp. Pass --allow-outside-home to override. .DS_Store behaviour on external volumes varies by filesystem and volume type.
git clone https://github.com/Sephyi/finderconf.git
cd finderconf
uv sync
uv run pytest
uv run ruff check
pre-commit install# Fast iteration (editable install via uv sync)
uv run finderconf --help
uv run finderconf export ~/dev/foo bar
# As a module (good for pdb)
uv run python -m finderconf --help
# Install on $PATH pointing at this tree
uv tool install --from . finderconf
# Re-run with --force after code changes to pick them up.
# One-shot ephemeral run
uvx --from . finderconf --helpSee CONTRIBUTING.md for commit style, TDD expectations, and PR guidelines.
MIT — see LICENSE.
dmgbuild/ds_store— pure-Python.DS_Storeread/write library.- Mac-Finder-DSStore (CPAN) — the authoritative reverse-engineered
.DS_Storeformat specification.