All About ProcessWire Hooks

ProcessWire contains many methods that you may hook into in order to modify the behavior of the method. Hooks are typically attached by modules (most often autoload modules). However, hooks may be attached from anywhere that you use ProcessWire's API, like your template files for example.

What are the different types of hooks in ProcessWire?

  • Before hooks: A hook that runs before the hooked method. These hooks typically analyze and/or modify the arguments passed to the hooked method.

  • After hooks: A hook that runs after the hooked method. These hooks typically analyze and/or modify the return value of the method.

  • Replace hooks: A hook that entirely replaces the hooked method. This is a type of Before hook that signals to ProcessWire that it wants to replace the hooked method.

  • Method hooks: A hook that adds a new method to a class. The new method becomes accessible via $object->method().

  • Property hooks: A hook that adds a new property/variable to a class. The new property becomes accessible via $object->property. Not all ProcessWire classes support property hooks.

What methods in ProcessWire are hookable?

Any method that begins with 3 underscores, i.e. ___method(), is hookable in ProcessWire. There are hundreds of them. See Captain Hook!

Method hooks (new methods added to a class via a hook) are themselves hookable too. For example, Page::render(), Page::viewable() and Page::editable() are all methods added to the Page class via method hooks. If you view the /wire/core/Page.php class, you'll see that none of these methods actually exist in the class. However, you can hook before or after any of them just as easily as if they were defined in the class.

If you can't find a hook that you need (or just aren't sure) post a question in the forum. If we don't already have a hook for what you need, we can usually add one.

Defining Hooks

Do I want my hook to run before or after the method I am hooking? (Or, does it matter?)

Before hooks
You would want your hook to run before the hooked method if you want to analyze or modify the arguments supplied to the hooked method. Another use case for a before hook is if you wanted to entirely replace the implementation of the hooked method. Before hooks are defined with $this->addHookBefore(…); within a class, or wire()->addHookBefore(…); outside of a class.

After hooks
You would want your hook to run after the hooked method if you wanted to analyze or modify the return value of the hooked method. An After hook can also analyze the arguments supplied to the hooked method, but not modify them (at least, it wouldn't be particularly useful to). After hooks are defined with $this->addHookAfter(…); within a class, or wire()->addHookAfter(…); outside of a class.

Hooks where it doesn't matter
When you are defining a new method or property for a class via a hook, there is no question of before or after, so you can simply define such a hook with $this->addHook(…) or wire()->addHook(…).

Some hookable methods in ProcessWire have no implementation and exist purely for you to hook. Because hooks like this exist purely "to be hooked", it doesn't matter if you hook before or after them. Examples include Pages::saveReady which is called after it's been determined the page will definitely be saved, but right before it is done. Another is Pages::saved, which is called after a page has successfully been saved. These hooks provide more certainty and less need for error checking than hooking before or after Pages::save. You may see several other specific-purpose hooks like this in ProcessWire.

Do I want my hook to apply to all instances of a class, or just one?

Most of the time you want a hook to apply to all instances of a class. For instance, when you hook Page::viewable, you likely want your hook to be called regardless of what Page it is. Such hooks are defined by specifying the class and method (i.e. Class::method) in the addHook() call. For example:

// inside a class
$this->addHookAfter('Class::method', function($event) {
  // function implementation

// inside a class (if 'myHookMethodName' is a public method of class)
$this->addHookAfter('Class::method', $this, 'myHookMethodName');
// outside a class  
wire()->addHookAfter('Class::method', function($event) {
  // function implementation

// outside a class (if 'myHookFunctionName' defined separately)
wire()->addHookAfter('Class::method', null, 'myHookFunctionName');

For the times when you want your hook to apply only to a single instance of a class, you can attach the hook directly to the object instance of that class:

// inside a class
$page = $this->wire('page'); // may be any Page object instance
$page->addHookAfter('render', function($event) { ... });

// outside a class ($page may be any Page object instance)
$page->addHookAfter('render', function($event) { ... }); 

The primary distinction is that we are calling the addHookAfter() method directly on the $page instance we want to attach it to (rather than attaching it using $this or wire()).

Some of ProcessWire's API variables (like $pages) only ever contain one instance. As a result, you may find it more efficient to attach such hooks directly to $pages (though it really doesn't matter much). For example, these two calls (below) do exactly the same thing. But the second one that attaches directly to $pages may be slightly more efficient:

$this->addHookAfter('Pages::saveReady', function($event) { ... }); 
$pages->addHookAfter('saveReady', function($event) { ... }); // equivalent

Where should I define my hook?

Hooks are most often defined in ProcessWire plugin modules or initialization template files. However, this is not a requirement, and you may technically define hooks anywhere you want. But you need to make sure that your hook really does get called when you want it to, so your hook must be defined and attached sometime before the method you are hooking gets called.

Attaching hooks from a plugin module

Attach hooks with a plugin module set to "autoload" on every request. Such modules are called "autoload modules." These autoload modules have an init() method that gets called before ProcessWire handles a web request. This is the best place to attach hooks. However, this init() method is called before ProcessWire even knows what $page it will be rendering. So if your module needs to determine whether it should hook or not based on what $page is being viewed, you should attach your hook in a ready() method rather than an init() method. The ready() method is always called for all autoload modules right after ProcessWire determines what $page is going to be viewed.

To summarize: when defining hooks through modules, you will most likely want to attach those hooks through either the init() or ready() method of the module.

Attaching hooks from a template file

If you are defining hooks via your template file(s), you will most likely want to place such definitions near the top of the file, or in a common header include file used by all of your template files. An easy way to do this is to define $config->prependTemplateFile in your /site/config.php file. Whatever file you define there will be automatically included before all of your site template files (except for the one used by the admin). Many of ProcessWire's site profile already have this defined as _init.php. Regardless of what it is named, this is the file where you would want to place your hooks.

The $config->prependTemplateFile is not used by the ProcessWire admin, so if you are defining hooks specific to the ProcessWire admin, you should define them at the very top of the /site/templates/admin.php. If your hooks are applicable to both your site (front-end) and the ProcessWire admin, then you should manually include your sites initialization fie, i.e. include("./_init.php"); from your /site/templates/admin.php file.

How can my hook read (or modify) the arguments sent to the hooked method?

Every hook is passed an object called $event (of type HookEvent). This object contains an arguments() method that you can access to retrieve the arguments of the method either by index or name. This is best demonstrated in code. Lets say that you wanted to hook Pages::saved. In looking at the function definition in Captain Hook or directly in the file at /wire/core/Pages.php, you'll note that this function has one argument called $page. With this knowledge in hand, you have everything you need to retrieve that in your hook function:

public function myHookFunction($event) {
  // retrieve first argument by index (0 based)
  $page = $event->arguments(0);
  // OR: retrieve argument by name 'page'
  $page = $event->arguments('page');
  $this->message("You saved page: $page->path");

A Before hook has the opportunity to modify the arguments sent to the hooked function. In order to modify the argument, the hook must assign the argument back to $event->arguments by index or name, like this:

// assign by index (0=first argument)
$event->arguments(0, $myValue);

// assign by name
$event->arguments('argument_name', $myValue); 

How can my hook read (or modify) the return value of the hooked method?

The return value of a hooked function may be read or modified in a similar manner to the arguments. However, the return value is only available to After hooks. This is best demonstrated by a code example. Lets say that we want to add the text "Hello World" before the closing </body> tag of each rendered page. We would hook after Page::render with a hook method implementation like this:

public function hookAfterPageRender($event) {
  // $value contains the full rendered markup of a $page
  $value  = $event->return;
  $value = str_replace("</body>", "<p>Hello World!</p></body>", $value);
  // set the modified value back to the return value
  $event->return = $value;

How can my hook know what object/instance the hook was called on?

Every hook function receives an $event object and this $event contains a property called $event->object. That property ($event->object) contains the instance of the object that the hook was executed on. For example:

public function hookAfterPageRender($event) {
  $page = $event->object;
  if($page->id == 1) {
    $event->return = str_replace("</body>", "<p>Homepage!</p></body>", $event->return);

How can my hook replace another method entirely?

This is a rare need, and one to be careful with, but it is possible to accomplish in ProcessWire. A hookable method can only be replaced with a Before hook. The hook sets the value of $event->replace = true; and this tells ProcessWire not to call the originally hooked method. Assuming the originally hooked method was going to return some value, the hook should populate $event->return with an expected value since the original method won't be able to do so.

public function hookBeforePageRender($event) {
  $page = $event->object;
  // we'll only apply this to the front-end of our site
  if($page->template == 'admin') return;
  // tell ProcessWire we are replacing the method we've hooked
  $event->replace = true;
  $event->return = "Sorry the site is undergoing maintenance";

How can I define my own hookable methods?

Hookable methods can be defined in any class derived from ProcessWire's Wire class. Nearly all of ProcessWire's core classes and modules are derivative of the Wire class, and thus capable of having hookable methods. To make a class capable of having hookable methods, simply extend one of ProcessWire's classes such as Wire or WireData as your base. Most ProcessWire modules typically extend the WireData class. Assuming your class or module extends one of ProcessWire's classes, you can make any method hookable simply by prepending 3 underscores to the beginning of the methods you want to be hookable.

public function ___hookableMethod($arg1, $arg2) {
  // this method is hookable

public function regularMethod($arg1, $arg2) {
  // this method is not hookable

Calls to a hookable method should always be without the 3 underscores, i.e.

$this->hookableMethod('a', 'b'); 

If for some reason you need to bypass ProcessWire's hook system for a given method, you can include the 3 underscores in the method call and no hooks will be executed.

How can I add a new method via a hook?

You can add new methods to any ProcessWire class in the same way that you would hook an existing method. The only difference is that the method doesn't already exist, so you are adding it. Also, because there is no distinction of "before" or "after" for something that doesn't already exist, you can attach the method using addHook(); rather than addHookBefore(); or addHookAfter() … though technically it doesn't really matter what you use here. Lets say that you wanted to add a new method to all Page instances that outputs a relative time string of when the page was last modified (for example, "45 minutes ago" or "3 days ago", etc.):

public function init() {
  $this->addHook('Page::lastModified', $this, 'lastModified');

public function lastModified($event) {
  $page = $event->object;
  $event->return = wireRelativeTimeStr($page->modified);

Now all Page instances have a $page->lastModified(); method:

echo $page->lastModified(); // outputs "10 minutes ago"

New methods that you add can also use arguments. Lets say that we wanted to change our lastModified() function to accept a single boolean argument that indicates whether it should return a defined date/time like "2013-05-15 10:15:12" or a relative date/time like "10 minutes ago":

public function lastModified($event) {
  $page = $event->object;
  if($event->arguments(0) === true) {
    // return relative time
    $event->return = wireRelativeTimeStr($page->modified);
  } else {
    $event->return = date('Y-m-d H:i:s', $page->modified);

Here are example calls:

echo $page->lastModified(true); // returns "10 minutes ago"
echo $page->lastModified(false); // returns "2013-05-15 10:15:12"
echo $page->lastModified(); // returns "2013-05-15 10:15:12"

How can I add a new property via a hook?

You can add new properties to some ProcessWire classes (like the Page class) by attaching it with addHookProperty(). For example, lets say that we wanted to add a "hello" property to all User objects that contains a custom greeting to the user like "Hello [username]":

public function init() {
  $this->addHookProperty("User::hello", $this, "hookUserHello");

public function hookUserHello($event) {
  $user = $event->object;
  $event->return = "Hello $user->name";

Now all User instances contain a "hello" property that when accessed, says "Hello [username]":

echo $user->hello; // outputs "Hello Karena"

As another example, lets say that we want to add a new property called "intro" that returns a short version of the body copy limited to 255 characters. For the sake of diversity, we'll use the "outside of a class" approach that you might use from your template files. In such a case, you would want to define this at the top of your template file (or in a common header include).

wire()->addHookProperty('Page::intro', function($event) {
  $page = $event->object;
  $intro = substr(strip_tags($page->body), 0, 255);
  $lastPeriodPos = strrpos($intro, '.');
  if($lastPeriod !== false) $intro = substr($intro, 0, $lastPeriodPos);
  $event->return = $intro;

Now every Page object has an "intro" property that is a short version of the body copy that can be accessed via $page->intro:

foreach($page->children as $child) {
  echo "<p><a href='$child->url'>$child->title</a><br />$child->intro</p>";

Shorter hook syntax for ProcessWire 2.4 / PHP 5.3+

When using ProcessWire 2.4 (or newer) with PHP 5.3+ you can take advantage of anonymous functions and shorter syntax. This enables you to attach and define a hook within 1 function call:

$this->addHookAfter('Class::method', function($event) { // inside a class
  // hook method implementation

wire()->addHookAfter('Class::method', function($event) { // outside a class
  // hook method implementation

Note: since the anonymous function usage is now common, we use examples of both throughout this documentation page. If for some reason you are using a really old very of ProcessWire (prior to 2.4) you'll want to stick with the non-anonymous function version.

See Also


  • Bruce

    Bruce 4 years ago 43

    I am very novice at both PHP and ProcessWire so pardon me if this question doesn't make a lot of sense. Where would you insert a hook to add a property to $page?

  • Bruce

    Bruce 4 years ago 33

    OK, my last comment jumped the gun. I didn't supply enough information for it all to make sense.

    I would like to add a property to $page that is an object that I can add further properties to. So logically, I'd like to:

    $page->myobject = new MyObject();
    $page->myobject should just return the object from which its various properties could be accessed.

    Looking at the addHookProperty examples they all use existing $variable properties to derive their fetched value.

    So my first question is how would I create a new object that is stored within $page, for each page.

    My second question is where is the best place for this to take place. If I declare the public function init() is that automatically called no matter what? Where should it be declared.

    As you can tell, I'm new to both PHP and PW, but not programming, so pardon me if I'm missing obvious things - I find the best way to learn is to jump right in.

    • ryan

      ryan 4 years ago 32

      Bruce, if you just want to add a property to a single page, then you can just add it:

      $page->yourProperty = $yourObject;

      The only reason you'd want to use addHookProperty() is if you want to add a method to a Page object (or more likely, ALL Page objects), that can be accessed as a property.

      For instance, lets say you wanted to add a 'lastModified' property to all Page objects, so that one could receive a formatted date string of the last modified date whenever they accessed $anyPage->lastModified; Here's how you would add it:

      wire()->addHookProperty('Page::lastModified', function($event) {
      return date('Y-m-d H:i:s', $event->object->modified);

      In terms of where you would add that, most likely you'd want it in your site's initialization file, typically called /site/templates/_init.php. (That _init.php file is defined in your /site/config.php file via $config->prependTemplateFile).

      Another place you could add it is in an autoload module, which is what you'd want to do if creating modular code to share with other PW installations.

Post a Comment

Your e-mail is kept confidential and not included with your comment. Website is optional.