PrevThrottling AI bot traffic in ProcessWire
Many websites these days are the feeding ground for AI bots. In this post we look at a tool for taming all the hungry crawlers and bots. More
23 January 2026 by Ryan Cramer 0 Comments
Included are more than 70 issue fixes and 175 commits. Here we’ll zoom in on the numerous new features and improvements to the core for one of our best new versions yet!
Like with most ProcessWire upgrades, you should be able to just replace your /wire/ directory with the one from the new version. As always, make sure you are actually replacing the /wire/ directory and not merging the new one into the old one.
We also made some changes in the root /index.php file specific to the newest PHP versions. So if you get any PHP notifications after upgrading, then you'll also want to replace the root /index.php file with the one from the new version. If you aren't getting any notifications then I would suggest just leaving the old index.php file in place.
The biggest addition to this major version release was the new design option "Konkat Default" for AdminThemeUikit, created by Konkat Studio (Jan Ploch and Diogo Oliveira). It provides a new, more modern look for ProcessWire, which also shares some elements of consistency with our new website. (The website was also designed by Konkat Studio).

Using the new admin design is optional and is available as an alternative to the original design. It provides a new look which we think will help to better position ProcessWire for the future. It's the design theme we'll use when taking screenshots of ProcessWire or demonstrating it to others.
When you upgrade to ProcessWire 3.0.255+, it will automatically ask you if you want to use the new design. You can also switch between "Konkat Default" and "Core Original" in the AdminThemeUikit module settings. When installing ProcessWire for the first time, the installer also gives you the choice of which design theme you'd like to begin with.

Beyond the improved design, the Konkat Default theme has a lot of unique features to offer. Here are a few of my favorites:
A big thanks to Konkat Studio for their great work on the new admin theme and website designs, and we hope that you enjoy having new options available for the look and feel of the ProcessWire admin.
Support has been added for a new type of conditional hook that matches method return values. This enables you to specify whether or not your hook should be called based on the return value of the hooked method. It works very much like the existing support for matching arguments of the method call, but instead matches the return value. For details see hooks matching return value or type.
$wire->addHookAfter('Field::getInputfield:(label*=Currency)', function($e) {
$f = $event->return;
$e->message("Matched '$f->name' with label: $f->label");
});Markup Regions can now remove class names with wildcards. For instance, the example below would remove all class names from the div#content starting with "uk-width-".
<!-- remove all classes starting with uk-width -->
<div id="content" class="-uk-width-*" pw-append></div>Want to use a regex pattern rather than a wildcard? Use the slash "/" character as the starting and ending delimiter for the pattern to match. The example below demonstrates this. It removes all class names from div#content starting with uk-text or uk-width.
<!-- remove all classes starting with uk-text or uk-width -->
<div id="content" class="-/^uk-(text|width).+$/" pw-append></div>Markup Regions have also been improved so that they now allow modification of markup regions from files such as _main.php (or files included from it) … files that previously were only used for defining markup regions. Previously it was not possible to populate markup regions after rendering of _main.php started. So if you've ever attempted to populate a markup region and found that it didn't work, chances are that it will work in ProcessWire 3.0.255.
<div id="hello">Hello</div>
<span pw-append="hello">World</span>
Result: <div id="hello"><span>World</span></div>We've updated the core Javascript API functions: ProcessWire.alert(), ProcessWire.confirm() and ProcessWire.prompt() to use Uikit modals rather than Vex modals. We also added two new Javascript API methods: ProcessWire.entities1() and ProcessWire.unentities()` to mirror those in $sanitizer.
ProcessWire.alert('Hello World');
ProcessWire.confirm('Want to proceed?', function() {
// code for yes
}, function() {
// code for no
});
ProcessWire.prompt('Your name', 'First/last name', function(val) {
ProcessWire.alert('You entered: ' + val);
});
console.log(ProcessWire.entities1('A & B')); // A & B
console.log(ProcessWire.entities1('A & B')); // A & B
console.log(ProcessWire.unentities('A & B')); // A & BSupport was added for new custom value variables in Selectors (whether runtime or from PageFinder). This enables you to specify a custom value that can be referenced from any selector. This is useful in cases where you don't know the value you want to use in a selector at the time the selector is being defined. For example, when selectors are stored in configuration (such as those used for Page fields). The example below demonstrates how to set a custom variable value and then how to refer to it from within a selector:
Selectors::setCustomVariableValue('foo', 'bar');
$s = new Selectors("name=[foo]");
echo $s; // outputs: "name=bar"
$pages->find("name=[foo]"); // finds pages with name=barThe $files->unzip() method was rewritten to add a lot of new options and features, as well as support the new FileValidatorZip core module. In particular, note all the new settings available with the $options argument.
The installer has been updated to generate a random session cookie name. This helps if you have multiple ProcessWire installs in the same environment, as switching between them won't cause you to be logged out. Previously, ProcessWire installations all started with the session cookie named "wire" or "wires" and thus multiple installs in the same environment would collide with each other and cause you to be logged out.
In addition, the installer now asks you whether you'd like to use the Konkat Default or Core Original design theme of AdminThemeUikit.
In ProcessWire 3.0.255+ now you can use $users->new(), $roles->new(), and $permissions->new(), all of which return a new instance of the given Page. They are functionally similar to the existing $pages->new() method, but specific to their types.
$u = $users->new();
$u->name = 'hanna';
$u->pass = '33030303!303O0o3l1';
$u->addRole('member');
$u->save();A new InputfieldWrapper::removeByName() method has been added for removing an Inputfield from the form by name. It doesn't matter if the referred Inputfield is nested within others or not, this method will find it and remove it:
$f = $form->removeByName('middle_name');
if($f) {
// Inputfield $f was removed from the form
} else {
// field with that name is not in this form
}A new $notices->getVisible() method was added that returns all Notice objects visible to current user. This prevents the need for filtering notices at output time. For example, if a notice has the logOnly flag, then that means it is intended only for log, and thus would be considered not visible. But this method filters for many other flags as well.
foreach($notices->getVisible() as $notice) {
$cls = 'uk-alert-' . ($notice instanceof NoticeMessage ? 'primary' : 'warning');
echo
"<p class='uk-alert $cls'>" .
$sanitizer->entities($notice->text) .
"</p>";
}The $notices API variable also has two new methods added: render() and renderText(). Both are hookable, meaning they can have the arguments modified, return value modified, or be completely overridden if needed. The render() method uses the current $adminTheme to render all currently visible notices. It accepts a single $options (array) argument where you can override any of the classes, icons, or markup used by the notices. The following example is equivalent to the previous example above:
echo $notices->render();The $notices->renderText() method is similar to the render() method except that it outputs plain text only (no markup). It is intended for console usage of ProcessWire where you might be outputting to a command line console. It produces output like this:
✓ All is well, this is a message notice
⇾ Something might need adjustment, this is a warning
⚠ Well this is no good, this is an error
A new WireRandom::string() method has been added to the WireRandom class. This method lets you specify that it should create a random string using specific characters. The first argument is the length (or 0 for random length) and the second argument is a string containing the characters that it is allowed to use.
$rand = new WireRandom();
$s = $rand->string(5, '01X');
echo $s; // 101X0If you omit the first argument (length) then a random length is assumed. If you omit the second argument (characters) then it will use any ASCII characters.
The Pageimage class has a new filenameDoesNotExist() hookable method. This is called by the $image->size() method when a resize of an image is attempted, but the original/source image does not exist. You might use this to provide a substitute image. If you do so, then you'd make the method return true.
$wire->addHookAfter('Pageimage::filenameDoesNotExist', function($e) {
$filename = $event->arguments(0);
$replacement = config()->paths->templates . 'styles/images/placeholder.jpg';
files()->copy($replacement, $filename);
$e->return = true;
});A new getCommentByID() method was added to FieldtypeComments. This provides a simple way to get a single comment by its ID, and accompanies the existing getCommentsByID() (plural) method.
$field = $fields->get('reviews');
$review = $field->type->getCommentByID($field, 1234);
if($review) {
// Review/comment with id 1234 found
}ProcessWire 3.0.255+ comes with a lot of new hookable methods. In particular, the Page class has twenty new hookable methods, most of which are similar to those in the Pages class, but can be a lot more convenient, especially when using custom page classes. Let's say that you wanted to hook after a page was saved, but only for pages using your custom page class BlogPostPage. Previously you would have done this:
$pages->addHook('Pages::saved', function(HookEvent $e) {
$page = $e->arguments(0);
if($page instanceof BlogPostPage) {
// your code here
}
});Now you can do this:
$wire->addHook('BlogPostPage::saved', function(HookEvent $e) {
$page = $e->object;
// your code here
});So not only is code in your hook simpler, but it's also more efficient, as ProcessWire doesn't have to call your hook code at all for pages that aren't specifically BlogPostPag instances (or whatever custom page class you are using).
Here's a complete list of the new hookable methods available in the Page class, and any custom page classes:
editReady(InputfieldWrapper $form) // page is about to be edited in the page editor
saveReady(array $changes, $name = false) // before page saved
saved(array $changes, $name = false) // after page saved
addReady() // before new page is added
added() // after new page is added
moveReady(Page $oldParent, Page $newParent) // before page is moved
moved(Page $oldParent, Page $newParent) // after page is moved
deleteReady(array $options) // before page is deleted
deleted(array $options) // after page is deleted
cloneReady(Page $copy) // before page is cloned
cloned(Page $copy) // after page is cloned
renameReady(string $oldName, string $newName) // before page is renamed
renamed(string $oldName, string $newName) // after page is renamed
addStatusReady($name, $value) // before page has status changed
addedStatus(string $name, int $value) // after page status has been changed
removeStatusReady($name, $value) // before a status is removed from page
removedStatus(string $name, int $value) // after status removed from pageThese hookable render methods were also added:
render($options = [], $options2 = null) // render page or field output
renderPage(array $options = []) // render page output
renderField($fieldName, $file = '') // render field output for pageSee a list of all Page class hookable methods.
The Pages class ($pages API var) also got a new hookable method: Pages::addReady(). This method is called whenever a new page is about to be added and saved to the DB. You might hook into this to make some modification to the page before it is created. This accompanies the existing Pages::added() hookable method, which is called after the page has been saved to the DB.
$pages->addHook('addReady', function(HookEvent $e) {
$page = $e->arguments(0);
$page->name = str_replace('pw', 'processwire', $page->name);
});Want to add a single hook that will get called for ALL hookable methods in a class? Now you can with the new wildcard method hooks. I see this as primarily useful for debugging, but depending on the class you are hooking, there maybe other use cases as well.
$wire->addHook('HomePage::*', function(HookEvent $e) {
$page = $event->object;
bd($page, $e->method); // i.e. "path", "renderPage", etc.
});Just be aware that some hooks get called a LOT and so if you hook something like Page::* then it might be more than can be handled. Luckily, the wildcard can apply to method names as well. So if you only wanted to hook methods of the Page class beginning with save, then you'd do this:
$wire->addHook('Page::save*', function(HookEvent $e) {
$page = $e->object;
bd($page, $e->method); // i.e. "saveReady", "saved"
});In order to validate that a session is legitimate, ProcessWire uses a fingerprint value defined by the flags in $config->sessionFingerprint. It's always had a default value that uses flags requiring that both the user-agent and IP address don't change. The problem is that it seems a large amount of legit traffic is often using IP addresses that regularly change during a session, especially on mobile networks. So we've updated the default value (for new installations only) to allow for dynamic IP addresses, while still requiring a user-agent match. Since this change affects new installations only, if you want to apply the same change to an existing installation, add this to your /site/config.php file:
$config->sessionFingerprint = 8;Or if you want your fingerprint to include the IP address on a NEW installation, add this to your /site/config.php file:
$config->sessionFingerprint = 2 | 8;ProcessWire 3.0.255+ now comes with an option for a verbose 404 log. This logs all requests that result in a 404 to your Setup > Logs > http404 log. It's probably not something that you want to leave on permanently, but it can be useful for identifying short term issues. To enable it, add the following to your /site/config.php file:
$config->logs = [
'modules',
'exceptions',
'http404', // add
];The modules and exceptions logs are enabled by default, which is why we're repeating them here before our http404 log. Other log options include pages, fields, templates and deprecated.
ProcessWire 3.0.255+ now includes a new FileValidator module for validating ZIP files: FileValidatorZip. It automatically validates uploaded zip files. This prevents bad zip files or zip files with zillions of files from causing issues after upload, further enhancing safety.
Depending on the module settings, sometimes validation can also result in a false positive, especially when it comes to quantity of files in a zip. If you are uploading a zip to your system (such as a new module) and you get a "too many files" error message, note that you can control what the ZipFileValidator allows by going to the module settings in: Modules > Configure > ZipFileValidator. This module is automatically installed on new installations, and after upgrading to 3.0.255+. Here's a list of the validations it has the ability to perform:
With this new version of ProcessWire, our API reference continues to improve a lot. Documentation was improved or added for roughly 50 core modules. You'll find this documentation both in our online API reference and as phpdoc in the core PHP files.
In addition, documentation improvements were made to all the Pages* classes for improved output in our API reference.
The MarkupAdminDataTable module is used through ProcesWire's admin, and also by many 3rd party modules. It's an API for HTML tables, but hasn't previously supported the colspan attribute for <td> elements. Now it does. To use it, you specify boolean true for a column you want to skip in calls to $table->row(), making the previous column span into it. Note that this is supported by the $table->row() method but not the $table->headerRow() method.
$table = $modules->get('MarkupAdminDataTable');
$table->row('AAA', 'BBB', 'CCC');
$table->row('111', true, '333');
$table->row('FOO', 'BAR', true);
echo $table->render();-------------------
| AAA | BBB | CCC |
| 111 | 333 |
| FOO | BAR |
-------------------
Several improvements and optimizations were made to ProcessWire's full-text searching operators in $pages->find() selectors, making them more accurate and faster in many situations.
New versions of PHP continue to deprecate things from prior versions of PHP, and we continue to update ProcessWire to make sure that it's ready for the latest and great from PHP. I'm not always an early adopter when it comes to PHP versions, so we depend on the community to help identify necessary changes for newer PHP versions. A big thanks to Matjazp, Adrian and others that take the time to find and report these things.
Certainly one of the biggest parts of every new main/master version involves resolution of numerous issue reports. And that is also true of this version, with 70 resolved issues, making this a highly stable and reliable version of ProcessWire.
Every new version also involves ideas and solutions from numerous people in the community, so a big thanks to our community and contributors. This version in particular contains major work by Diogo Oliveira and Jan Ploch of KONKAT Studio, which we are very thankful for! Be sure to check out Jan’s Page Grid for an amazing site builder in ProcessWire. We hope that you enjoy this new version and look forward to the next.
12 September 2025 1
Many websites these days are the feeding ground for AI bots. In this post we look at a tool for taming all the hungry crawlers and bots. More
17 October 2025 2
ProcessWire’s API is accessible through API variables and it provides multiple ways to access them. There are benefits and drawbacks to each approach and this post aims to cover them all. More