Why this matters
When someone runs composer require your/package, Composer downloads an archive of your repository at the tagged commit. Unless you say otherwise, that archive contains everything in the repository: your test suite, your CI configuration, your editor settings, your AI assistant instructions, your changelog, your Dockerfile.
None of that runs in the user's application. It does make every install slower and every vendor/ directory larger. It gives security scanners more files to raise questions about. And it exposes files, such as internal tooling configuration, that were never meant to be part of the product.
Trimming the archive is a one-time change to one file.
What good looks like
The released archive contains the source code, composer.json, the license, and the README. Tests, CI configuration, static analysis and code style configuration, editor and AI assistant settings, and development tooling files are all left out.
How to do it
Add export-ignore rules
Create or edit .gitattributes in the repository root. Every path marked export-ignore is left out of archives that Git builds, which is how Packagist and GitHub produce release downloads.
# Tests
/tests export-ignore
/phpunit.xml.dist export-ignore
/phpstan.neon export-ignore
/psalm.xml export-ignore
/infection.json5 export-ignore
# CI and repository configuration
/.github export-ignore
/.gitlab-ci.yml export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.editorconfig export-ignore
# Editor and AI assistant settings
/.idea export-ignore
/.vscode export-ignore
/.claude export-ignore
/.cursor export-ignore
/CLAUDE.md export-ignore
/AGENTS.md export-ignore
# Development tooling
/.php-cs-fixer.dist.php export-ignore
/rector.php export-ignore
/pint.json export-ignore
/Makefile export-ignore
/docker-compose.yml export-ignore
/Dockerfile export-ignore
/CHANGELOG.md export-ignore
/CONTRIBUTING.md export-ignore
Adjust the list to the files you actually have. A leading slash anchors the rule to the repository root.
Or use composer.json
Composer's own archive command reads archive.exclude in composer.json. Packagist serves Git archives for most packages, so .gitattributes is the more reliable choice, but the two can coexist:
{
"archive": {
"exclude": ["/tests", "/.github", "/phpunit.xml.dist"]
}
}
Check the result
Before tagging, build the archive locally and list what it contains:
git archive --format=tar HEAD | tar -t
Anything you see there is what users will download. Then tag a release. Archives for existing tags are already built and will not change.
Things to watch for
- Forgetting to tag. The change only affects archives built from commits that include the new
.gitattributes. Existing releases keep their contents. - Excluding files the package needs at runtime. Configuration stubs, published assets, translation files, and migrations often live in top-level folders. Only exclude what the application never loads.
- Case-sensitive paths.
tests/andTests/are different directories. Match what is in your repository. - Whether to keep the changelog. Some maintainers ship
CHANGELOG.mddeliberately so it is readable insidevendor/. That is a fair choice; the rest of the list still applies.