composer/composer · error · InvalidArgumentException

Package %s must have a source or dist specified

Error message

Package %s must have a source or dist specified

What it means

Thrown by DownloadManager::getAvailableSources (a private method feeding install/update source selection). It builds a $sources array from sourceType and distType; if both are empty, the package has nothing to download and the InvalidArgumentException fires. Distinguishes from error 232: that is about installationSource being unset, this is about the package lacking source AND dist entirely.

Source

Thrown at src/Composer/Downloader/DownloadManager.php:452

     * @return string[]
     * @phpstan-return array<'dist'|'source'>&non-empty-array
     */
    private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array
    {
        $sourceType = $package->getSourceType();
        $distType = $package->getDistType();

        // add source before dist by default
        $sources = [];
        if ($sourceType) {
            $sources[] = 'source';
        }
        if ($distType) {
            $sources[] = 'dist';
        }

        if (empty($sources)) {
            throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
        }

        if (
            $prevPackage
            // if we are updating, we want to keep the same source as the previously installed package (if available in the new one)
            && in_array($prevPackage->getInstallationSource(), $sources, true)
            // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over
            && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())
        ) {
            $prevSource = $prevPackage->getInstallationSource();
            usort($sources, static function ($a, $b) use ($prevSource): int {
                return $a === $prevSource ? -1 : 1;
            });

            return $sources;
        }

        // reverse sources in case dist is the preferred source for this package

View on GitHub (pinned to c435d285c9)

Solutions

  1. Confirm the package is meant to be installed (not just provided/replaced); if it's virtual, don't install it.
  2. Ensure the repository serving the package includes a dist or source block in its metadata.
  3. If the package is yours, publish a tag/release so a dist is generated, or add source info.
  4. Use 'composer why <pkg>' to verify the dependency isn't pulling a stub package.

Example fix

// before - package.json-style entry with no source/dist
{"name": "vendor/pkg", "version": "1.0.0"}
// after - add a dist
{"name": "vendor/pkg", "version": "1.0.0", "dist": {"url": "https://.../pkg.zip", "type": "zip"}}
Defensive patterns

Strategy: validation

Validate before calling

if ($package->getType() !== 'metapackage'
    && empty($package->getSourceType())
    && empty($package->getDistType())) {
    throw new InvalidArgumentException('Package ' . $package->getName() . ' has neither source nor dist');
}

Type guard

function hasSourceOrDist(\Composer\Package\PackageInterface $p): bool {
    return !empty($p->getSourceType()) || !empty($p->getDistType());
}

Prevention

When it happens

Trigger: A package declares neither a source (git/svn/hg) nor a dist (zip/tar) — i.e. getSourceType() and getDistType() are both empty/null. Reached during install/update when Composer tries to determine which source(s) to offer.

Common situations: A provider/replace-only or virtual package that was mistakenly treated as installable. A custom repository serving packages without dist/source metadata. A metapackage mishandled upstream of the call.

Related errors


AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07). Data as JSON: /api/errors/2d5aab1f44e1f4c1. Report an issue: GitHub.