How the plugin is built
This page is for developers who want to extend WP Job Portal or understand where something happens in the code. It describes the free plugin, wp-job-portal, version 2.7.0.
Folder layout
Section titled “Folder layout”| Path | What it holds |
|---|---|
wp-job-portal.php |
The main plugin file and the wpjobportal class: loading, activation, stylesheets, cron and service registration. |
modules/ |
One folder per feature, such as job, company, resume, jobapply, user and configuration. |
includes/ |
Shared code: the includer, request handling, form fields, shortcodes, widgets, Elementor and block support. |
includes/services/ |
Service classes: licences and updates, modes and onboarding, sample data, authorization, notifications, privacy, migrations and diagnostics. |
includes/features/ |
The twelve features absorbed from former add-ons (class-wpjobportal-feature-*.php). |
includes/tables/ |
Table classes, one per database table. |
includes/css/, includes/js/ |
Front-end and admin assets. |
templates/ |
Shared page parts such as the page title, header, pagination and form field wrappers, with admin versions in templates/admin/. |
Modules
Section titled “Modules”Each module is a small MVC unit:
modules/job/├── controller.php handles the request and picks a layout├── model.php queries and saves data└── tmpl/ layouts: jobs.php, viewjob.php, addjob.php, admin_jobs.php … └── views/ partials used by those layoutsA layout can have a companion file ending in .inc.php, for example jobs.inc.php. It is loaded just before the layout and usually prepares data or scripts for it.
Code reaches modules through WPJOBPORTALincluder:
| Call | Returns |
|---|---|
WPJOBPORTALincluder::getJSModel('job') |
An instance of WPJOBPORTALjobModel. |
WPJOBPORTALincluder::getJSTable('job') |
An instance of the table class WPJOBPORTALjobTable. |
WPJOBPORTALincluder::getObjectClass('user') |
An instance of a class from includes/classes/, here WPJOBPORTALuser. |
WPJOBPORTALincluder::include_file($layout, $module) |
Renders a module layout. |
WPJOBPORTALincluder::getTemplate($name, $args) |
Renders a shared template from templates/. |
Data passed from models to layouts lives in the static array wpjobportal::$_data.
How requests are routed
Section titled “How requests are routed”In the admin, each screen is a WordPress admin page named after its module. The wpjobportallt query variable picks the layout:
admin.php?page=wpjobportal_job&wpjobportallt=jobsadmin.php?page=wpjobportal_postinstallation&wpjobportallt=quickstartActions that change data are sent as tasks, either as a form post carrying form_request=wpjobportal, or as task=<method>&action=wpjobportaltask. The task names a method on the module’s controller. Every state-changing task is registered in a central route registry and an action policy, which check the HTTP method, the user’s role or capability, the nonce and, where it applies, record ownership. Unknown core controller actions are refused. System Status reports these checks under Request security.
On the front end, a shortcode sets the starting module and layout, for example [wpjobportal_jobseeker_controlpanel] starts at module jobseeker, layout controlpanel. Links inside the job board then change the wpjobportalme (module) and wpjobportallt (layout) query variables on the same page.
WP Job Portal keeps its data in its own tables, named {prefix}wj_portal_*: for example wj_portal_jobs, wj_portal_companies, wj_portal_resume, wj_portal_jobapply and wj_portal_config for settings. WordPress users are linked through wj_portal_users, which stores each person’s job board role.
Uploaded files are saved under the uploads directory, in the folder named by the data_directory setting (wpjobportaldata by default).
Database changes between versions are run by WPJOBPORTAL_Migration_Service, which records each step in a migration ledger shown under Migration history on System Status.
How Pro and the suites plug in
Section titled “How Pro and the suites plug in”Pro, Marketplace Suite, Recruit Suite and Smart Search & Matching are separate plugins. Each requires only the free plugin and talks to it through hooks.
Active add-ons
Section titled “Active add-ons”On load, the free plugin builds wpjobportal::$_active_addons from the active plugins whose folder starts with wp-job-portal-, minus that prefix. Much of the code checks this list to decide whether to show a button or load a feature, for example:
if ( in_array( 'tag', wpjobportal::$_active_addons ) ) { // tag features}Features now supplied by the free plugin or by a product add their old add-on key to this list when they register, so those checks keep working.
The provider API
Section titled “The provider API”Products that serve modules which used to be separate plugins do it through the provider API. Its version is the constant WPJOBPORTAL_PROVIDER_API, which is 1 in 2.7.0 and covers three filters:
| Filter | Purpose |
|---|---|
wpjobportal_includer_plugin_path |
Resolve a module’s controller, model, class, table or layout file to the product’s own folder. |
wpjobportal_includer_template_path |
Resolve a module view partial. |
wpjobportal_boot_controller_on_include |
Stop a controller from booting itself when it is included, so a product can reuse a free-plugin controller without rendering twice. |
A product checks the constant before it registers anything. If the free plugin is older than the product needs, the product stays dormant and shows an admin notice instead of half-loading.
Old add-ons still installed
Section titled “Old add-ons still installed”The free plugin, Pro and Marketplace Suite each leave a feature to its old single-feature add-on while that add-on is active, so the same hooks are never registered twice. When the old add-on is deactivated, the new provider takes over. WPJOBPORTAL_Absorbed_Features does this for the twelve features in the free plugin.
Two guards protect data during the switch:
WPJOBPORTAL_Absorbed_Config_Guardkeeps settings an absorbed feature reads from being switched off when the old add-on is deactivated.WPJOBPORTAL_Uninstall_Guarddeclines an old add-on’sDROP TABLEwhen it is deleted, for any table another product still reads.
Where the free plugin and Pro share a feature, the free plugin owns the free half and a filter raises the ceiling. WPJOBPORTAL_Feature_Tiers defines these, for example wpjobportal_tier_job_alert_frequencies (weekly only, by default), wpjobportal_tier_print_provider and wpjobportal_tier_basic_widgets.
Extension points
Section titled “Extension points”The rest is ordinary WordPress hooks. Many filters named wpjobportal_addons_* receive false as their first value: the free plugin asks, and a product that provides the feature answers with its output. See the hooks reference.

