DEV Community

Cover image for Keeping Laravel Projects Findable When Local Work Starts to Sprawl
Saqueib Ansari
Saqueib Ansari

Posted on • Originally published at qcode.in

Keeping Laravel Projects Findable When Local Work Starts to Sprawl

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
Enter fullscreen mode Exit fullscreen mode

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, or new-app without context.
  • Move dead repos to archive so 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-api
  • acme-inventory-admin
  • northwind-client-portal
  • qcode-content-pipeline
  • internal-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"
}
Enter fullscreen mode Exit fullscreen mode

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 .env to double-check the database
  • checking php -v because older apps might break
  • searching old notes for the right local URL
  • guessing whether this repo uses npm run dev or composer 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"
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Open Finder.
  2. Search for a client name.
  3. Open the wrong repo first.
  4. Check .env.
  5. Open the terminal.
  6. Realize the app actually lives in another folder.

After a real workflow:

  1. Run lp.
  2. Type acme adm.
  3. Land in acme-inventory-admin with 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"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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 app or laravel.
  • Put the app name in .env values where it helps, such as Redis prefixes or queue names.
  • Add a visible APP_NAME that 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
Enter fullscreen mode Exit fullscreen mode

Better yet, wrap it:

ctx() {
  echo "Project: $(basename "$PWD")"
  echo "Branch:  $(git branch --show-current 2>/dev/null)"
  php artisan env 2>/dev/null
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. One predictable folder structure.
  2. Clear, searchable names.
  3. Basic project metadata.
  4. One fuzzy shortcut or launcher.
  5. 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)