composer/composer · error · InvalidArgumentException

Package 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.

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.

Appendix: 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)