Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions scripts/sync-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ rewrite_links() {
-e 's|(meta\.md\(#[^)]*\)\?)|(/content/typed-meta/\1)|g' \
-e 's|(options\.md\(#[^)]*\)\?)|(/content/options/\1)|g' \
-e 's|(hooks\.md\(#[^)]*\)\?)|(/hooks/actions-filters/\1)|g' \
-e 's|(async-actions\.md\(#[^)]*\)\?)|(/hooks/async-actions/\1)|g' \
-e 's|(events-listeners\.md\(#[^)]*\)\?)|(/hooks/events-listeners/\1)|g' \
-e 's|(wordpress-events-reference\.md\(#[^)]*\)\?)|(/hooks/wordpress-events-reference/\1)|g' \
-e 's|(theming\.md\(#[^)]*\)\?)|(/theming/theme-structure/\1)|g' \
Expand Down Expand Up @@ -119,8 +120,9 @@ sync_file "options.md" "content/options.md" "Options" "Read and write WordPress

# Hooks & Events
sync_file "hooks.md" "hooks/actions-filters.md" "Actions & Filters" "Register WordPress actions and filters in Pollora with PHP 8 attributes or the Action and Filter facades, including deferred callbacks and service access." 1
sync_file "events-listeners.md" "hooks/events-listeners.md" "Events & Listeners" "Listen to WordPress actions and filters as typed Laravel events in Pollora, and use standard Laravel listeners and subscribers inside a WordPress site." 2
sync_file "wordpress-events-reference.md" "hooks/wordpress-events-reference.md" "WordPress Events Reference" "Complete list of the Laravel events Pollora dispatches for WordPress core and supported plugins, with the class to listen to for each of them." 3
sync_file "async-actions.md" "hooks/async-actions.md" "Asynchronous Actions" "Run a WordPress action after the request in Pollora with ->async() or #[Async]: queued to a Laravel worker, Action Scheduler or WP-Cron, with retries and tests." 2
sync_file "events-listeners.md" "hooks/events-listeners.md" "Events & Listeners" "Listen to WordPress actions and filters as typed Laravel events in Pollora, and use standard Laravel listeners and subscribers inside a WordPress site." 3
sync_file "wordpress-events-reference.md" "hooks/wordpress-events-reference.md" "WordPress Events Reference" "Complete list of the Laravel events Pollora dispatches for WordPress core and supported plugins, with the class to listen to for each of them." 4

# Theming
sync_file "theming.md" "theming/theme-structure.md" "Theme Structure" "Create a Pollora theme: folder structure, theme.json, Blade templates and the template hierarchy, Vite and Tailwind CSS, localization and the login screen." 1
Expand Down
55 changes: 55 additions & 0 deletions src/content/blog/pollora-13-35-2-async-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: "Pollora 13.35.2: asynchronous actions"
description: Add ->async() to an action, or #[Async] next to #[Action], and its handler runs after the request, through a Laravel queue worker, Action Scheduler or WP-Cron.
date: 2026-10-07
category: release
version: 13.35.2
---

A handler attached to `save_post` or to a WooCommerce order runs inside the request that fires it. When it calls a CRM, an ERP or an email API, the editor waits for it, and when that service fails, the save fails with it. Pollora 13.35.2 lets that handler run a moment later, without changing how it is written.

## One line

```php title="app/Cms/Hooks/SyncEventToCrm.php"
use Pollora\Attributes\Action;
use Pollora\Attributes\Async;

class SyncEventToCrm
{
#[Action('save_post_event', priority: 20)]
#[Async(tries: 3, unique: true)]
public function handle(int $postId, CrmClient $crm): void
{
// Runs after the request; $crm comes from the container
}
}
```

Remove `#[Async]` and the action is synchronous again. The facade has the same option: `Action::add(...)->async()->delay(60)->tries(3)`. `php artisan pollora:make:action SyncEventToCrm --hook=save_post_event --async` generates the class.

The hook arguments travel with the job. A `WP_Post`, a `WP_User` or an Eloquent model travels by ID and is reloaded when the handler runs. `capture()` records what would otherwise be gone by then, such as the post status at save time, and `when()` leaves out revisions and autosaves before anything is queued.

## Three drivers, one setting

The default driver, `auto`, uses the Laravel queue once `HOOKS_ASYNC_CONNECTION` names a connection a worker runs, then Action Scheduler when WooCommerce or another plugin bundles it, then WP-Cron. The queue is opt-in because a fresh project ships with a `database` queue and no worker: the default would otherwise queue jobs that never run. `HOOKS_ASYNC_DRIVER=sync` in a developer's `.env` runs everything at once.

One thing to set up: Pollora disables WP-Cron on page loads (`DISABLE_WP_CRON`). With WP-Cron or Action Scheduler, a system cron must request `wp-cron.php` every minute; with the queue, a worker must run.

## Knowing what runs

Asynchronous work fails quietly, so the tooling looks for it:

- `pollora:doctor` and Site Health flag an `#[Async]` that was ignored (and so runs synchronously), an unavailable driver, WP-Cron events an hour late, and jobs waiting fifteen minutes for a worker.
- `pollora:async:list` shows every asynchronous action, its driver and options, and where the default driver comes from.
- `Async::fake()` records what would be queued in a test, with `assertDispatched()` and `runDispatched()`.

The mechanism itself lives in [`pollora/hook`](https://github.com/Pollora/hook) 1.4, which a plain WordPress plugin can use on its own.

## Upgrade

```bash
composer update pollora/framework --with-dependencies
php artisan pollora:doctor
```

An asynchronous handler can run twice and in any order: the [Asynchronous Actions](/hooks/async-actions/) page starts with the four rules to follow.
2 changes: 2 additions & 0 deletions src/content/blog/roadmap-async-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ date: 2026-10-07
category: roadmap
---

> **Update, 7 October 2026: shipped in [Pollora 13.35.2](/blog/pollora-13-35-2-async-actions/).** One change from the design below: `auto` takes the Laravel queue only once `HOOKS_ASYNC_CONNECTION` names the connection a worker runs, since a fresh project ships a `database` queue and no worker. See [Asynchronous Actions](/hooks/async-actions/).

A WordPress hook runs inside the request that fires it. Attach a call to a CRM, an ERP or an email API to `save_post`, and every editor waits for that API each time they press *Save*. Attach it to a WooCommerce order, and the customer waits at checkout. If the third-party service fails, the save or the payment can fail with it.

This post describes the next feature we are building. It is a design, not a release: names and options may still move, and this is the right moment to tell us what you would change.
Expand Down
9 changes: 5 additions & 4 deletions src/content/docs/advanced/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ php artisan pollora:status
Example output:

```
Pollora v13.35.1 (latest: v13.35.1) ✓
Pollora v13.35.2 (latest: v13.35.2) ✓

PHP 8.4.12 | Laravel 13.35 | WordPress 7.1

Expand Down Expand Up @@ -93,8 +93,8 @@ This outputs the complete system information as a JSON object:
```json
{
"framework": {
"current": "13.35.1",
"latest": "13.35.1",
"current": "13.35.2",
"latest": "13.35.2",
"update_available": false,
"development": false
},
Expand Down Expand Up @@ -129,7 +129,7 @@ This outputs the complete system information as a JSON object:
When running a dev branch (`dev-develop`, `13.x-dev`, etc.), the command adapts its output:

```
Pollora dev-develop (latest stable: v13.35.1)
Pollora dev-develop (latest stable: v13.35.2)
```

No misleading "update available" warning is shown for development installations: `development` is `true` in the JSON output, and Site Health reports a development build instead of comparing it with releases.
Expand Down Expand Up @@ -170,6 +170,7 @@ It looks for failures that stay silent — the site renders, every command exits
| Theme pattern cache | pattern files missing from WordPress's cached list |
| Routes over block templates | `Route::wp()` routes answering in place of a block theme's templates |
| Blocks in the legacy folder | blocks still in `resources/blocks`, which stops loading in v15 |
| Asynchronous actions | an ignored `#[Async]`, an unavailable driver, WP-Cron events overdue or queued jobs waiting with nothing to run them — see [Asynchronous Actions](/hooks/async-actions/#something-must-run-the-queue) |

The builds, directories, placeholders and blocks are checked for the active theme, every Pollora plugin and every enabled Laravel module.

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/compare.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Pollora's own WordPress models are built on **`pollora/colt`, a fork of Corcel**
| **Testing** | Laravel's testing tools | Laravel's testing tools | Your setup | Pest, Playwright, linting in GitHub Actions | Your app's tests |
| **AI tooling** | [Nectar](/nectar/overview/): guidelines, 9 agent skills and 10 MCP tools, on Laravel Boost | | | | |
| **License and price** | MIT, free | MIT, free | MIT, free | One-time purchase: $80 for one site, $240 for unlimited sites | MIT, free |
| **Maturity** (October 2026) | v13.35.0 stable, a young project | v6.3.0, about 2.66M Packagist installs | v11.2.1, about 13.3k GitHub stars | Commercial product from Roots | v9.0.0, about 4.8k GitHub stars |
| **Maturity** (October 2026) | v13.35.2 stable, a young project | v6.3.0, about 2.66M Packagist installs | v11.2.1, about 13.3k GitHub stars | Commercial product from Roots | v9.0.0, about 4.8k GitHub stars |

Sources: each project's GitHub repository, Packagist and product page (roots.io/acorn, roots.io/radicle), checked on 1 October 2026.

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Yes. The framework (`pollora/framework`), the project skeleton (`pollora/pollora

## Is Pollora production ready?

Yes, the current release, v13.35.0, is a stable release, and `composer create-project pollora/pollora` installs it. Pollora has gone through several major versions: Nectar ships upgrade prompts from Pollora 12 to 13. Its community is still small compared with older WordPress tooling, which is worth weighing for long-lived projects.
Yes, the current release, v13.35.2, is a stable release, and `composer create-project pollora/pollora` installs it. Pollora has gone through several major versions: Nectar ships upgrade prompts from Pollora 12 to 13. Its community is still small compared with older WordPress tooling, which is worth weighing for long-lived projects.

## How do Pollora version numbers work?

Expand Down
4 changes: 2 additions & 2 deletions src/content/docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ number in its `composer.lock` — which is what `create-project` installs from,
whatever constraint the `composer.json` carries. So one version number describes
an install completely.

The current release is **v13.35.1**, a stable release: a plain
The current release is **v13.35.2**, a stable release: a plain
`composer create-project pollora/pollora` installs it.

## Installation Methods
Expand Down Expand Up @@ -74,7 +74,7 @@ With `--ddev`, the CLI configures DDEV (WordPress project type, PHP 8.4, MariaDB
| `--ver=VERSION` | Install a specific version or constraint (e.g. `13.35.0`, `^13.35`) |
| `--stable` | Install the latest stable release instead of the latest pre-release |

`pollora new` installs the latest release **including pre-releases**: today that is the stable v13.35.1, and a beta published after it would be picked up. Pass `--stable` to never get a pre-release, or `--ver` to pin an exact version.
`pollora new` installs the latest release **including pre-releases**: today that is the stable v13.35.2, and a beta published after it would be picked up. Pass `--stable` to never get a pre-release, or `--ver` to pin an exact version.

### 2. Composer create-project

Expand Down
5 changes: 5 additions & 0 deletions src/content/docs/hooks/actions-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ $callbacks = Action::callbacks('init');
$filterCallbacks = Filter::callbacks('the_content');
```

## Asynchronous actions

An action can run after the request instead of inside it: add `->async()` after `Action::add()`, or `#[Async]` next to `#[Action]`. See [Asynchronous Actions](/hooks/async-actions/).

## Deferred callbacks

Pollora aligns with WordPress behavior for callback registration: callbacks that are not yet callable at registration time (e.g., functions defined by plugins loaded later) are accepted without throwing an exception. Validation occurs at execution time, just like WordPress's native `add_action()`/`add_filter()`.
Expand Down Expand Up @@ -259,5 +263,6 @@ class MyService {

## See also

- [Asynchronous Actions](/hooks/async-actions/)
- [WordPress hooks with PHP 8 attributes](https://pollora.dev/guides/wordpress-hooks-php-attributes/)
- [How Pollora runs WordPress inside Laravel](https://pollora.dev/guides/how-pollora-runs-wordpress-inside-laravel/)
Loading