Skip to content

Method

How the record is built, and what is wrong with it

Three passes over the public plugin directory, one language-model call per plugin, and a short list of things that turned out to be harder than they looked. If you are going to quote a figure from this site, read this page first.

Pipeline

Three passes

Each one is independent and re-runnable. Nothing downstream has to be rebuilt when an earlier pass runs again, which is the only reason keeping a hundred thousand records current is practical at all.

Pass one Read the index

wordpress.org publishes a plain index of every plugin slug it hosts or has hosted. That index is read in full and each slug is stored once. A slug is never deleted from this table, which is how the record keeps plugins the directory has stopped showing.

Pass two Fetch each record

Each slug is put to the plugin information API and the whole response is stored - the metrics in their own columns for querying, and the raw JSON beside them so a field nobody thought to extract is still recoverable later without re-fetching a hundred thousand plugins.

Pass three Classify

A language model is given the plugin's description and the entire taxonomy built so far, and answers with one placement: an existing category and sub-category, or a proposal for a new one. One call, one plugin, one answer.

Fields

What is stored per plugin

Everything the public API returns, whether or not a page on this site currently uses it. Storage is cheap; a second pass over the whole directory is not.

  • Name, slug, author and author profile
  • Current version, and the full version history count
  • Active installs, as the directory rounds them
  • Rating out of 100, and the number of ratings behind it
  • Support threads, and how many were resolved
  • Date added, and date last updated
  • Required WordPress and PHP versions, and tested-up-to
  • Contributors, tags, screenshots and banners
  • Description, installation notes, changelog and FAQ text
  • The status the API answered with, and its reason for a closure
One field is missing on purpose

The all-time download count is stored but is empty for every plugin - the API stopped populating it. Rather than show a column of zeros, no page on this site ranks or reports downloads. If you see a WordPress plugin "total downloads" figure anywhere, ask where it came from.

Consistency

The classifier had to be rebuilt

The first design asked the model to look up candidate categories with a tool call and then place the plugin in a second call. It worked, in the sense that it always produced an answer. It was not consistent, which is worse than not working.

2 in 3 Before: disagreed with itself Re-running one plugin through the same model changed its top-level category two times in three
40 / 40 After: agreed with itself Forty plugins re-run against their stored placement; forty top-level matches, one sub-category flip
40% → 15% New-category rate How often a plugin caused a brand-new category to be invented. A taxonomy that keeps growing is not a taxonomy.

What changed: the prompt now carries the whole taxonomy, both levels, in the single call that does the placing. The model is no longer choosing from a shortlist it fetched a moment earlier - it sees everything that exists before it decides whether anything fits.

That halved the cost per plugin as a side effect, from two calls to one. The reason for the change was the consistency, not the cost, but it is a rare case where the cheaper design is also the better one.

Deduplication

A gate in front of every new category

Even with the full taxonomy in the prompt, a model asked to name a category will occasionally produce a name that already exists in a slightly different shape - "E-Commerce" against "Ecommerce", "Backups" against "Backup & Restore".

So a proposed category is compared against every existing one before it is created, and a close enough match is folded onto the existing row instead. The comparison deliberately crosses category boundaries: a sub-category proposed under the wrong parent gets matched to the right one, which corrects the placement rather than duplicating it.

The names are also normalised before they are keyed, by removing every non-alphanumeric character rather than by replacing it with a space. That distinction is not academic - collapsing separators to a space gives "e commerce" for "E-Commerce" but "ecommerce" for "eCommerce", which would have forked the taxonomy on a capital letter.

What the gate checks

  • Exact match on the normalised name key
  • Similarity against every existing category name
  • Similarity against sub-categories under other parents
  • A threshold below which the proposal is accepted as genuinely new
  • A health screen that lists the near-duplicate pairs it let through

Integrity

Three things that quietly lost data

None of these threw an error. That is what makes them worth writing down - each one would have left a record that looked complete and was not.

The database merged distinct slugs

MySQL's default collation ignores accents and zero-width characters, so contenido-del-dia and contenido-del-día collided on a unique key - and so did a slug with an invisible character pasted into it. Five real plugins vanished with no error at all. Slug columns now use a binary collation.

A shared temporary file

The index download wrote to one fixed filename. Whichever process got there first owned it, and every later run - including every run from the browser after one from the command line - failed on permissions. Each run now writes to its own file.

A name key that forked on case

The rule that folds duplicate category names together was nearly written to collapse punctuation into spaces, which would have made "E-Commerce" and "eCommerce" two different categories forever. A test caught it before it touched real data.

Limits

What these figures cannot tell you

Every limit below is a property of the source data, not of this site. None of them are fixable from here, and all of them change how a number should be read.

Read every figure with these in mind

  • Install counts are rounded, hard. The directory publishes bands, not numbers - never below ten, and a single value shared by everything above ten million. Ordering inside a band is not meaningful.
  • An unrated plugin stores a rating of zero. Average anything across the whole directory without excluding those and you will publish a statistic about missing data. Every average on this site excludes them, and says so.
  • Support thread counts are a window, not a total. The directory reports recent activity, so a quiet plugin and an abandoned one look identical.
  • Arrival years only count survivors. A plugin added in 2009 and closed in 2012 is in the closed set, not in the 2009 column, so early years read thinner than they were.
  • Classification covers a sample. The taxonomy is built from the plugins that have been through the classifier, and the categories page states how many that is. Category sizes are sample sizes.
  • A model can be confidently wrong. Consistency was measured; correctness was not, and cannot be without a ground truth that does not exist. The placements are published so they can be judged.
  • Nothing here is a recommendation. A plugin at the top of a board is popular, which is a measurement, not an endorsement - and some of the most installed plugins in the directory have open security advisories in their history.

Freshness

How current any of this is

Two different clocks, and it is worth knowing which one you are reading.

The aggregates

Every total, distribution and leaderboard on this site is computed from the stored records and rebuilt once a day. The stamp under each section says when the copy you are looking at was read.

The records themselves

A plugin's row is as current as the last time its slug came up in a fetch pass. Passes run over the directory in batches, so the record is continuously refreshed rather than snapshotted on a date.

Read from 131,237 plugin records - figures refreshed 5 hours ago

Now go and read the numbers

You know what they are made of and where they bend.