The Problem Every Package Developer Knows

Ask any Laravel developer who maintains internal or private packages about their development workflow and you will hear some version of the same story.

You are working on company/crm-package which needs to be tested inside app-one. The official workflow is:

  1. Edit composer.json in app-one to add a "repositories" entry pointing to your local package directory
  2. Run composer update company/crm-package
  3. Make a change in the package
  4. Either run composer update company/crm-package again, or clear the Composer class cache, or restart your queue worker, or some combination of all three
  5. Repeat

In a monorepo with five packages that reference each other, this becomes a genuinely painful workflow that consumes significant development time. I measured it once: between Composer invocations, cache-busting steps, and debugging phantom "old code still running" issues, the overhead was roughly 20 minutes per hour of actual package development.

LaraLink is the tool I built to eliminate that overhead.

How LaraLink Works

LaraLink uses Composer's path repository type to create a symlinked connection between your local package directory and your application. The difference from doing this manually is that LaraLink:

  • Handles the composer.json modification for you
  • Tracks all links in a local .laralink registry file
  • Ensures the autoloader is reloaded correctly after linking
  • Provides a clean CLI to manage all your linked packages at once
  • Removes all traces when you unlink
your-app/
├── composer.json          ← LaraLink modifies this
├── .laralink              ← LaraLink creates and manages this
└── vendor/
    └── company/
        └── crm-package -> ../../packages/crm-package  ← Symlink created by LaraLink

Installation

composer require amjadiqbal/laralink --dev
php artisan laralink:install

The --dev flag ensures LaraLink itself does not end up in production deployments.

Linking a Package

# Link a package from a relative path
php artisan laralink:link ../packages/my-filament-widget

# Or an absolute path
php artisan laralink:link /Users/amjad/code/packages/my-filament-widget

LaraLink reads the package's composer.json to extract the package name and namespace, then:

  1. Adds a path repository to your application's composer.json
  2. Adds the package as a require-dev dependency
  3. Runs composer update to create the symlink
  4. Records the link in .laralink

After linking, changes to the package source are reflected immediately in your application without any Composer or cache intervention.

Checking Your Links

php artisan laralink:status

Output:

┌─────────────────────────────────────────────────────────┐
│ LaraLink Status                                         │
├──────────────────────────┬──────────────────────────────┤
│ Package                  │ Path                         │
├──────────────────────────┼──────────────────────────────┤
│ company/crm-package      │ ../packages/crm-package      │
│ company/email-templates  │ ../packages/email-templates  │
└──────────────────────────┴──────────────────────────────┘
2 packages linked

Unlinking When Done

When you are ready to switch back to the Packagist version:

php artisan laralink:unlink company/crm-package

LaraLink:

  1. Removes the path repository from composer.json
  2. Changes the requirement from the linked version back to a semver constraint
  3. Runs composer update to restore the installed package from Packagist
  4. Removes the record from .laralink

The .laralink Registry File

{
    "version": 1,
    "links": {
        "company/crm-package": {
            "path": "../packages/crm-package",
            "linked_at": "2026-03-10T14:32:00Z"
        },
        "company/email-templates": {
            "path": "../packages/email-templates",
            "linked_at": "2026-03-11T09:15:00Z"
        }
    }
}

This file should be added to .gitignore — it is a local developer state file, not application configuration.

Monorepo Support

LaraLink is particularly useful in monorepo setups. If you have a repository structure like:

workspace/
├── apps/
│   ├── app-one/
│   └── app-two/
└── packages/
    ├── crm-package/
    ├── notifications/
    └── billing/

You can link all packages to an application in one pass:

cd apps/app-one
php artisan laralink:link-all ../../packages/

LaraLink will discover all directories in the target path that contain a valid composer.json and link them all.

Why Not Just Use Composer Path Repositories Directly?

You can. LaraLink is a convenience tool, not a technical breakthrough. The value is:

  • No manual composer.json editing — error-prone in complex files with many dependencies
  • Centralised state.laralink tells you exactly what is linked without reading composer.json
  • Clean unlink — restoring composer.json to its pre-link state is non-trivial manually
  • Team conventions — the Artisan CLI makes the workflow consistent across team members

GitHub Repository

Source code and CLI documentation on GitHub.

composer require amjadiqbal/laralink --dev

Conclusion

LaraLink will not change your life. But if you develop Laravel packages regularly, it will quietly eliminate a small but consistently annoying source of friction. Sometimes the best tools are the ones you stop thinking about because they just work.