If you keep more than a handful of Laravel apps on your machine, the real problem is not php artisan serve. It is finding the right project fast enough to stay in flow.
That sounds minor until you are juggling client work, experiments, internal tools, and old repos with nearly identical names. Then the waste compounds: wrong .env, wrong database, wrong branch, wrong terminal tab, wrong PHP version, wrong queue worker. The fix is not another bookmark folder. The fix is a local project workflow that makes Laravel apps discoverable, identifiable, and safe to switch between.
My recommendation is simple: treat local Laravel projects like an indexed system, not a pile of folders. Use consistent naming, one searchable project root, machine-readable metadata, and a small layer of shortcuts. Once you do that, project sprawl stops feeling like chaos and starts feeling operational.
Start With a Single Project Map
Most Laravel sprawl begins with random placement. One app lives in ~/Code, another in ~/Sites, two more inside a client archive folder, and one odd internal tool is buried under Desktop/new-final-final. No launcher can fix a layout that has no rules.
The first move is to create a single top-level convention for active work. That does not mean every repo must physically live in one folder, but your active Laravel estate should follow one predictable structure.
A pragmatic layout looks like this:
~/work
/clients
/acme-billing-api
/acme-admin-portal
/northwind-dashboard
/products
/qcode-cms
/internal-ops
/experiments
/laravel-octane-bench
/rag-prototype
/archive
/old-client-portal
This is intentionally boring. That is the point. A naming scheme should remove decisions, not create branding opportunities.
A few rules matter more than people think:
- Put project type or ownership in the path:
clients,products,experiments,archive. - Make folder names globally unique on your machine.
- Stop using vague names like
admin,backend,api, ornew-appwithout context. - Move dead repos to
archiveso search results stay clean.
The useful shift here is cognitive. You are no longer asking, "Where did I put that Laravel app?" You are asking, "Which bucket should this live in?" That question is much cheaper.
Name Projects for Retrieval, Not Aesthetics
A lot of local confusion comes from pretty repo names that are terrible search keys. pulse, forge, core, platform, studio, dashboard all sound fine until you have six unrelated projects with overlapping intent.
Local naming needs to optimize for retrieval under pressure. When you are in Spotlight, Raycast, Alfred, a shell fuzzy finder, or a menu bar launcher, you want project names that disambiguate themselves immediately.
A better pattern is:
{owner-or-domain}-{app-name}-{role}
Examples:
acme-inventory-apiacme-inventory-adminnorthwind-client-portalqcode-content-pipelineinternal-support-desk
That may look slightly longer, but it pays for itself every single day. Long names are not the problem. Ambiguous names are the problem.
What Good Names Prevent
Good naming does more than help search. It prevents operational mistakes.
If you have both acme-api and acme-admin, you are less likely to run migrations in the wrong repo than if both directories are called some variation of backend. If one app is archived, the path itself tells you that before you even open it.
This also helps with Git branch lists, terminal prompts, Docker container names, and editor workspace tabs. A strong project name keeps paying rent across the whole toolchain.
My rule: if a folder name cannot tell you who it belongs to and what it does, rename it.
Add Lightweight Project Metadata
Folder structure gets you halfway. The next step is to make each Laravel app self-identifying.
When you open a repo after three weeks away, you should be able to answer these questions instantly:
- Which PHP version does it need?
- Does it use Valet, Herd, Sail, or a custom Docker stack?
- Which database does it talk to locally?
- Which branch is considered safe or default?
- Are queues, Horizon, Reverb, or Vite expected to be running?
Do not keep that in your head. Put it in the repo or alongside it.
A simple approach is a small machine-friendly file like .project-meta.json or a short PROJECT.md at the root.
{
"name": "acme-inventory-admin",
"runtime": "laravel-herd",
"php": "8.3",
"node": "22",
"database": "acme_inventory_admin",
"default_branch": "main",
"services": ["vite", "queue"],
"notes": "Uses S3-compatible local storage and requires Redis"
}
This is not about documentation theater. It is about making the project indexable by scripts and readable by humans.
For example, a launcher script can parse that file and tell you whether the app expects Sail or whether it should open a terminal and start npm run dev. Even if you never automate it, the metadata reduces hesitation.
The Hidden Win: Safer Context Switching
Most local friction is really context switching risk. You are not slow because cd is hard. You are slow because every project switch carries uncertainty.
That uncertainty creates annoying habits:
- opening
.envto double-check the database - checking
php -vbecause older apps might break - searching old notes for the right local URL
- guessing whether this repo uses
npm run devorcomposer dev
A tiny metadata file turns those repeated checks into one glance. That is a real workflow improvement, not a cosmetic one.
Use Searchable Shortcuts, Not Memory
Once your folders and names are sane, add a thin shortcut layer. This is where a launcher, menu bar app, Raycast script, shell function, or even fzf becomes genuinely useful.
The mistake is relying on memory first and tools second. Flip that. Assume you will not remember where the project lives or what command it needs. Build the lookup path so it is faster than remembering.
A shell-based version is enough for most developers.
# ~/.zshrc
lp() {
local root="$HOME/work"
local project
project=$(find "$root" -maxdepth 3 -type d \( -name .git -prune \) -o -name artisan -print 2>/dev/null | \
sed 's#/artisan##' | \
fzf --prompt='Laravel project > ' --height=40%)
[ -z "$project" ] && return
cd "$project" || return
echo "Switched to: $project"
}
That function is intentionally simple. It finds directories containing artisan, lets you fuzzy-search them, and drops you into the chosen app.
You can extend it without turning it into a framework:
- show branch name in the picker
- preview
.project-meta.json - open in Cursor or VS Code automatically
- copy the local URL
- start the expected services
If you prefer GUI tools, the same logic applies. A menu bar app or launcher is useful when it is backed by your conventions. Without that, it is just a prettier search box.
Example: From Finder Hunting to One Command
Before a real workflow:
- Open Finder.
- Search for a client name.
- Open the wrong repo first.
- Check
.env. - Open the terminal.
- Realize the app actually lives in another folder.
After a real workflow:
- Run
lp. - Type
acme adm. - Land in
acme-inventory-adminwith the right context.
That difference sounds trivial. It is not. Across dozens of switches per week, it is the difference between feeling sharp and feeling scattered.
Standardize Local Runtime Entry Points
A discoverable project is still annoying if every repo boots differently. One Laravel app wants composer run dev, another needs php artisan serve, another uses Sail, and one ancient client project still expects Valet plus a manual queue worker.
You do not need to eliminate those differences entirely, but you should normalize the entry point.
The cleanest pattern is to make every project support one obvious startup command, even if the internals differ.
For example, add a make dev, just dev, or Composer script per repo:
{
"scripts": {
"dev": [
"Composer\\Config::disableProcessTimeout",
"php artisan serve",
"php artisan queue:listen --tries=1",
"npm run dev"
]
}
}
For Sail-based apps, your shortcut can still show dev, but the implementation may call Docker instead. The external interface stays stable.
That matters because the brain remembers one action per project category much better than a pile of exceptions.
Where Teams Usually Get This Wrong
They document startup steps in a README and call it done. That helps onboarding, but it does not solve day-to-day retrieval. The local workflow still depends on recall.
A better rule is:
- README explains the stack.
- metadata describes the local context.
- one standard command starts the app.
- your launcher or shell function exposes both.
This is the difference between documentation and operations.
Make Wrong-Project Mistakes Harder
The real danger in project sprawl is not wasted seconds. It is doing the right action in the wrong repo.
That is how people run a migration against the wrong local database, delete seed data they needed, or commit to a stale branch they forgot existed.
You should make those mistakes harder by default.
Practical safeguards:
- Show the project name and Git branch in your shell prompt.
- Use distinct local database names per app, never shared generic names like
apporlaravel. - Put the app name in
.envvalues where it helps, such as Redis prefixes or queue names. - Add a visible
APP_NAMEthat makes the browser tab unmistakable. - Keep archived apps out of your active search scope.
A Good Safety Pattern
If you work on many similar client dashboards, create an environment check command that tells you exactly where you are before destructive work:
php artisan about
php artisan env
git branch --show-current
Better yet, wrap it:
ctx() {
echo "Project: $(basename "$PWD")"
echo "Branch: $(git branch --show-current 2>/dev/null)"
php artisan env 2>/dev/null
}
Run ctx before migrations, bulk imports, queue restarts, or search-and-replace work. It is a tiny habit, but it kills a surprising number of avoidable mistakes.
Build the Workflow in Layers
Do not over-engineer this on day one. The right approach is layered.
Start with the minimum system that solves the actual problem:
- One predictable folder structure.
- Clear, searchable names.
- Basic project metadata.
- One fuzzy shortcut or launcher.
- One standard local startup command.
That alone will clean up most Laravel project sprawl.
Only after that should you add richer tooling like a menu bar index, auto-detected local URLs, branch-aware launchers, or project dashboards. Those are useful, but they are multipliers. They are not the foundation.
This is why many local productivity tools disappoint. They try to become the solution while your filesystem, naming, and runtime conventions remain inconsistent underneath. Tooling helps, but tooling cannot rescue chaos you chose to keep.
The Practical Rule
If you regularly work across multiple Laravel codebases, stop treating project discovery as an informal habit. Turn it into a system.
The winning setup is not complicated: stable paths, explicit names, lightweight metadata, searchable shortcuts, and standardized startup commands. That combination removes friction without adding ceremony.
If you only change one thing this week, rename your ambiguous repos and put active Laravel apps under a single indexed root. That one move usually exposes the rest of the mess quickly, and once you can find projects reliably, every other local workflow improvement gets easier.
Read the full post on QCode: https://qcode.in/laravel-project-sprawl-keep-local-apps-findable/
Top comments (0)