The guide
Section 01
Composer reads your composer.json to install a package. Packagist reads it to decide what to show a stranger who's never heard of your package and is trying to work out, in about four seconds, whether it's worth a closer look. Most maintainers optimise heavily for the first audience and barely touch the second.
The result is a Packagist page with a name, a version number, and not much else — no tags to click through from a related package, no link to documentation, no way to tell if the maintainer is still around. None of that is a bug in Packagist. It's metadata that was never filled in.
This guide covers the composer.json fields that exist specifically for that second audience, what each one actually renders as once it's published, and a complete example you can adapt.
What Packagist actually does with your metadata
A package page on Packagist is assembled almost entirely from fields you control:
- Title and description — your package name and the
descriptionstring, shown at the top of the page and in search results. - Keywords — rendered as clickable tags. Clicking one runs a Packagist search for that term, which is also how people land on your package while looking for something else entirely.
- Homepage — a direct link, separate from the repository link.
- Support links —
issues,docs,source,chat,email, and the others, each shown as its own labelled link rather than buried in a README. - Funding — sponsorship links, shown as a distinct "fund this package" element when present.
Packagist's search index refreshes every five minutes after a package is crawled, so there's no lag-based excuse for shipping a tag or a doc link and not seeing it appear. If a field is empty, it's because composer.json doesn't have it — not because Packagist is slow to notice.
keywords: the field everyone forgets
keywords takes an array of strings and turns each one into a clickable tag on your package page. It's also one of the few fields that feeds Packagist's search and category filtering directly, which means a missing keywords array isn't a cosmetic gap — it's a way for someone searching for exactly what your package does to never see it.
{
"keywords": [
"laravel",
"php",
"rate-limiting",
"middleware",
"throttle"
]
}
A useful keyword list mixes the broad terms people start a search with (laravel, php) with the specific terms that describe the actual problem you solve (rate-limiting, feature-flags, audit-log). The broad terms get you into a category; the specific ones get you found by someone who already knows what they're looking for. Composer restricts keyword characters to letters, numbers, spaces, dots, underscores, and dashes, so keep each entry to a single short term rather than a comma-separated phrase.
homepage and support: telling people where to actually go
homepage is a single URL — your project's front door if you have one separate from the repository: a documentation site, a landing page, anything more deliberately built than a GitHub README.
support is where most of the useful links live, and it's a full object rather than a single string:
{
"support": {
"docs": "https://example.com/docs",
"issues": "https://github.com/vendor/package/issues",
"source": "https://github.com/vendor/package",
"chat": "https://discord.gg/example",
"security": "https://github.com/vendor/package/security/policy"
}
}
Each key Packagist understands — email, issues, forum, wiki, irc, source, docs, rss, chat, security — shows up as its own labelled link on the package page. If your repository host already provides a "source" link by virtue of being a GitHub repo, the field worth adding deliberately is docs: a lot of packages have real documentation living on a separate site that never gets connected back to the Packagist listing at all.
support.security: give vulnerability reports a destination
support.security deserves calling out on its own, because it answers a question that otherwise has no good default: where does someone send a vulnerability report instead of opening a public GitHub issue?
{
"support": {
"security": "https://github.com/vendor/package/security/policy"
}
}
This pairs directly with filing advisories through FriendsOfPHP or the GitHub Advisory Database, covered in Using Composer 2.10 to Help Your Users Stay Secure — having a support.security link doesn't replace filing the advisory once a fix ships, but it gives a researcher somewhere responsible to go before that point exists. The same instinct applies to the account behind the package; see Securing Your Packagist Account for the other half of that picture.
funding: letting adoption turn into support
funding is an array of { "type": ..., "url": ... } pairs. type is just a label — Packagist recognises common platforms like github, patreon, tidelift, and open_collective, and falls back to a generic link for other or anything it doesn't specifically recognise.
{
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/vendor"
},
{
"type": "open_collective",
"url": "https://opencollective.com/vendor"
}
]
}
It's a small field, but it's the one most directly tied to whether downloads ever turn into anything sustaining the maintainer. If you're already tracking adoption numbers — see Measuring the Success of Your Laravel Package — funding is the field that gives a fraction of those users a way to act on it.
abandoned: discoverability includes knowing when to stop looking
It feels backwards to include a field for "this package shouldn't be found," but it belongs here precisely because it's a discovery problem in reverse. A package with no commits in three years and no abandoned flag still shows up in search results looking exactly as viable as one that's actively maintained — someone has to install it and dig through issues before they find out otherwise.
{
"abandoned": "vendor/the-actively-maintained-fork"
}
Setting abandoned to true flags the package without a recommendation; pointing it at a string names the replacement directly, and Packagist surfaces that suggestion right on the package page. It costs nothing and saves the next person the hour you already know they'll lose.
A complete example
Pulling the discovery-facing fields into one block:
{
"name": "vendor/package",
"description": "A short, specific sentence describing what this package does.",
"keywords": ["laravel", "php", "rate-limiting", "middleware"],
"homepage": "https://example.com",
"support": {
"docs": "https://example.com/docs",
"issues": "https://github.com/vendor/package/issues",
"source": "https://github.com/vendor/package",
"security": "https://github.com/vendor/package/security/policy"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/vendor"
}
]
}
None of this is required for composer install to work. All of it is required for a stranger on Packagist to understand, in the few seconds they'll actually spend, what your package does, where to read more, and whether anyone is still home.
The checklist
- Write a
descriptionthat's one specific sentence, not a restatement of the package name. - Add 4–8
keywords— a couple of broad terms, the rest specific to the problem you solve. - Set
homepageif you have anything more deliberate than the bare repository. - Fill in
support.docsandsupport.securityat minimum — the two links a Packagist page is least likely to have by default. - Add
fundingif you accept sponsorship anywhere, even informally. - Mark
abandonedthe day you know you've stopped maintaining a package, ideally pointing at whatever you'd recommend instead.
For more on what happens once people find the package — measuring whether it's actually being adopted, and keeping the account behind it secure — see Measuring the Success of Your Laravel Package and Securing Your Packagist Account.
End of guide · 2026
← Back to the archive