Academic Tools. Smarter Research.
Open Journal SystemsOJS Plugin Architecture & Custom Extension

The Complete Guide to OJS Plugin Architecture & Custom Extension

An exhaustive engineering guide to developing custom plugins for OJS. Covers plugin taxonomy, lifecycle methods, HookRegistry patterns, database migrations, and REST endpoints.

Custom plugins represent the pinnacle of Open Journal Systems engineering, allowing developers to inject custom business logic, automate editorial workflows, and integrate third-party APIs without modifying a single line of core code.

Real Problem

Institutions requiring custom integrations often hack core OJS files, which instantly voids upgradeability and introduces severe security vulnerabilities.

Practical Solution

Leverage the PKP Plugin Architecture: implement isolated Generic, Gateway, Block, and Import/Export plugins utilizing HookRegistry interception, schema migrations, and REST endpoints.

OJS plugin developmentcustom OJS pluginsPKP plugin architectureextend Open Journal Systemsgeneric plugin OJS tutorial

The Architecture of PKP Extensibility

The Public Knowledge Project designed Open Journal Systems with modularity as a foundational principle. Rather than forcing publishers into a rigid workflow, OJS exposes hundreds of event hooks across its controller, database, and presentation layers.

By developing modular plugins, software engineers can extend OJS functionality—such as connecting institutional single sign-on (SSO), depositing metadata to national research councils, or generating automated invoices—without touching core application code.

The Five Core Plugin Categories

1. Generic Plugins (plugins/generic/)

The most common and powerful plugin category. Generic plugins can intercept application hooks anywhere in the lifecycle, add custom tabs to the Journal Settings dashboard, and inject JavaScript or CSS into frontend and backend interfaces. Examples: Crossref, ORCID Profile, Custom Block Manager.

2. Import/Export Plugins (plugins/importexport/)

Specialized plugins for ingesting or extracting structured bibliographic metadata. Examples: Native XML Import/Export, Crossref XML Export, PubMed/MEDLINE Export, DOAJ Export.

3. PubId Plugins (plugins/pubIds/)

Plugins responsible for generating and assigning persistent identifiers to published submissions, issues, and galleys. Examples: DOI Plugin, URN Plugin.

4. Block Plugins (plugins/blocks/)

Lightweight visual plugins that render custom sidebar or footer blocks in frontend reader themes. Examples: Language Toggle Block, DevelopedBy Block.

5. Gateway Plugins (plugins/gateways/)

Plugins that expose direct HTTP endpoints for external harvesting tools or integration daemons.

The Plugin Lifecycle: Registration and Hook Interception

When OJS initializes a request, it inspects active plugins in the plugin_settings database table. For each active plugin, OJS calls the register($category, $path, $mainContextId) method.

Inside this method, the plugin informs OJS which application events it wishes to intercept using the HookRegistry facade:

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

$success = parent::register($category, $path, $mainContextId);

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

HookRegistry::register('TemplateManager::display', [$this, 'injectHeaderAssets']);

HookRegistry::register('Publication::publish', [$this, 'handlePublicationEvent']);

}

return $success;

}

Preserving Clean Upgrades

Because custom plugins live inside their own isolated directory (e.g., plugins/generic/myPlugin/), core OJS upgrades can be executed around them without overwriting bespoke institutional code.

Step-by-Step Action Plan

1

Select Plugin Category

Choose from generic, blocks, importexport, gateways, or pubIds based on functional integration scope.

2

Define version.xml Metadata

Declare unique plugin application name, category type, semantic release version, and lazy-load flags.

3

Implement Primary Plugin Class

Extend the base Plugin or GenericPlugin class and implement the mandatory register() lifecycle hook.

4

Hook into Core Application Events

Register callbacks using HookRegistry::register() to intercept controller actions, templates, or emails.

5

Package & Deploy via Plugin Gallery

Bundle the plugin into a standardized tarball and test installation via the OJS Plugin Gallery installer.

Questions People Usually Ask

What is the difference between Generic Plugins and Gateway Plugins in OJS?

Generic plugins are versatile extensions that can intercept any core hook, add settings pages, and inject UI elements. Gateway plugins are specifically designed to expose public external endpoints (e.g., lockss, oai, custom data feeds).

Can a custom plugin add new tables to the OJS database?

Yes! Plugins can define custom database schema migrations. In OJS 3.3, this was handled via schema.xml; in OJS 3.4+, plugins use modern Laravel Illuminate migrations.

Why is lazy-load set to 1 in most generic plugins?

Setting lazy-load to 1 ensures the plugin class is only instantiated when one of its registered hooks is triggered, significantly conserving server memory across general page views.

Need a Custom OJS Plugin Developed?

AcademiaTech engineers enterprise-grade custom plugins for Open Journal Systems, connecting your editorial workflows to institutional databases and APIs.