Solution Folder Sync
Keep Visual Studio solution folders in step with folders on disk — automatically.
Solution folders are virtual. Drop a folder of documentation into your repository and nothing
appears in Solution Explorer. Delete those files and dead entries linger. Realigning the two is
manual work you have to repeat after every git pull, branch switch, or file drop — and it
quietly rots the moment you stop.
This extension does it for you.
Quick start
1. Create .folder-sync.json beside your .sln or .slnx:
{
"sections": [
{
"mappings": [
{ "solutionFolder": "docs", "diskFolder": "docs" }
]
}
]
}
2. Reopen the solution (or use Tools → Sync Solution Folders).
That's it. The solution folder docs now mirrors the docs folder on disk. Add a file there and
it appears in Solution Explorer within a second; delete it and the entry disappears.
What you get
|
|
| Automatic |
Syncs when the solution opens, and continuously while it's open. |
| Handles removals |
Deleted files don't leave dead entries behind. |
| Survives git |
A pull or branch switch made while the solution was closed is reconciled on open. |
| Safe |
Never writes to, moves, or deletes your files. Ever. |
| Quiet |
A sync that changes nothing tells you nothing. |
| Both formats |
.slnx and .sln. |
| Shared config |
One file can serve several solutions in the same folder. |
Configuration
.folder-sync.json lives beside your solution file. Without it the extension does nothing at
all — no watchers, no output.
A single folder
{
"sections": [
{
"mappings": [
{ "solutionFolder": "docs",
"diskFolder": "req-arch/docs",
"exclude": ["*.tmp", "drafts/"] }
]
}
]
}
| Key |
Required |
Meaning |
solutionFolder |
yes |
The solution folder to keep in sync, e.g. docs or tools/scripts. |
diskFolder |
yes |
The folder on disk to mirror, relative to the solution file. |
exclude |
no |
Extra patterns to leave out, on top of the built-in defaults. |
Several folders
{
"sections": [
{
"mappings": [
{ "solutionFolder": "docs", "diskFolder": "docs" },
{ "solutionFolder": "scripts", "diskFolder": "build/scripts" },
{ "solutionFolder": "_agents", "diskFolder": ".claude" }
]
}
]
}
Several solutions sharing one config
Sections exist for repositories with more than one solution in the same directory. A section's
solutions list says which solutions it applies to; omit it and the section applies to all of
them.
{
"sections": [
{
"mappings": [
{ "solutionFolder": "shared", "diskFolder": "common" }
]
},
{
"solutions": ["App.slnx", "App.Tests.slnx"],
"mappings": [
{ "solutionFolder": "docs", "diskFolder": "app-docs" }
]
},
{
"solutions": ["Tools.slnx"],
"mappings": [
{ "solutionFolder": "docs", "diskFolder": "tools-docs" }
]
}
]
}
Open App.slnx and you get shared plus docs from app-docs. Open Tools.slnx and you get
shared plus docs from tools-docs.
solutions entries match the solution's file name, case-insensitively, and accept wildcards:
| Entry |
Matches |
App.slnx |
exactly that file |
*.slnx |
any XML-format solution |
App* |
App.slnx, App.Tests.slnx, … |
If no section matches the solution you open, the extension stays completely inert. That's normal
for a shared config — not an error.
Folder naming
solutionFolder is used exactly as you write it. docs gives you docs; _agents keeps its
underscore. Nested paths work — tools/scripts creates tools and scripts inside it.
The solution folder and disk folder need not sit at the same depth. This mounts the contents of
build/ci at tools/scripts in Solution Explorer:
{ "solutionFolder": "tools/scripts", "diskFolder": "build/ci" }
Excluding things
exclude patterns work like .gitignore:
| Pattern |
Effect |
*.tmp |
any .tmp file, at any depth |
**/*.bak |
any .bak file, at any depth |
drafts/ |
the drafts folder and everything under it |
drafts/*.md |
.md files directly inside drafts, nowhere else |
/notes.md |
notes.md at the top level only |
a?.md |
ab.md but not abc.md |
A pattern with no / matches at any depth. One containing or starting with / is anchored to the
mapping root. A trailing / restricts the match to folders.
Always excluded
These are excluded from every mapping. Your exclude list adds to them; it cannot switch them
off.
- Folders:
bin, obj, .git, .vs, Debug, Release, packages, node_modules
- Files:
*.user, *.suo, *.obj, *.pdb, and .folder-sync.json itself
- Projects:
*.csproj, *.vbproj, *.fsproj, *.vcxproj, *.vcxproj.filters, *.shproj
Project files are excluded deliberately, so synchronisation can never add or remove a real project
reference. Add projects the normal way; this extension won't touch them.
How it behaves
Disk is the source of truth
Anything under a mapped solution folder that isn't on disk — or that matches an exclude pattern —
is removed from the solution.
That includes files you added by hand in Solution Explorer: the next sync takes them out again. If
you want a file in the solution permanently, keep it outside the mapped folders.
Your files are never touched
Removing an entry unlinks it from the solution. Nothing is ever deleted, moved, or modified on
disk. The extension only ever reads the folders it watches.
When it syncs
- When you open a solution
- When a watched folder changes
- When
.folder-sync.json changes
- When you invoke Tools → Sync Solution Folders
Changes are batched, so pasting a folder or switching branches produces one sync rather than one
per file.
What doesn't trigger a sync
Editing a file's contents changes nothing — only additions and removals affect solution
structure.
Empty folders
An empty folder on disk becomes an empty solution folder, so the tree matches disk exactly.
Seeing what happened
A sync that changed something shows a summary in the status bar:
Solution folders synced: +3 -1
Full detail goes to View → Output → Solution Folder Sync:
14:22:07 --- sync (disk change): +3 -1 ---
14:22:07 + folder /docs/guide/
14:22:07 + file req-arch/docs/guide/intro.md -> /docs/guide/
14:22:07 + file req-arch/docs/readme.md -> /docs/
14:22:07 - file req-arch/docs/old.md
That pane is the place to look when you want to know why your solution changed. Warnings and
errors go there too.
A sync that changed nothing is silent.
Troubleshooting
Nothing happens at all.
Check .folder-sync.json sits beside the solution file, not in a subfolder, and that its name is
exact. Then invoke Tools → Sync Solution Folders — in an unconfigured solution it says so
explicitly, which distinguishes "no config" from "not working".
Nothing happens, and I have a config.
If it uses sections with solutions entries, check one matches your solution's file name. No
match means deliberate inactivity. The Output pane confirms parse errors.
A folder didn't appear.
Check the Output pane for a warning. The likely causes are a diskFolder that doesn't exist
(skipped with a warning — it is never created for you) or one pointing outside the solution
directory (rejected).
Files I expected are missing.
They're probably excluded. Build output, project files and the always-excluded list above are
filtered by default.
My hand-added file keeps disappearing.
Working as designed: disk is authoritative. Move it outside the mapped folder.
"The solution file cannot be edited".
The solution is read-only, or a source-control checkout was declined. Resolve that and the next
change syncs; or use the Tools command to retry immediately.
Requirements
- Visual Studio 2022 (17.0) or later, including Visual Studio 2026
- .NET Framework 4.7.2
Privacy
No telemetry, no analytics, no network access whatsoever. See PRIVACY.md.
Licence
MIT — see LICENSE.
Documentation