PHPackages                             humanmade/wp-pattern-library - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. humanmade/wp-pattern-library

ActiveWordpress-plugin

humanmade/wp-pattern-library
============================

Serve a manifest and isolated previews of a WordPress site's registered block patterns, so a pattern library can be generated from them.

v0.1.1(today)00GPL-2.0-or-laterJavaScriptPHP &gt;=8.1

Since Jul 28Pushed todayCompare

[ Source](https://github.com/humanmade/wp-pattern-library)[ Packagist](https://packagist.org/packages/humanmade/wp-pattern-library)[ RSS](/packages/humanmade-wp-pattern-library/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (4)Versions (4)Used By (0)

WP Pattern Library
==================

[](#wp-pattern-library)

Generate a browsable Markdown pattern library — with screenshots — from a WordPress site's registered block patterns.

The site serves a manifest of its registered patterns and renders each one in isolation; a Node CLI captures them with Playwright and writes an index plus one page per pattern category. A GitHub Action runs the whole thing and opens a pull request with the refreshed docs.

Two packages ship from this repository:

PackageInstall`humanmade/wp-pattern-library``composer require humanmade/wp-pattern-library``@humanmade/wp-pattern-library``npm install -D @humanmade/wp-pattern-library`Install
-------

[](#install)

### 1. The plugin

[](#1-the-plugin)

```
composer require humanmade/wp-pattern-library
```

The Composer package declares `"type": "wordpress-plugin"`, so `composer/installers`routes it to the project's plugin directory — `content/plugins/` on Altis, `plugins/` on WordPress VIP — without any `installer-paths` change.

Activate it as you would any other plugin: through wp-admin, `wp plugin activate wp-pattern-library`, or your platform's code-activation helper.

```
// VIP: in client-mu-plugins/plugin-loader.php
wpcom_vip_load_plugin( 'wp-pattern-library' );
```

It belongs in the deployed environment, not in `require-dev`: the library is captured from the running production site, so the route has to be live there. What it adds to that site is one front-end route behind a dedicated capability, plus a WP-CLI command — no admin UI, no front-end assets, nothing on the request path for ordinary visitors.

Limit it to your own patterns, so core and plugin patterns stay out of the library:

```
add_filter( 'pattern_library_namespaces', fn () => [ 'my-theme/' ] );
```

### 2. A service account

[](#2-a-service-account)

The routes require a dedicated capability, `view_pattern_library`. The bundled WP-CLI command creates a role holding that capability and `read` — and nothing else, so the account cannot create or edit content:

```
wp pattern-library setup --login=pattern-library-bot
```

It prints an application password. Store it, along with the login, as CI secrets; it is not recoverable.

> On multisite, roles are stored per site. Run the command with `--url=` for each site whose patterns you want to capture.

To use an existing user instead, run `wp pattern-library setup` and then `wp pattern-library grant `.

### 3. Configuration

[](#3-configuration)

Create `pattern-library.config.js` in your project root:

```
export default {
	title: 'My Pattern Library',
	namespaces: [ 'my-theme/' ],
	outputDir: 'docs/pattern-library',
};
```

Credentials come from the environment, never the config file:

```
export PATTERN_LIBRARY_SITE="https://example.com"
export PATTERN_LIBRARY_WP_USER="pattern-library-bot"
export PATTERN_LIBRARY_WP_APP_PASSWORD="xxxx xxxx xxxx xxxx"
```

Usage
-----

[](#usage)

```
npx pattern-library build            # capture screenshots, then write Markdown
npx pattern-library build --dry-run  # report what would be included, write nothing
npx pattern-library capture hero     # screenshots only, for patterns matching "hero"
npx pattern-library generate         # Markdown only, from existing screenshots
npx pattern-library manifest         # print the filtered manifest as JSON
```

Screenshots are written only when their bytes change, so re-running against a live site does not churn images whose content merely shifted underneath them.

The run summary flags patterns that rendered empty, failed outright, or referenced resources — images, fonts — that no longer exist, so broken previews are caught at generation time rather than in review.

Configuration reference
-----------------------

[](#configuration-reference)

KeyDefaultNotes`title``Pattern Library`Index heading.`namespaces``[]` (all)Pattern-name prefixes to include.`outputDir``docs/pattern-library`Where pages and screenshots are written.`indexFile``/README.md`Index path.`screenshotsDir``/screenshots`Screenshot path.`imageFormat``webp``webp`, `avif`, `jpeg` or `png`.`imageQuality``80`Ignored for `png`.`defaultViewport``1440`Used when a pattern declares no `Viewport Width`.`exclude`see belowWhat to leave out of the library.`postTypeContext``{}`Basename to post type, for `core/post-template` patterns.`classify`flatCategory placement. See below.`animations``[ 'aos' ]`Animation libraries to settle before capture. See below.`extraFields``[]`Extra metadata lines per pattern. See below.`includeSkipped``true`List excluded patterns, with reasons, on the index page.`placeholderImages``true`Placeholder featured image for posts that have none.`exclude` defaults to skipping patterns hidden from the inserter and those scoped to `wp_template` / `wp_template_part`, since template parts render meaninglessly in isolation:

```
exclude: {
	inserterHidden: true,
	postTypes: [ 'wp_template', 'wp_template_part' ],
	patterns: [], // Exact pattern names.
}
```

### Patterns that render empty

[](#patterns-that-render-empty)

A pattern built as a query-loop *item template* has nothing to bind to when rendered alone. Give it a post type, keyed by basename:

```
postTypeContext: { 'person-card': 'person' }
```

The CLI reports any pattern that rendered empty, so these are easy to find.

### Animation libraries

[](#animation-libraries)

Scroll-triggered animation libraries hide content until it scrolls into view, so a capture must force everything to its finished state first. `animations` lists what to settle: built-in library names (currently `aos`), or custom `{ css, settle }` objects for project-specific conventions:

```
animations: [
	'aos',
	{
		// Optional: CSS injected before capture, overriding the hidden state.
		css: '.js-reveal { opacity: 1 !important; transform: none !important; }',
		// Optional: runs in the browser to flip elements to "done". Serialized
		// into the page, so it must not close over config-file variables.
		settle: () => {
			document
				.querySelectorAll( '.js-reveal' )
				.forEach( ( el ) => el.classList.add( 'is-revealed' ) );
		},
	},
]
```

A handler that proves generally useful belongs in `src/animations.mjs` as a new built-in, so other projects can list it by name.

### Extra metadata fields

[](#extra-metadata-fields)

Each pattern's section shows its description, categories, keywords, block types and post types by default. `extraFields` appends further lines: `value` is a manifest property name, or a function receiving the pattern:

```
extraFields: [
	{ label: 'Source', value: 'source' },
	{ label: 'Namespace', value: ( pattern ) => pattern.name.split( '/' )[ 0 ] },
]
```

### Grouping categories

[](#grouping-categories)

By default every registered category becomes one page in the output root. A project with a richer taxonomy can supply a `classify()` function, called once per category, returning where it belongs — or nothing, to drop it from the library:

```
classify: ( { slug, label } ) => {
	// A cross-cutting category of whole-page references, which should also lead
	// each section page rather than sitting in the list.
	if ( slug === 'full-page' ) {
		return { kind: 'reference', dir: 'references', label: 'Full page', leadsIn: 'section' };
	}
	if ( label.startsWith( 'Component - ' ) ) {
		return { kind: 'component', dir: 'components', label: label.slice( 12 ) };
	}
	return { kind: 'section', dir: 'sections', label };
}
```

`kind` groups pages under headings on the index, `dir` places the page, `label`sets its title, and `leadsIn` promotes its patterns to the top of every page of the named kind.

GitHub Action
-------------

[](#github-action)

Copy [`examples/refresh-pattern-library.yml`](examples/refresh-pattern-library.yml)into `.github/workflows/`. It is `workflow_dispatch`-triggered, takes a base branch and output path, and opens a pull request with the refreshed docs.

Capture from **production**. Screenshots reflect deployed code, so a pattern that exists only on the branch being documented renders as "preview pending" until it ships. Staging environments sitting behind their own HTTP Basic gate cannot be used: two `Authorization: Basic` headers cannot coexist on one request.

How authentication works
------------------------

[](#how-authentication-works)

Requests authenticate with a standard WordPress application password over HTTP Basic. Two details are worth knowing, because both fail *silently* otherwise — WordPress serves the logged-out page with a `200`, which would yield a run's worth of plausible but wrong screenshots:

- `wp_authenticate_application_password()` ignores any request that is not REST or XML-RPC. The preview route opts itself in through the `application_password_is_api_request` filter, scoped to its own query var.
- Browsers only attach Basic credentials after a `401` carrying `WWW-Authenticate`, and never send them preemptively. Playwright's `httpCredentials.send: 'always'` does not change this — it applies only to its API request context, not to page navigation. The route therefore sends a proper challenge with its `401`.

As a backstop, before capturing anything the CLI navigates the browser to the manifest URL and requires a `200` — a probe that authenticates through the same browser path the captures use, yet cannot be brought down by a single pattern that fails to render.

Filters
-------

[](#filters)

FilterPurpose`pattern_library_enabled`Disable the routes entirely.`pattern_library_namespaces`Pattern-name prefixes to expose.`pattern_library_user_can`Override the capability check.`pattern_library_placeholder_image`Markup of the placeholder featured image.Requirements
------------

[](#requirements)

PHP 8.1+, WordPress 6.0+, Node 24+.

The PHP side runs on the WordPress site; the Node side runs wherever the library is generated, which in practice is CI or a developer machine. They do not need to be the same host, and the site does not need Node.

Contributing and releases
-------------------------

[](#contributing-and-releases)

Both published packages — the Composer package and the npm package — are built from this one repository, from the same tree. There is no separate source for the Node CLI: `bin/` and `src/` are what npm publishes, `inc/` and `plugin.php` are what Packagist serves, and `action.yml` wraps the npm package for GitHub Actions.

One tag drives all three, so a given version means the same code everywhere:

1. Bump `version` in `package.json`, and the `Version:` header in `plugin.php`. Keep them equal — the plugin header is what shows in wp-admin, and a manifest served by one version being read by another is the failure mode this convention exists to prevent.
2. Tag the release `vX.Y.Z` and push the tag.
3. `npm publish --access public` publishes `@humanmade/wp-pattern-library`. Packagist picks up `humanmade/wp-pattern-library` from the same tag via its GitHub hook.

Consuming workflows pin the action to a released tag (`humanmade/wp-pattern-library@vX.Y.Z`). There is deliberately no moving `@v1`tag while the package is pre-1.0.

The two halves are coupled by the manifest: `inc/manifest.php` produces it and `src/manifest.mjs` consumes it. A change to either shape is a change to both, in one commit, released under one tag.

License
-------

[](#license)

GPL-2.0-or-later

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity34

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

2

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c42f12d9d5d3ae4995b27cca8e85fc8942a88fb5ed5b436a460a0ec289468d86?d=identicon)[goldenapples](/maintainers/goldenapples)

---

Top Contributors

[![goldenapples](https://avatars.githubusercontent.com/u/665992?v=4)](https://github.com/goldenapples "goldenapples (12 commits)")

---

Tags

wordpressdocumentationblock-patternspattern library

###  Code Quality

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/humanmade-wp-pattern-library/health.svg)

```
[![Health](https://phpackages.com/badges/humanmade-wp-pattern-library/health.svg)](https://phpackages.com/packages/humanmade-wp-pattern-library)
```

###  Alternatives

[swagger-api/swagger-ui

 Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.

28.9k51.0M115](/packages/swagger-api-swagger-ui)[behat/behat

Scenario-oriented BDD framework for PHP

4.0k103.4M2.3k](/packages/behat-behat)[wp-cli/wp-cli

WP-CLI framework

5.1k19.2M423](/packages/wp-cli-wp-cli)[wp-coding-standards/wpcs

PHP\_CodeSniffer rules (sniffs) to enforce WordPress coding conventions

2.8k49.8M2.4k](/packages/wp-coding-standards-wpcs)[nelmio/api-doc-bundle

Generates documentation for your REST API from attributes

2.4k68.6M271](/packages/nelmio-api-doc-bundle)[darkaonline/l5-swagger

OpenApi or Swagger integration to Laravel

2.9k38.9M143](/packages/darkaonline-l5-swagger)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
