Academic Tools. Smarter Research.
Open Journal SystemsMastering OJS Hook Architecture

Hook Architecture in OJS: Extending Core Functionality

Deep-dive technical guide into the PKP HookRegistry framework. Explains hook registration, callback parameters, return value conventions, and essential core hooks.

The HookRegistry is the central nervous system of Open Journal Systems; understanding how it intercepts execution allows you to modify almost any system behavior without touching core files.

Real Problem

Developers wanting to alter editorial decisions, email templates, or article landing pages often assume they must fork OJS core code, leading to unmaintainable systems.

Practical Solution

Master the PKP HookRegistry facade: learn how to inspect available core hooks, register callbacks, alter input/output parameters by reference, and control execution propagation.

OJS hook architectureHookRegistry PKPlist of OJS hooksextend OJS with hookscustom hook listener OJS

The Design Pattern: Event-Driven Interception

In enterprise software architecture, the Observer pattern allows components to listen for and respond to system events without tight coupling. In Open Journal Systems, this pattern is implemented through the static HookRegistry class.

Throughout the OJS lifecycle—from routing HTTP requests to saving submission records—core classes invoke:

HookRegistry::call('HookName', [&$param1, &$param2, ...]);

Any active plugin that registered a listener for 'HookName' will have its callback invoked sequentially, with the parameters passed directly into the callback function.

Critical Core Hooks in OJS

1. Template & Presentation Hooks

- TemplateManager::display: Fires before any Smarty template is rendered to the browser. Ideal for injecting custom CSS, JavaScript libraries, or global template variables.

- Templates::Article::Details::Reference: Fires during article landing page rendering, allowing custom citation formatting or external reference badge injection.

2. Submission Lifecycle Hooks

- Submission::submit: Fires immediately after an author completes manuscript submission.

- Publication::publish: Triggered when an editor clicks "Publish" on an issue or individual article.

- EditorDecisionHandler::recordDecision: Fires when an editor records an editorial decision (Accept, Revisions Required, Decline).

3. Email & Notification Hooks

- Mail::send: Fires prior to transmitting any outbound email, allowing custom footer injection, dynamic BCC addition, or routing through external API services.

Writing an Advanced Hook Callback

Consider a real-world scenario where a university press wants to automatically notify a Slack or Teams channel whenever an editor accepts a manuscript:

public function register($category, $path, $mainContextId = null) {

if (parent::register($category, $path, $mainContextId)) {

if ($this->getEnabled($mainContextId)) {

HookRegistry::register('EditorAction::recordDecision', [$this, 'onEditorialDecision']);

}

return true;

}

return false;

}

public function onEditorialDecision($hookName, $args) {

$submission = $args[0];

$decision = $args[1];

if ($decision['decision'] === SUBMISSION_EDITOR_DECISION_ACCEPT) {

$this->notifyEditorialChannel("Manuscript #{$submission->getId()} has been ACCEPTED!");

}

return false; // Continue normal OJS processing

}

By leveraging the hook architecture, the journal achieves complex automated integration while keeping the core OJS installation completely pure.

Step-by-Step Action Plan

1

Locate Target Core Hook

Search the PKP codebase for HookRegistry::call() statements at the exact point where customization is needed.

2

Register Callback Method

Use HookRegistry::register("HookName", [$this, "callbackMethod"]) in your plugin’s register() lifecycle.

3

Inspect Callback Arguments

Analyze the $args array passed to your callback to extract models, request objects, or output buffers.

4

Modify Objects by Reference

Update entity properties or inject additional template variables into the active context.

5

Control Propagation via Return Values

Return false to let other hooks execute, or return true to halt further hook processing and short-circuit core execution.

Questions People Usually Ask

What does returning true vs false from an OJS hook callback mean?

Returning false indicates that your callback executed normally and allows subsequent registered hooks to execute. Returning true signals that your callback handled the event completely and instructs OJS to halt further hook processing.

Can hooks be used to modify SQL queries executed by OJS?

Yes! Core DAO classes frequently invoke hooks before executing queries, allowing plugins to append WHERE clauses, change sort orders, or alter result sets.

How can I discover which hooks are firing on a specific page request?

You can register a wildcard or temporary debugging listener on HookRegistry::call() to log all executing hook names to the PHP error log during page generation.

Need Advanced Architectural Customization?

AcademiaTech architects clean, hook-driven extensions for complex university publishing requirements.