Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Packagist Api Laravel Package

spatie/packagist-api

Spatie's PHP package makes it easy to search and fetch package info from the Packagist API. Pass a Guzzle client and URL generator to the client, then list package names by type or vendor, fetch popular packages with pagination, and more.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and bind it once — there is no service provider. This is a framework-agnostic client: composer require spatie/packagist-api gives you a PackagistClient whose constructor wants a Guzzle client and a PackagistUrlGenerator. Nothing auto-registers, there's no config file, and there's no facade. Register a singleton in AppServiceProvider with sane HTTP defaults so the rest of your app never constructs it:

composer require spatie/packagist-api
use GuzzleHttp\Client;
use Spatie\Packagist\PackagistClient;
use Spatie\Packagist\PackagistUrlGenerator;

// AppServiceProvider::register()
$this->app->singleton(PackagistClient::class, fn () => new PackagistClient(
    new Client([
        'timeout' => 5,
        'connect_timeout' => 2,
        'headers' => ['User-Agent' => config('app.name').' ('.config('app.url').')'],
    ]),
    new PackagistUrlGenerator(),
));

The third-party Laravel wrapper (markwalet/laravel-packagist) exists but predates the 2.0 Packagist → PackagistClient rename and the API overhaul. A six-line binding is cheaper than an extra dependency you have to babysit.

Where to look first. Read packagist.org/apidoc next to the README — the client's methods map almost 1:1 to endpoints, and the API doc tells you what the returned arrays actually hold, which the README never does. Then skim PackagistClient (public methods are the product surface) and PackagistUrlGenerator (your extension seam, see below). Skim the changelog for 2.1.1 too, because it silently changed the metadata response shape.

First use case: a dependency inventory Artisan command. Parse composer.lock, loop your packages, and print licenses, latest version, download counts and abandoned flags. It's small, immediately useful, and exercises the four methods you'll actually live in.

$lock = json_decode(file_get_contents(base_path('composer.lock')), true);

foreach ($lock['packages'] as $package) {
    $meta  = $packagist->getPackageMetadata($package['name']);      // repo endpoint, fast
    $stats = $packagist->getPackageDownloadStats($package['name']); // total/monthly/daily
}

Implementation Patterns

Wrap the client in your own service. Never inject PackagistClient into ten controllers and commands. Create App\Support\Packagist\PackageInfo (or similar) that owns caching, exception handling, and maps the raw arrays into DTOs. That wrapper is what you mock in tests, and it's the single place you add throttling if you ever need it.

Cache every read, keyed by normalized name. Packagist caches API responses for 12 hours server-side, but that doesn't save you the HTTP round trip. Lowercase the package name for cache keys so Spatie/Packagist-Api and spatie/packagist-api don't create duplicate entries:

public function metadata(string $name): array
{
    return Cache::remember(
        'packagist:meta:'.strtolower($name),
        now()->addHours(6),
        fn () => $this->client->getPackageMetadata($name),
    );
}

Keep the two data sources distinct in your API. getPackageMetadata() hits the Composer repository endpoint (fast, all versions, ideal for version lists and license/require resolution). getPackage() hits the main API (slower, one "current" record, richer in some fields). Model them as two methods on your wrapper — versions($name) and details($name) — so callers can't accidentally treat one as the other.

Security-advisory sweep for CI or a scheduled command. This is the highest-value day-to-day use. Feed composer.lock in and fail the build on hits:

$locked = collect($lock['packages'])
    ->mapWithKeys(fn ($p) => [$p['name'] => ltrim($p['version'], 'v')]);

$advisories = $packagist->getAdvisoriesAffectingVersions($locked->all());

if ($advisories !== []) {
    $this->error('Vulnerable packages: '.implode(', ', array_keys($advisories)));
    return self::FAILURE;
}

Use the timestamp overload on getAdvisories($packages, $since) for incremental scans — re-scanning everything nightly is wasted traffic.

Package discovery UI. searchPackages($q, [...]), searchPackagesByName, searchPackagesByTags and searchPackagesByType all return the same paginated envelope. Wire them to a simple "browse packages" page with page/perPage forwarded straight through, and read total/next from the payload rather than guessing page counts.

Point the client somewhere else by decorating the URL generator. Because URL generation is its own class, you can subclass PackagistUrlGenerator to target a Private Packagist instance or a Satis mirror and still use the same client, caching and tests. This is the one extension point worth knowing about.

Testing. Don't hit the network. Use Guzzle's MockHandler + HandlerStack with canned JSON in unit tests, and mock your wrapper (not the Spatie client) in feature tests so fixtures live in one place.

Gotchas and Tips

  • getPackageMetadata() is not getPackage(). Since 2.1.1 the metadata call uses Composer v2 metadata, which returns a packages key holding an array of version objects (with version_normalized, dist, require, …). getPackage() returns a package key with a single "latest" record. Code written against 2.1.0 will break on 2.1.1+ — pin and read the changelog before bumping.
  • No HTTP error handling inside the client. Packagist 404s (package renamed, typo'd vendor) and 5xx come back as Guzzle exceptions. Catch GuzzleHttp\Exception\ClientException/RequestException in your wrapper, or configure 'http_errors' => false on the Guzzle client and check status codes yourself.
  • Set a timeout. The default Guzzle config has none, and a hung request will happily occupy a queue worker forever.
  • getPackagesNames() is enormous. It returns the entire package-name catalogue. Never call it in a web request — use getPackagesNamesByVendor() / ByType() or search instead.
  • Dev metadata is big. The true third argument to getPackageMetadata($vendor, $package, true) pulls ~dev metadata for every branch. Request it only when you specifically need branch versions, and cache it harder.
  • Both signatures exist, and one throws. Methods accept ('spatie/packagist-api') or ('spatie', 'packagist-api'). Passing a string with no / throws (behaviour added in 1.2.1); an empty vendor throws too. Validate/normalize at your boundary.
  • Arrays all the way down. Nothing is hydrated into objects. Type against your own DTOs or you'll be doing $data['package']['downloads']['total'] ?? null in Blade templates.
  • Watch the N+1. Metadata and download stats are separate requests per package. For a 60-package composer.lock that's 120 calls. Chunk the list, cache aggressively, and consider usleep() between batches to be a good API citizen — Packagist is a free, unauthenticated service.
  • Check the abandoned key before surfacing a dependency as "healthy". It's present in the metadata payload and is often the most actionable thing in your inventory report.
  • Debugging blind? Attach Guzzle's Middleware::history() (or a logging handler) behind a config flag; nothing in this package logs the outgoing URL for you, and the URL is usually the fastest way to see why a lookup failed.
  • Don't reach for the Laravel wrapper unless you're pinned to the pre-2.0 API — it doesn't track this package's current interface.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table