PHPackages                             humanmade/hm-query-loop - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. humanmade/hm-query-loop

ActiveWordpress-plugin[Utility &amp; Helpers](/categories/utility)

humanmade/hm-query-loop
=======================

WordPress Query Loop Block Tools

0.4.3(3mo ago)62.6k↓14.1%[3 PRs](https://github.com/humanmade/hm-query-loop/pulls)GPL-2.0-or-laterJavaScriptCI passing

Since Nov 6Pushed 2mo agoCompare

[ Source](https://github.com/humanmade/hm-query-loop)[ Packagist](https://packagist.org/packages/humanmade/hm-query-loop)[ RSS](/packages/humanmade-hm-query-loop/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (8)Dependencies (1)Versions (17)Used By (0)

HM Query Loop
=============

[](#hm-query-loop)

A WordPress plugin that extends the core Query Loop block with advanced controls for managing multiple query loops on a single page.

Features
--------

[](#features)

### 1. Posts Per Page Override for Inherited Queries

[](#1-posts-per-page-override-for-inherited-queries)

When a Query Loop block is set to "Inherit query from template", you can now override the number of posts to display. This is useful when you want to show a different number of posts than the main query. Leave the field empty to use the default number of posts.

**Editor Preview**: The posts per page override is now reflected in the editor preview, making it easier to see how your content will appear without needing to preview or publish the page.

### 2. Hide on Paginated Pages

[](#2-hide-on-paginated-pages)

Toggle whether the query loop should be hidden when viewing page 2 or higher (when the `paged` query var is greater than 1). This is useful for creating layouts where different query loops appear on the first page vs subsequent pages.

### 3. Exclude Already Displayed Posts

[](#3-exclude-already-displayed-posts)

Enable this option to automatically exclude posts that have been displayed by previous query loops on the same page. This ensures no duplicate posts appear when you have multiple query loops with different layouts.

**Important:** The exclusion applies to all query loops rendered before the current one, regardless of whether they were visible (e.g., hidden due to pagination settings).

Installation
------------

[](#installation)

1. Upload the plugin to your `/wp-content/plugins/` directory
2. Run `npm install` in the plugin directory
3. Run `npm run build` to compile the assets
4. Activate the plugin through the 'Plugins' menu in WordPress

Development
-----------

[](#development)

### Build Commands

[](#build-commands)

- `npm run start` - Start the development build with watch mode
- `npm run build` - Create a production build
- `npm run lint:js` - Lint JavaScript files
- `npm run lint:css` - Lint CSS/SCSS files
- `npm run format` - Format all files

### Testing

[](#testing)

The plugin includes end-to-end tests using Playwright and `@wordpress/scripts`.

#### Running Tests

[](#running-tests)

1. Start the WordPress test environment:

    ```
    npm run wp-env start
    ```
2. Run the tests:

    ```
    npm run test:e2e
    ```

#### Additional Test Commands

[](#additional-test-commands)

- `npm run test:e2e:debug` - Run tests in debug mode
- `npm run test:e2e:watch` - Run tests in watch mode (reruns on changes)
- `npm run wp-env stop` - Stop the WordPress environment
- `npm run wp-env` - Access wp-env commands directly

See [tests/e2e/README.md](tests/e2e/README.md) for more details on the test setup and writing tests.

Usage
-----

[](#usage)

1. Add a Query Loop block to your page or template
2. In the block settings sidebar, find the "HM Query Loop Settings" panel
3. Configure the options as needed:
    - **Posts per page (Override)**: Only visible when inheriting query - enter a number to override posts per page, or leave empty to use default
    - **Hide on paginated pages**: Toggle to hide this block on page 2+
    - **Exclude already displayed posts**: Toggle to avoid showing duplicate posts

Block Context
-------------

[](#block-context)

The plugin exposes a single `hm-query-loop/settings` context object that can be accessed by child blocks (like `core/post-template`):

```
{
	perPage: number | undefined,      // Custom posts per page value
	hideOnPaged: boolean,             // Whether to hide on paginated pages
	excludeDisplayed: boolean          // Whether to exclude displayed posts
}
```

This context is automatically registered for the `core/post-template` block both in JavaScript and PHP.

Technical Implementation
------------------------

[](#technical-implementation)

The plugin uses a dual-approach to handle both inherited and non-inherited Query Loop blocks:

### For Inherited Queries (uses main query):

[](#for-inherited-queries-uses-main-query)

- **`pre_render_block`**: Runs before a Query Loop block renders

    - Captures block attributes and stores them globally
    - Checks pagination visibility settings (returns empty string if hidden)
    - Hooks `modify_query_from_block_attrs` to `pre_get_posts`
- **`pre_get_posts` (via `modify_query_from_block_attrs`)**: Dynamically hooked during block rendering

    - Retrieves block attributes from global storage
    - Modifies the query (posts per page, exclusions)
    - Works with inherited queries and main query
- **`render_block`**: Runs after the Query Loop block renders

    - Unhooks `modify_query_from_block_attrs` from `pre_get_posts`
    - Clears global block attributes

### For Non-Inherited Queries (custom WP\_Query):

[](#for-non-inherited-queries-custom-wp_query)

- **`query_loop_block_query_vars`**: Passes custom block attributes into WP\_Query vars
    - Adds `hm_query_loop_id`, `hm_query_loop_per_page`, `hm_query_loop_exclude_displayed`
    - This filter runs for non-inherited queries only

Note: Since `query_loop_block_query_vars` doesn't fire for inherited queries, we use the `pre_render_block`/`render_block` approach to hook/unhook `pre_get_posts` dynamically around the block rendering.

### Post Tracking:

[](#post-tracking)

- **`the_posts`**: Runs after posts are fetched
    - Tracks post IDs from Query Loop blocks (both approaches) and main query
    - Builds a global list for subsequent query loops to exclude

Example Use Case
----------------

[](#example-use-case)

Create a custom archive layout with different query loops:

1. **First Query Loop**: Show 3 featured posts in a grid layout

    - Posts per page: 3
    - Hide on paginated pages: Yes
    - Exclude already displayed posts: No
2. **Second Query Loop**: Show remaining posts in a list layout

    - Posts per page: 10
    - Hide on paginated pages: No
    - Exclude already displayed posts: Yes (excludes the 3 featured posts)

On page 1, both query loops appear. On page 2+, only the second query loop appears, continuing to exclude the 3 featured posts from page 1.

Requirements
------------

[](#requirements)

- WordPress 6.0 or higher
- PHP 7.4 or higher
- Node.js 16 or higher (for development)

Release Process
---------------

[](#release-process)

This plugin uses GitHub Actions for automated versioning and release asset creation.

### Creating a Release

[](#creating-a-release)

1. Make sure the `release` branch is built and ready (assets should be in the `build/` directory)
2. Go to the GitHub repository and create a new release:
    - Click "Releases" → "Draft a new release"
    - Create a new tag (e.g., `v1.2.3`) from the `release` branch
    - Add release title and notes
    - Click "Publish release"
3. The GitHub Action will automatically:
    - Checkout the code at the tag you created
    - Replace `__VERSION__` placeholders with the actual version number
    - Commit the versioned file back to the tag
    - Create a production-ready ZIP file (excluding dev files)
    - Upload the ZIP as a release asset

The tag version (e.g., `v1.2.3`) will be used as the plugin version. The version number should follow semantic versioning.

License
-------

[](#license)

GPL-2.0+

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance84

Actively maintained with recent releases

Popularity28

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 71.1% 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 ~15 days

Total

7

Last Release

103d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/77dbeefb7745010589603f2ffc6ff310d8f700b58e08d52af190744c43342526?d=identicon)[roborourke](/maintainers/roborourke)

![](https://avatars.githubusercontent.com/u/494927?v=4)[Matthew Haines-Young](/maintainers/mattheu)[@mattheu](https://github.com/mattheu)

---

Top Contributors

[![roborourke](https://avatars.githubusercontent.com/u/23417?v=4)](https://github.com/roborourke "roborourke (32 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (12 commits)")[![tomjn](https://avatars.githubusercontent.com/u/58855?v=4)](https://github.com/tomjn "tomjn (1 commits)")

### Embed Badge

![Health badge](/badges/humanmade-hm-query-loop/health.svg)

```
[![Health](https://phpackages.com/badges/humanmade-hm-query-loop/health.svg)](https://phpackages.com/packages/humanmade-hm-query-loop)
```

###  Alternatives

[rainlab/blog-plugin

Blog plugin for October CMS

17257.7k](/packages/rainlab-blog-plugin)[rainlab/builder-plugin

Builder plugin for October CMS

17147.2k1](/packages/rainlab-builder-plugin)[pfefferle/wordpress-activitypub

The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.

5671.4k1](/packages/pfefferle-wordpress-activitypub)[civicrm/civicrm-drupal-8

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

18238.1k2](/packages/civicrm-civicrm-drupal-8)[mediawiki/semantic-glossary

A terminology markup extension with a Semantic MediaWiki back-end

1352.4k](/packages/mediawiki-semantic-glossary)[humanmade/lottie-lite

A lightweight Lottie Animations Extension for WordPress

374.3k](/packages/humanmade-lottie-lite)

PHPackages © 2026

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