Wbcom Designs WP Career Board Pro Docs
Back to product Buy Now

Getting Started

Install, license, and activate WP Career Board Pro.

Introduction to WP Career Board Pro

WP Career Board Pro extends the free WP Career Board plugin with powerful features for growing job boards that need more than the basics.

WP Career Board Pro - Feature Overview

What You Get with Pro

Resume Builder

Candidates build structured, multi-section resumes directly on your site. Employers can view formatted resumes linked to each application. → Learn more

Custom Field Builder

Add any custom field to jobs, companies, or candidate profiles - no code required. Text fields, dropdowns, checkboxes, date pickers, file uploads. → Learn more

Application Pipeline (ATS)

Replace the simple 3-status application flow with a full stage pipeline: Screening, Interview, Offer, Hired/Rejected. Track candidates through custom stages with a visual Kanban board. → Learn more

Credit System

Charge employers credits to post jobs. Credit purchases run through your active commerce adapter - WooCommerce, WooCommerce Subscriptions, WooCommerce Memberships, PMPro, or MemberPress. Posting is free by default; a board only charges once you set a per-board credit cost. → Learn more

Multi-Board Engine

Run multiple independent job boards from one WordPress install. Each board has its own jobs, employers, and settings. A Board Switcher block lets visitors tab between boards. → Learn more

Job Alerts

Candidates subscribe to saved searches and receive email digests when new matching jobs are posted. Frequency controls (daily, weekly, instant) per alert. → Learn more

Job Map

Display job locations on an interactive map alongside your listings. Syncs with the search/filter state - filtering jobs also filters the map pins. → Learn more

AI Hiring Tools

Optional AI features that work with OpenAI, Anthropic Claude, or self-hosted Ollama. Candidates get natural-language AI Chat Search, "Recommended for you" job matches, and an AI cover-letter writer in the apply panel. Employers get one-click applicant ranking with 0-100 fit scores, reasons, and TL;DR candidate summaries, plus an AI Description Writer on the post-a-job form. Analysis and embedding providers are chosen independently, each with its own key and model. → Learn more

Find Resumes (Candidate Archive)

A public-facing resume archive block. Employers and admins can browse publicly listed candidate profiles filtered by skills, location, and job title. Resume visibility is configurable (public to anyone, or logged-in members only) under Settings > Resumes.

Pro Blocks Reference

WP Career Board Pro adds 16 blocks to the WordPress block inserter on top of the blocks included in the free plugin. Each Pro block also has a matching shortcode for page builders. → Full block reference

Requirement: Free Plugin

WP Career Board Pro requires the free WP Career Board plugin to be installed and active. Pro adds modules on top of Free - it does not work standalone.

If you haven't installed the free plugin yet, start here.

Licensing

WP Career Board Pro is sold with an annual license. License tiers:

Tier Sites
Single Site 1 site
Business 5 sites
Agency Unlimited sites

Your license includes updates and support for the license period. Renew to continue receiving updates.

Installing WP Career Board Pro

WP Career Board Pro is an add-on plugin. It requires WP Career Board (free) to be installed and activated first.

Prerequisites

Before installing Pro:

  1. ✅ WP Career Board (free) is installed and activated
  2. ✅ WordPress 6.9 or higher
  3. ✅ PHP 8.1 or higher
  4. ✅ You have a valid Pro license from wbcomdesigns.com

Installation Steps

  1. Log in to your account at wbcomdesigns.com
  2. Go to My Account → Downloads
  3. Download wp-career-board-pro.zip
  4. In your WordPress admin, go to Plugins → Add New → Upload Plugin
  5. Select the downloaded zip file and click Install Now
  6. Click Activate Plugin

Pro Plugin Upload

What Happens on Activation

On activation, WP Career Board Pro:

  • Checks that WP Career Board (free) is active - if not, activation is blocked with an error message
  • Creates the additional Pro database tables (wcb_credit_ledger, wcb_field_groups, wcb_field_definitions, wcb_field_values, wcb_job_boards, wcb_job_alerts, wcb_application_stages, wcb_ai_vectors, wcb_notifications)
  • Registers all 16 Pro blocks in the block editor (each with a matching shortcode for page builders)
  • Adds Pro settings tabs to WP Career Board → Settings

After Activation

You will see a notice asking you to activate your license. See License Activation for the next step.

Troubleshooting Installation

"WP Career Board (Free) must be installed and active" Install and activate the free plugin first, then retry Pro activation.

"License could not be verified" Make sure your site can make outbound HTTP requests. Check with your hosting provider if firewalled.

Pro tabs not appearing in Settings Deactivate and reactivate Pro. If the issue persists, check WP Career Board → Settings → System Status for errors.

License Activation

Activating your license connects WP Career Board Pro to wbcomdesigns.com for updates and support.

Where to Find Your License Key

  1. Log in to your account at wbcomdesigns.com
  2. Go to My Account → Licenses
  3. Copy the license key for WP Career Board Pro

Primary Method: Pro Setup Wizard

After activating the Pro plugin, a notice appears on all WP Career Board admin screens:

"WP Career Board Pro needs setup - configure your license and credits."

Click Complete Pro Setup to open the Pro Setup Wizard. The wizard walks you through:

  1. License step - paste your license key and click Activate & Continue
  2. Credits Setup step - configure your low-credit threshold and purchase URL
  3. Pro Pages step (mini-wizard only) - creates the Find Candidates page if missing

The wizard is the recommended activation path, especially on new installs.

Alternative: Settings Page

You can also activate the license directly without the wizard:

  1. In your WordPress admin, go to WP Career Board → Settings → License
  2. Paste your license key in the License Key field
  3. Click Activate License

License Settings - Activate

A confirmation message shows your license status, expiry date, and how many sites are active on this license.

License Statuses

Status Meaning
Active Valid license, updates available
Expired License period ended - plugin still works but no updates
Inactive Key entered but not activated on this site
Invalid Key does not match any license in our system
No activations left All license sites are used - deactivate from another site first

Managing Sites on Your License

To add your license to another site, deactivate it from the current site first:

  1. Go to WP Career Board → Settings → License
  2. Click Deactivate License
  3. Activate on the new site

You can also manage all site activations from your account at wbcomdesigns.com.

Renewing Your License

When your license expires, WP Career Board Pro continues to work - you just won't receive plugin updates. To renew:

  1. Log in to wbcomdesigns.com
  2. Go to My Account → Licenses
  3. Click Renew next to WP Career Board Pro

After renewal, the plugin automatically picks up the new expiry date on the next license check.

Updates Without an Active License

The plugin functions fully without an active license. License activation is only required to:

  • Receive automatic plugin updates
  • Access priority support

What's New in 1.7.0

WP Career Board Pro 1.7.0 adds a native Buy Credits checkout (Stripe and PayPal, no e-commerce plugin required), mobile push notifications, and an admin Transactions view over the credit ledger. Install it alongside WP Career Board (Free) 1.7.0 - the two plugins ship in lockstep and their versions must match.

1.7.0

Native credit checkout, mobile push notifications, and a large-database performance and money-safety pass.

  • New - Native Buy Credits purchase panel with Stripe and PayPal checkout and a webhook-confirmed return experience. See Native Buy Credits Checkout.
  • New - Credits admin with a packs editor, custom-amount purchasing, and payment-method toggles.
  • New - Native mobile push notifications for companion apps.
  • New - Admin Transactions view over the credit ledger, paginated and filterable, with an inline-confirm refund.
  • New - Pipeline stage manager to create, rename, recolor, reorder, and delete stages from the Boards tab.
  • New - Server-side content filtering for resumes: a candidate an employer has blocked (or who has blocked the employer) no longer appears in the candidate directory or single-resume view for that viewer.
  • Improve - Kanban boards paginate per column with a true count and Load more, replacing the flat 1000-row pull.
  • Improve - Field Builder fields can be reordered with per-field Move up and Move down controls.
  • Improve - Imported jobs are geocoded and can notify matching candidates.
  • Improve - AI ranking runs asynchronously instead of making synchronous language-model calls during a request.
  • Fix - The legacy Credits, AI Settings, Job Feed, and Field Builder admin URLs redirect to their Settings tab instead of returning a permissions error.
  • Security - The append-only credit ledger is never deleted when a user is removed.
  • Security - Resume privacy is enforced and AI applicant listings are scoped to the requesting employer.
  • Compat - Requires WP Career Board 1.7.0. Install both updates together.

1.6.0

Translation-ready release adding an analytics dashboard and a result-aware job map.

  • New - Analytics dashboard tab summarising job-board activity. See Analytics Overview.
  • New - The job map now narrows its pins to the current job-listings results, so the map matches the filtered list.
  • New - Full translation readiness with bundled German, French, Spanish, Dutch and Korean translations; credit balances now display in the site's locale format.
  • Improve - CSV job import is now idempotent - re-importing the same file updates existing jobs instead of creating duplicates, and the admin notice reports Imported, Updated, Skipped and Warnings.
  • Fix - Pro notification emails now render their message body, honouring the configurable body text from the free plugin.
  • Compat - Aligned with WP Career Board 1.6.0. Install both updates together.

1.5.0

  • Improve - Migrated the Pro block and admin colours onto the shared canonical token namespace used by the free plugin.
  • Improve - The resume form now adopts the active theme accent colour instead of a fixed one.
  • Fix - AI Chat Search now honours the typed query and renders the matched jobs, instead of ignoring the search text and showing nothing.
  • Compat - Aligned with WP Career Board 1.5.0. Install both updates together.

1.4.6

  • Improve - Custom fields are defined once and apply to every board. You no longer recreate the same fields for each board, and there is no board picker to choose first. Company fields are now global, matching candidate and resume fields.
  • New - On the Job Fields tab, each field group has a "Hide on boards" control to opt that group out of specific boards. It uses a type-ahead token field, so it stays compact whether you have one board or fifty.

1.4.5

  • New - Years of experience and Open to Work fields are now in the full resume builder and the resume form, not just the simple profile form, so candidates who use any editor feed the Find Candidates Experience and Open-to-Work filters.
  • New - Field Builder choices editor: configure the options for dropdown, multi-select, radio and checkbox custom fields directly in the builder.
  • Fix - Editing a custom field now keeps its configured choices (the update path dropped them).
  • Fix - Field Builder custom fields render and save end to end for every type - multi-select, date range, salary range, video URL, file link, location and repeater (one entry per line).
  • Fix - Skills added in the resume builder appear in the candidate directory skill filter right away.

1.4.4

  • New - Candidate experience filter on the Find Candidates directory. Visitors can filter candidates by experience level; the bands are derived from each candidate's years of experience and are customizable via the wcbp_resume_experience_bands filter.
  • Improve - Resume proficiency is now a single filterable scale. The builder dropdown and the resume bar both derive from one source (wcbp_resume_proficiency_scale), so a level you add appears on both input and output.
  • Fix - Resume proficiency no longer shows a phantom level. A value with no defined weight renders no bar instead of a misleading one.
  • Fix - The candidate directory "Clear filters" button now clears hero and URL filters (skill, experience, search, open-to-work) instead of leaving the archive filtered with no way out.

1.4.3

  • New - BuddyPress Group Job Boards are now opt-in. A "Group Job Boards" toggle under Settings -> Integrations -> BuddyPress (off by default) turns each group into its own job board; enabling it adds your existing groups as boards in the background, and turning it off keeps the boards already created. See Group-scoped job boards.
  • Improve - WP Career Board Pro adapts to BuddyX and BuddyX Pro 5.1 light and dark color modes (avatars, dashboards, buttons, and widgets re-color correctly in dark mode).
  • Fix - Career Board notifications now appear in the theme notification bell (Reign, BuddyX) instead of only in the dashboard tabs. They were saved but never registered with the theme's notification component, so the header bell stayed empty. See Notifications.
  • Dev - New wcb_notification_created action so a central notification center (such as BuddyNext) can mirror Career Board notifications.

1.4.0 - AI hiring tools

The 1.4.0 release was the big AI release. If you are upgrading from an older version, this is where most of the new functionality landed. Full detail lives in the AI Features guide.

Per-task AI providers and models

AI providers are now chosen per task. Pick an analysis & ranking provider (Anthropic Claude on Sonnet, OpenAI, or Ollama) and an embedding/matching provider (OpenAI or Ollama) independently, each with its own API key. Each provider also exposes a model selector so you can trade quality for cost: Claude (Haiku / Sonnet / Opus), OpenAI (GPT-4o mini / GPT-4o plus an embedding model), and Ollama (model names). Configure under Settings > AI Settings.

The legacy single-provider options (wcbp_ai_provider, wcbp_ai_api_key, wcbp_ai_base_url) were removed.

AI applicant ranking, TL;DR summaries, and cover letters

  • Applicant ranking - on the Employer Dashboard, rank a job's applicants by AI fit with one click. Each applicant gets a 0-100 fit score and a short reason, sorted best-first.
  • Applicant TL;DR - alongside the fit score, each applicant gets a one or two sentence AI summary of their background, shown in the Employer Dashboard list and detail.
  • Cover-letter writer - candidates generate a tailored cover letter from their resume and the job in the apply panel, then edit it before submitting.
  • Auto-score on submit (optional) - new applications are scored in the background so the Employer Dashboard shows AI fit instantly.

Fit scores, reasons, and summaries are cached per application, so re-opening the dashboard never re-bills the model.

AI candidate matching and indexing

Candidates see a "Recommended for you" set of jobs matched to their resume, and an AI Job Search page is created for natural-language search. This needs an embedding provider (OpenAI or Ollama). An Index existing jobs button on AI Settings backfills embeddings for jobs that existed before AI was enabled, so matching works on your current catalog.

Custom resume fields and resume privacy

  • A Resume Fields tab in the Field Builder lets admins add their own resume fields with the same builder used for job and company fields. The Field Builder now also edits existing fields and renames groups across all entity types.
  • Resume privacy controls (Settings > Resumes) set the candidate directory and single resume profiles to "Public - anyone" or "Logged-in members only". All resume options now live together under Settings > Resumes.

Security and reliability fixes

  • AI and map provider API keys are never written back into the settings page HTML. Key fields render empty with a "saved" indicator and update only when you enter a new value.
  • Applying with a Resume Builder resume that has no PDF now generates the PDF automatically and attaches it.
  • Releasing a credit hold on approval now returns the actual amount held, not the final charge, so the employer is never left a credit short.

Earlier releases

  • 1.4.2 - Removed two orphaned, unreachable surfaces (the credit-package catalog and a dead Kanban tags field). Credit purchases run through your active adapter (WooCommerce, PMPro, MemberPress) configured under Settings > Credits.
  • 1.4.1 - Build and packaging hardening only. Map blocks bundle the Leaflet library instead of loading it from a CDN.
  • 1.3.0 - Credits became opt-in: posting a job is free by default, and a board only charges credits once you set a per-board credit cost.
  • 1.2.0 - First public Pro release after 1.0.x. Added the single-page resume form, BuddyPress group-scoped job boards (with activity stream, notifications, and group-scoped moderation), member directory filters, and tiered credit pricing. See BuddyPress Integration.

For developers

Full reference lives in the developer guide:

Notable AI extension points added in 1.4.x:

  • wcbp_ai_candidate_matches - filter the enriched candidate-to-job match cards.
  • wcbp_ai_ranked_applications - filter the AI-ranked application list.
  • wcbp_ai_provider_drivers - register a 4th AI provider driver.
  • wcbp_ai_provider_requires_api_key - control whether a provider needs a key.
  • wcbp_ai_score_application (action) - background scoring event for one application.
  • wcbp_ai_auto_rank (option) - enables auto-scoring on application submit.

Credit System

WooCommerce, PMPro, MemberPress, and Stripe credit packages for employers.

Credit System - Overview

The Credit System lets you charge employers credits to post jobs on your board. You sell credit packages, employers buy credits, and credits are deducted when they post a job.

Credit System - Employer Dashboard Credits

How It Works

  1. Admin sets prices - configure how many credits a job post costs (per-board) and what featured-upgrades cost. Posting is free by default; a board only charges credits once you give it a per-board cost (opt-in since 1.3.0).
  2. Admin maps a purchase channel - connect a WooCommerce product, PMPro level, MemberPress membership, or a direct Stripe/PayPal checkout to a credit amount.
  3. Employer buys credits - pays via the configured adapter (WooCommerce checkout, PMPro signup, or MemberPress membership) or a direct payment gateway (Stripe Checkout / PayPal). No single-processor lock-in.
  4. Credits are held on post - when the employer submits a job to a paid board, the required credits are held.
  5. Credits are deducted - when the job goes live (after approval), credits are deducted from the employer's balance.
  6. Refund on rejection - if a job is rejected by the admin, held credits are released back to the employer.

Setup is a two-step pair

The Buy Credits button on the Credit Balance block only renders when both halves of the setup are in place at the same time:

  1. A Credits Purchase URL pointing at the WooCommerce product / PMPro level / MemberPress membership.
  2. At least one Credit Mapping entry mapping that purchase target to a positive credit amount.

If only one half is set - typically a Purchase URL with no mapping - an employer who completes the order would receive zero credits because the adapter has no record of what to top up. To prevent that, the Buy Credits button stays hidden until both halves match, and the Credits admin tab shows a yellow banner pointing at the missing half (1.2.0+):

Credits admin warning when mapping is missing

Add a mapping in the Credit Mappings section below the warning to unblock the button. Removing the Purchase URL clears the warning too (and hides the Buy Credits button entirely until the URL is set again).

Two consumers

Pro registers two distinct credit consumers with the Credits SDK - each can charge different amounts for different actions:

Consumer What it covers Cost source
job_post Posting a new job to a paid board Per-board credit cost setting (filtered through wcbp_consumer_cost)
featured_upgrade Upgrading an existing job to Featured wcbp_featured_upgrade_cost option (default 10), filtered through wcbp_consumer_cost

Both consumers run their cost callback through the tiered pricing matrix so paid-membership levels can post for less (or for free). The featured_upgrade consumer is wired to the wcb_featured_upgrade_requested / _completed / _failed hooks - see Featured-upgrade consumer for how to trigger it.

Wbcom Credits SDK

The credit system is built on the Wbcom Credits SDK - a shared library bundled inside WP Career Board Pro. The SDK provides the ledger, balance calculations, and adapter integrations. You do not need to install the SDK separately.

Credit Ledger

Every credit transaction is logged in an append-only ledger. This means:

  • You always have a full audit trail
  • No credit entry is ever edited or deleted
  • Balance is calculated as the sum of all transactions

Transaction types:

Type Effect
Top-up Credits added (from a purchase)
Hold Credits reserved (job submitted, awaiting approval)
Deduct Credits consumed (job approved and live)
Refund Credits returned (job rejected or cancelled)

Employer Experience

Employers see their credit balance in the Employer Dashboard header. When they submit a job to a paid board and don't have enough credits, the request is rejected before the job is created and they are prompted to buy credits - the purchase flow runs through whichever channel you've configured (a WooCommerce / membership product, or a direct Stripe / PayPal checkout).

The same balance + recent-history widget can be embedded on any page using the [wcbp_credit_balance] shortcode or the Credit Balance block (#blocks-reference-wcbp)).

Free Job Posts

Leaving a board's credit cost at 0 (the default) keeps job posting free. The credit system stays installed but no credits are consumed, so employers don't need to buy anything. Use this to run free posting with the infrastructure already in place to monetize later - just set a board cost when you're ready.

Where to Next

Direct Payment Gateways (Stripe & PayPal)

Direct payment gateways let you sell credits straight through Stripe Checkout or PayPal without running a separate e-commerce plugin. The Wbcom Credits SDK that powers the credit system bundles both gateways, so all you do is paste your keys and a webhook URL.

History: the 1.0.x releases shipped a built-in Stripe integration with its own API-key fields and a /wcb/v1/stripe/webhook endpoint. That was removed in 1.1.0 - the old Stripe options are deleted automatically on upgrade. Since 1.2.0 the credit system uses the Wbcom Credits SDK, which exposes Stripe and PayPal as Direct Payment Gateways under a single webhook namespace. This guide covers the current (1.2.0+) gateway model. If you already sell through WooCommerce or a membership plugin, use the adapter guides instead - you do not need a direct gateway as well.

When to use a direct gateway

  • You do not run WooCommerce or a membership plugin and don't want to install one just to sell credits.
  • You want a one-click hosted checkout (Stripe Checkout or PayPal) where cardholder data never touches your site.

If you already have WooCommerce, PMPro, or MemberPress, the adapter path is simpler - skip this page.

Where the settings live

  1. Go to WP Career Board -> Settings -> Credits.
  2. Scroll to the Direct Payment Gateways card.
  3. Expand the Stripe or PayPal block.

Each gateway block renders inline on this card - there is no separate Stripe tab and no publishable/secret-key field outside this block. All gateway settings save to a single option (wbcom_credits_gateway_settings_wp-career-board).

Stripe

Stripe uses Stripe Checkout (hosted by Stripe), so cardholder data never reaches your server.

Fields

Field Notes
Enable Stripe Turns the gateway on. Until this is on and a secret key is set, Stripe is reported as unavailable.
Mode Test or Live.
Publishable key (active mode) From Stripe -> Developers -> API keys.
Secret key (active mode) From the same screen.
Webhook signing secret The whsec_... value Stripe shows after you add the webhook endpoint (below).
Post-purchase redirect Where the buyer lands after a successful payment. Optional - defaults to your site home with a success flag.
Cancel-redirect URL Where the buyer lands if they cancel. Optional.

Webhook

The Credits card shows the exact webhook URL to paste into Stripe. It looks like this:

https://yourdomain.com/wp-json/wbcom-credits/v1/wp-career-board/webhook/stripe

In your Stripe Dashboard go to Developers -> Webhooks -> Add endpoint, paste that URL, and subscribe to exactly these two events:

  • checkout.session.completed
  • charge.refunded

Copy the endpoint's signing secret (whsec_...) into the Webhook signing secret field. Without the webhook, employers can pay but never receive credits, and refunds will not credit back.

PayPal

Fields

Field Notes
Enable PayPal Turns the gateway on.
Client ID From developer.paypal.com -> Apps & Credentials (Merchant REST app).
Client secret From the same app.
Webhook ID The ID of the webhook you create in the PayPal app settings (below).

Account checklist

  1. Create a Merchant REST app at developer.paypal.com -> Apps & Credentials. Copy the Client ID and secret into the fields above.
  2. In App settings -> Features, tick Accept payments and Refund. No other scopes are needed.
  3. In App settings -> Webhooks, add a webhook endpoint and copy its Webhook ID into the Webhook ID field.

Webhook

The Credits card shows the PayPal webhook URL:

https://yourdomain.com/wp-json/wbcom-credits/v1/wp-career-board/webhook/paypal

Subscribe the webhook to exactly these two events:

  • PAYMENT.CAPTURE.COMPLETED
  • PAYMENT.CAPTURE.REFUNDED

How a purchase flows

  1. The buyer starts a credit checkout. The SDK calls the gateway and redirects to the hosted checkout page (Stripe Checkout or PayPal).
  2. After payment, the gateway sends a webhook to the URL above.
  3. The SDK verifies the signature, cross-checks the amount, and writes a topup row to the credit ledger - the balance updates automatically.
  4. A refund issued from the provider dashboard fires the refund event, and the SDK writes a matching deduct row, returning the credits.

Every webhook is processed once. Duplicate deliveries do not double-credit.

Testing

For Stripe, set Mode to Test, use test-mode keys, and pay with Stripe's test card 4242 4242 4242 4242 (any future expiry, any CVC). Confirm a topup row appears in the employer's credit ledger.

For PayPal, use a sandbox app and a sandbox buyer account.

Note: keep test and live credentials separate. Switching Mode to Live (Stripe) or swapping in live app credentials (PayPal) makes the next purchase a real charge.

Credit Packages & Costs

This page covers the two halves of credit pricing: what employers pay you for credits (packages) and what posting a job costs them in credits.

Changed in 1.4.2: earlier builds shipped a standalone "Credit Packages" admin screen (a custom post type with its own editor and a /credits/packages REST endpoint). That screen was never wired into the purchase flow, so it was removed. Credits are now sold through your active purchase channel - a WooCommerce product, a membership level, or a direct Stripe/PayPal checkout - and "package" simply means the product or level you map to a credit amount. There is no longer a "Credit Packages" menu in wp-admin.

How credit packages work now

A credit package is whatever you sell that grants credits:

You connect each product or level to a credit amount in WP Career Board -> Settings -> Credits -> Credit Mappings. That mapping is what turns a completed purchase into a topup on the employer's ledger.

Example pricing structure

A typical tiered set of WooCommerce products:

Product Credits Price Per-Credit Cost
Starter 3 credits $29 $9.67
Growth 10 credits $79 $7.90
Agency 25 credits $149 $5.96

Bulk packages offer better value, which encourages larger purchases. Create one product per tier, then add one Credit Mapping per product.

Credits admin: packs editor & custom-amount purchasing

Since 1.7.0, WP Career Board -> Settings -> Credits has a Credit packs & pricing section that defines what employers can buy through the native Stripe/PayPal checkout - separate from the product-mapping packages described above.

  • Fixed packs - add one or more packs, each with a credit amount and a price. These render as selectable pack cards in the Buy Credits panel (for example "10 credits - $29", "50 credits - $99").
  • Custom amount - optionally let employers type any number of credits (bounded by a minimum and maximum you set) and pay a rate you configure per credit, instead of picking a fixed pack.
  • Currency - set once for the packs and the custom-amount rate.

You can offer packs, a custom amount, or both at the same time. This pricing only feeds the native checkout panel - it has no effect on the purchase-URL / product-mapping flow described above, which keeps its own per-product credit mapping.

See Native Buy Credits Checkout for how this pricing appears to employers and how to turn native checkout on.

Setting the job-post cost

Credits are opt-in (since 1.3.0). Posting a job is free by default; a board only charges credits once you give it a per-board cost.

  1. Go to WP Career Board -> Job Boards and edit a board.
  2. Set its Credit cost (the number of credits a job post deducts on that board).
  3. Save.

The cost is per board, so different boards can charge different amounts. The posting gate and the credit hold both read this same value, so what an employer is quoted is exactly what is held.

A premium board (credit cost greater than 0) automatically marks its jobs as Featured.

Republishing an expired job

Republishing an expired job back onto a paid board charges credits again, just like the first post: the board's credit cost is held and then deducted from the employer's balance, and the move is recorded in the ledger. Republishing to a free board (cost 0) charges nothing. Developers can change the republish cost - for example to run a "renew cheaper than re-post" price - through the wcb_job_republish_credit_cost filter, which defaults to the board's normal cost.

Tiered pricing by membership

The base per-board cost can be discounted (or waived) per BuddyPress member type, PMPro level, or MemberPress membership using the tiered credit pricing matrix. The cheapest applicable tier wins, so a paid membership can post for less - or for free.

Free job posting

To keep posting free, leave the board credit cost at 0 (the default). The credit system stays installed, so you can switch a board to paid later just by setting a cost - no migration needed.

Employer credit balance

Employers see their current credit balance in:

  • The Employer Dashboard header.
  • The Credit Balance block / [wcbp_credit_balance] shortcode on any page.

When the balance is too low to post to a paid board, the posting request is rejected before the job is inserted, and the dashboard shows a Buy Credits button (when a purchase URL and a mapping are both set - see Overview).

Granting credits manually

Admins can add (or remove) credits for any employer from WP Career Board -> Settings -> Credits -> Admin Credit Adjustment - see Manual adjustments & refunds.

Viewing the credit ledger

Every top-up, hold, deduction, and refund is recorded in an append-only ledger. An employer's recent history shows in the Credit Balance block (Transaction History panel) and is available over the REST API at GET /wcb/v1/employers/{id}/credits.

Selling Credits Through WooCommerce

This is the most common credit-purchase setup. WooCommerce handles payment processing (with its own Stripe, PayPal, Square, or any other WC gateway), and WP Career Board Pro listens for completed orders and credits the employer's balance automatically.

Use this guide when:

  • You already have a WooCommerce store on your site.
  • You want employers to buy credit packs the same way they would buy any other product (cart, checkout, account orders page).
  • You want to sell recurring credits (e.g. "10 credits per month") - that uses WooCommerce Subscriptions, covered below.

Prerequisites

  • WooCommerce is installed and active.
  • (Optional) WooCommerce Subscriptions is installed if you want recurring credit packs.
  • At least one WooCommerce payment gateway is configured (WooCommerce → Settings → Payments).
  • The credit system is active automatically (it is part of Pro). To actually charge for posting, set a per-board credit cost - see Credit packages & costs. Selling credit packs works regardless of whether posting charges credits.

Step 1: Create a Credit Product in WooCommerce

  1. Go to Products → Add New.
  2. Set the product name (e.g. "10 Job Posting Credits").
  3. Set the product Price.
  4. Under Product Data, leave the type as Simple product (or Simple subscription for recurring credits).
  5. Optionally mark the product Virtual - credits aren't shipped.
  6. Publish the product.

Step 2: Map the Product to a Credit Amount

  1. Go to Career Board → Settings → Credits.
  2. Scroll to the Credit Mappings section.
  3. In the Product / Plan dropdown, pick the product you just created. Products are grouped by source, so WooCommerce products appear under a "WooCommerce" heading. (If WooCommerce is not active, it shows as "not installed" in the adapter list above and its products won't appear here.)
  4. Enter the number of Credits (e.g. 10).
  5. Click + Add Mapping.

The new row appears in the mappings table (Provider / Product / Plan / Credits). Repeat for as many credit packs as you want to offer (10, 50, 100, …).

Step 3: Set the "Buy Credits" Page URL

When an employer is low on credits, the dashboard shows a Buy Credits button. Point that button at your WooCommerce shop:

  1. Career Board → Settings → Credits → Credit Purchase URL.
  2. Set it to the URL of either:
    • A single product page (e.g. /product/10-job-posting-credits/)
    • A category page that lists every credit pack
    • A custom "Buy Credits" page you've built with a product grid
  3. Save Changes.

Leave it empty if you don't want the "Buy Credits" button to appear.

Step 4: Test the Flow

  1. Log in as an employer (or use a test account).
  2. Go to Career Board → Employer Dashboard.
  3. Note the current credit balance.
  4. Click Buy Credits, complete the WooCommerce checkout.
  5. Once the order status moves to Completed, return to the dashboard.
  6. The balance updates automatically - the credit-purchase row appears in the credit ledger with type: topup.

Recurring Credits With WooCommerce Subscriptions

If you want monthly credit packs (e.g. "20 credits/month"):

  1. Install WooCommerce Subscriptions.
  2. Create the product as a Simple subscription.
  3. Map it the same way under Credit Mappings - the subscription product appears under the "WooCommerce Subscriptions" group in the Product / Plan dropdown.
  4. Set the credit amount that gets added on every successful renewal.

When the subscription renews, the same topup ledger entry is written - same flow, just on a schedule.

How It Looks in the Ledger

Every credit transaction is recorded in an append-only ledger. An employer's recent history is shown in the Credit Balance block (Transaction History panel) and over the REST API at GET /wcb/v1/employers/{id}/credits:

Type Trigger Notes
topup WooCommerce order completed Credit amount from the product mapping
hold Employer submitted a job to a paid board Reserves the cost; awaiting approval
deduct Job approved + published Final consumption
refund Job rejected by admin Returns the held credits

The ledger is the source of truth - even if a customer disputes later, you can prove exactly what happened.

Troubleshooting

Order completed but credits didn't add. On Career Board → Settings → Credits, the adapter list at the top of the Credit Mappings area shows each source as active or not installed. If WooCommerce shows "not installed" but Woo is active, deactivate and reactivate Career Board Pro to re-trigger registration. Also confirm the purchased product has a mapping row.

Credits added twice for the same order. The ledger is idempotent on order ID - duplicate webhook deliveries do not double-credit. If you genuinely see two topup rows for the same order, file a bug - that's a real failure mode and we want to catch it.

Employer bought credits before the product was mapped. The order won't credit them automatically. Use Manual Adjustments (see 08-manual-adjustments-and-refunds.md) to add the credits by hand.

Granting Credits via Paid Memberships Pro

Use this when you want employers to subscribe to a membership level that includes job-posting credits - instead of buying credit packs one at a time.

Use this guide when:

  • You already use Paid Memberships Pro to gate access on your site.
  • You want a "Gold tier: 100 credits/month, Silver tier: 25/month" pricing structure.
  • You want to grant credits automatically on a membership level upgrade (or take them away on downgrade).

Prerequisites

  • Paid Memberships Pro is installed and active.
  • You've created at least one Membership Level (PMPro → Membership Levels).
  • The credit system is active automatically (it ships with Pro). There is no separate "enable credits" toggle.

Step 1: Map a Level to a Credit Amount

  1. Go to Career Board → Settings → Credits.
  2. Scroll to Credit Mappings. In the Product / Plan dropdown, pick your membership level - PMPro levels appear under the "Paid Memberships Pro" group. (If PMPro shows "not installed" in the adapter list above, its levels won't appear here.)
  3. Enter the credit amount granted on every renewal (e.g. 100).
  4. Click + Add Mapping.

Repeat for each tier you want to offer.

Step 2: How the Grant Works

  • On first checkout - when a member completes payment for a level, the mapped credit amount is added immediately (one topup ledger row).
  • On renewal - for recurring levels, every renewal adds the same credits (a fresh topup row each cycle). Credits do NOT roll over by default - see "Rolling vs Resetting" below.
  • On cancellation / expiry - no automatic deduction. The member keeps whatever balance they had. If you need to claw credits back on expiry, do it with a manual adjustment.

Step 3: Test the Flow

  1. Go to PMPro → Membership Levels → choose your mapped level → Test Checkout.
  2. Use a test card (or a free level with a temporary discount code).
  3. Complete the checkout.
  4. As that test user, visit Career Board → Employer Dashboard.
  5. The credit balance shows the mapped amount.
  6. The Credit Balance block (Transaction History) shows a topup row for the granted amount.

Credits accumulate

Credits accumulate - if Gold gives 100/month and the employer doesn't post any jobs, after 3 months they have 300 credits. The credit system has no automatic "reset to allotment on renewal" option. If you need to reset a balance, use a manual adjustment (deduct the leftover, or top up to the target).

Common Patterns

Free trial: 5 credits, then 100/month on upgrade.

  1. Create a "Free Trial" PMPro level (price $0).
  2. Map it to 5 credits.
  3. Create your paid level ("Employer Gold").
  4. Map it to 100 credits.

When the trial member upgrades, they get 5 + 100 = 105 (credits accumulate). If you'd rather they end up with only 100, deduct the leftover 5 with a manual adjustment.

Annual plans.

PMPro supports annual recurring levels - the adapter listens for the renewal payment event regardless of cycle length, so the mapping works the same way for monthly or yearly plans.

Troubleshooting

Member subscribed but no credits added. On Career Board → Settings → Credits, the adapter list shows Paid Memberships Pro as active or "not installed". If it shows "not installed" while PMPro is active, deactivate and reactivate Career Board Pro. Also confirm the level has a mapping row.

Test transactions don't add credits. The adapter grants on the pmpro_after_change_membership_level hook (initial checkout / level change) and pmpro_subscription_payment_completed (renewals). Test modes that don't fire those hooks won't trigger credits. Use PMPro's "Test Checkout" link instead.

Renewal didn't add credits. Renewals grant on pmpro_subscription_payment_completed. If the renewal isn't logged in PMPro (PMPro → Memberships → Reports), the credit grant won't fire either - investigate the gateway first.

Granting Credits via MemberPress

The MemberPress adapter works the same way as PMPro - a member buys a membership product, the membership grants a credit amount, every renewal tops up again.

Use this guide when:

  • You use MemberPress to manage members and access rules.
  • You want tiered credit allotments based on membership level.
  • You want recurring credit grants tied to MemberPress renewals.

Prerequisites

  • MemberPress is installed and active.
  • You've created at least one Membership (MemberPress → Memberships).
  • The credit system is active automatically (it ships with Pro). There is no separate "enable credits" toggle.

Step 1: Map a Membership to Credits

  1. Go to Career Board → Settings → Credits.
  2. Scroll to Credit Mappings. In the Product / Plan dropdown, pick your membership - MemberPress memberships appear under the "MemberPress" group. (If MemberPress shows "not installed" in the adapter list above, its memberships won't appear here.)
  3. Enter the credit amount granted per cycle (e.g. 50).
  4. Click + Add Mapping.

Step 2: How the Grant Works

  • On signup - the mepr_event_transaction_completed event fires; the adapter writes a topup ledger row for the mapped amount.
  • On renewal - the same event fires for recurring memberships; another topup row each cycle.
  • On cancellation / expiry - no automatic deduction. The member keeps the balance they had. To claw credits back, use a manual adjustment.
  • On manual transaction creation (via MemberPress admin) - the same event fires, same grant happens.

Step 3: Test the Flow

  1. MemberPress → Memberships → choose your mapped membership.
  2. Use the Membership URL to subscribe with a test account.
  3. Complete checkout.
  4. As that member, visit Career Board → Employer Dashboard.
  5. Confirm the balance shows the mapped amount.
  6. The Credit Balance block (Transaction History) shows a topup row for the granted amount.

Common Patterns

Trial + paid combination.

Create two memberships: "Trial (free, 5 credits, 7 days)" and "Premium (paid recurring, 50 credits/month)". Map each. MemberPress handles the transition; the adapter responds to whichever transaction event fires.

One-time membership = one-time credit grant.

Map a non-recurring membership to a credit amount. Member buys once, gets the credits once. They keep the credits indefinitely; they just don't get more without buying again.

Reading a member's credit balance in code.

The balance is computed from the append-only credit ledger, not stored in a user-meta field. Read it programmatically with \Wbcom\Credits\Credits::get_balance( 'wp-career-board', $user_id ), or over REST at GET /wcb/v1/employers/{id}/credits. There is no _wcb_credit_balance user-meta key to query directly.

Troubleshooting

Member completed checkout but no credits. On Career Board → Settings → Credits, the adapter list shows MemberPress as active or "not installed". If it shows "not installed" while MemberPress is active, deactivate and reactivate Career Board Pro. Also confirm the membership has a mapping row.

Webhook-based payment didn't credit. The adapter grants on the mepr_event_transaction_completed event, which fires only after the payment gateway confirms. If a transaction is still "Pending" (MemberPress → Reports → Transactions), no credits fire. Investigate the gateway first.

Manual Credit Adjustments & Refunds

Sometimes the automatic flow doesn't fire - a payment processor glitches, an order was placed before you mapped the product, or a customer needs a comp credit. The Admin Credit Adjustment panel is where admins add or remove credits by hand.

Use this when:

  • You need to grant credits manually (comp, promotional, support gesture, migration).
  • You need to deduct credits manually (recoup credits after a chargeback or duplicate grant).
  • You need to return credits for a job that was rejected or disputed.

Where to find the panel

WP Career Board -> Settings -> Credits -> Admin Credit Adjustment.

It is a single card at the bottom of the Credits settings tab. There is no separate "Reports -> Credit Ledger" screen and no per-employer "Adjust" button - all manual adjustments happen from this one card.

Checking a balance first

The same card shows a balance lookup. Enter a User ID and submit to see that user's current credit balance before you adjust it.

Granting credits

  1. Open Settings -> Credits and scroll to Admin Credit Adjustment.
  2. Enter the employer's User ID.
  3. Enter the number of Credits to add.
  4. Set Type to Top-up (add).
  5. Enter a Note (the reason - for example "Comp for incident #4892" or "Migration grant").
  6. Click Apply.

A topup row is written to the ledger with your note (stored as "Admin top-up: "), and the employer's balance updates immediately.

Deducting credits

  1. Same card, but set Type to Deduct (remove).
  2. Enter the amount to remove and a note.
  3. Click Apply.

A deduct row is written with "Admin deduction: ".

Returning credits for a rejected job

Job posting uses a hold -> deduct / refund cycle, so most returns are automatic:

  • If you reject a pending job, the held credits are released back to the employer automatically (a refund row is written) - you do not need to adjust anything by hand.
  • If a job was already approved (credits deducted) and you later need to return them, use the Admin Credit Adjustment card with Type: Top-up and a clear note referencing the job.

There is no per-job "refund credits" button on the job edit screen - use the adjustment card for after-the-fact returns.

Audit trail

Every manual action writes an append-only ledger row with the credit amount, the entry type (topup / deduct), your note, and a timestamp. Ledger rows are never edited or deleted - if you make a mistake, write a reversing entry with a clear note ("Reversing earlier comp - applied to the wrong account").

This also holds when a user account is removed: the ledger is never deleted when a user is deleted. Deleting an employer erases their alerts, notifications, and AI matching data, but their ledger rows stay in place (with the user_id link anonymized) so the financial record and audit trail survive account deletion.

Exporting the ledger

To pull the whole credit ledger for accounting or an audit, use the bulk CSV export. It is a single REST download:

GET /wp-json/wcb/v1/analytics/credits.csv

The export streams every ledger row, newest first, with these columns: ID, Employer, Amount, Type, Job, Note, Date. "Employer" is the user ID and "Job" is the related job ID for that entry. The download is gated on the wcb/manage-credits ability, so only admins (and any role you have explicitly granted that ability) can fetch it. This is the path to use for a one-shot export of the entire ledger rather than reading rows one by one.

Admin Transactions view (native checkout)

Since 1.7.0, WP Career Board -> Settings -> Analytics has a Transactions card that reads every native Stripe/PayPal purchase and refund directly from the gateway log - a separate, read-only surface from the Admin Credit Adjustment card above.

  • The list is paginated (20 rows per page) and filterable by kind
    • All, Purchases, or Refunds.
  • Each row shows the date, employer, gateway, type, credits, amount, and status (Paid, Partially refunded, or Refunded).
  • A still-refundable purchase gets a Refund button. Clicking it shows an inline Confirm refund / Cancel choice before anything happens - there is no separate confirmation page or modal. Confirming calls the SDK's refund route, which issues the refund with the gateway (Stripe or PayPal) directly; the credit balance updates once the gateway confirms and sends its refund webhook.
  • Purchases and refunds made through the product-mapping / purchase-URL flow (WooCommerce, PMPro, MemberPress) do not appear here - this view is scoped to the native gateway checkout only. Use the Admin Credit Adjustment card above for those.

Refunding external payments

If a customer charges back a credit-pack purchase through their bank or the payment provider:

  1. Confirm the chargeback in WooCommerce / PMPro / MemberPress or the gateway dashboard first - that record is authoritative.
  2. Deduct the credits manually using the steps above, with a note like "Chargeback on order #X".

The credit system does not automatically deduct credits when a WooCommerce order is refunded - that is intentional, because some refunds are goodwill gestures where you want the customer to keep the credits. Direct-gateway refunds (Stripe / PayPal) are the exception: when you refund from the provider dashboard, the gateway webhook fires and the SDK writes the matching deduct automatically (#stripe-setup-wcbp)).

Permissions

The Admin Credit Adjustment card requires the wcb_manage_settings capability. Site administrators have it by default; grant it to other roles through your role manager if needed.

Troubleshooting

Adjustment applied but the dashboard balance didn't change. The employer dashboard caches the balance briefly. Either wait, or have the employer reload the dashboard.

I deducted credits and the customer disputes it. The ledger row carries your note and timestamp - that is your record. If you genuinely made an error, write a reversing topup row; never try to edit or delete the original entry.

Native Buy Credits Checkout

Since 1.7.0, the Credit Balance block includes a native "Buy Credits" purchase panel: employers pick a pack (or a custom amount), choose Stripe or PayPal, and pay without ever leaving the page. This sits alongside - not instead of - the product-mapping purchase flow described in Credit packages & costs.

How it's different from the purchase-URL flow

Purchase URL / product mapping Native checkout
Where the buyer pays Your WooCommerce / PMPro / MemberPress checkout A panel inside the Credit Balance block itself
What you configure A Credits Purchase URL + Credit Mappings Credit packs / custom-amount pricing + gateway keys
Gateways Whatever your e-commerce plugin supports Stripe Checkout and PayPal, built in

Both can be enabled at once. If native checkout isn't available (no gateway configured, or nothing priced to sell), the block falls back to the "Buy Credits" link that points at your Purchase URL.

Turning it on

Go to WP Career Board -> Settings -> Credits and:

  1. Under Payment methods, turn on Native checkout ("Allow Stripe / PayPal hosted checkout"). This is one of two independent toggles - the other, Product mapping, controls the purchase-URL flow and can stay on or off regardless of native checkout.
  2. Configure at least one gateway in the Direct Payment Gateways card further down the same tab - see Direct Payment Gateways for the Stripe and PayPal fields, keys, and webhook setup. Native checkout has no effect until a gateway is enabled and its webhook is wired up.
  3. Under Credit packs & pricing (same tab), define what you're selling - fixed packs, a custom-amount rate, or both. See Credit packages & costs for the packs editor.

The panel only appears on the frontend once all three are true: native checkout is turned on, at least one gateway reports itself available (enabled with valid keys), and there is something priced to sell (a pack or an enabled custom rate).

What the employer sees

On the Credit Balance block (or the [wcbp_credit_balance] shortcode), employers see a Buy Credits button next to their balance. Clicking it expands a panel with:

  • A payment method row listing every available gateway (Stripe, PayPal) as a selectable pill.
  • Credit pack cards - one per configured pack, showing the credit amount and price.
  • A custom amount field (when custom pricing is enabled), with the price recalculated live as the employer types, bounded by the configured minimum and maximum credits.
  • A Continue to payment button that starts the checkout once a gateway and a pack (or a valid custom amount) are selected.

Clicking Continue to payment redirects the browser to the hosted Stripe Checkout page or PayPal flow. The price is always computed server-side - nothing the browser sends is trusted for the charge amount.

The webhook-confirmed return

After the buyer pays (or cancels) on the gateway's hosted page, they land back on the same page they started from with a status flag appended to the URL:

  • Success - the panel shows "Payment received - confirming your credits..." and polls the real balance endpoint (GET /wcb/v1/employers/{id}/credits) until it reflects the top-up, or a short time passes (whichever comes first).
  • Cancel - the panel shows "Checkout canceled - you were not charged."

The redirect is UX only. The signed webhook from Stripe or PayPal is what actually credits the ledger (see Direct Payment Gateways), and it may land a moment before or after the browser redirect. The page never asserts a purchase succeeded just because the return URL says so - it always confirms against the live balance. If the employer closes the tab before the redirect completes, the webhook still credits their account once it arrives.

Application Pipeline

Kanban-style ATS pipeline with configurable hiring stages.

Application Pipeline - Overview

The Application Pipeline replaces the free version's simple three-status system (Submitted / Reviewed / Closed) with a fully customizable ATS-style stage workflow.

Application Pipeline - Kanban Board

What You Get

  • Custom stages per board - define as many stages as your hiring process needs (for example Screening, Phone Interview, Technical Test, Final Interview, Offer, Hired). Stages are scoped to each Pro job board, so different boards can run different pipelines.
  • Kanban board view - the Application Kanban block (wcb/application-kanban) renders one column per stage and lets reviewers drag applicant cards between stages. Since 1.7.0, each column loads applications a page at a time with a true count and a Load more control, so large boards stay fast to open.
  • Terminal stages - mark a stage as terminal with an outcome of "Hired" or "Rejected". When an application reaches a terminal stage, its core status is automatically set to Closed.
  • Stage badge color - each stage carries a color used for its Kanban column and badges.

How It Compares to the Free Version

Free Pro
Application statuses Submitted, Reviewed, Closed Custom stages you define, per board
Pipeline view List only Kanban board (drag-and-drop)
Terminal outcomes Closed Hired / Rejected (terminal stages auto-close to Closed)

Stages Are Created by You

Pro does not ship a pre-seeded set of stages. A new board starts with no stages, and you add the stages your process needs. A typical set is:

  1. Submitted (first stage)
  2. Screening
  3. Interview
  4. Offer
  5. Hired (terminal - outcome: Hired)
  6. Rejected (terminal - outcome: Rejected)

You can add, rename, recolor, reorder, or delete stages at any time from the Pipeline Stages manager on the Boards tab (#configure-stages-wcbp)).

How a Terminal Stage Works

When an application is moved into a stage marked as terminal:

  • Its core application status (_wcb_status) is set to closed.
  • The wcb_application_status_changed action fires so other features and any addons can react (for example sending a notification).

The terminal outcome value (hired or rejected) is stored on the stage so reporting and addons can tell the two terminal types apart.

Where to Next

Configuring Pipeline Stages

Pipeline stages are configured per job board. Each Pro board has its own set of stages, so a "Engineering" board and a "Sales" board can run completely different hiring pipelines.

Where Stages Live

Go to Career Board → Boards (also available as Settings → Boards). The board list shows each board with its current stage count and a Manage link next to that count.

A new board starts with no stages. You build the pipeline by adding the stages your process needs.

Managing Stages from the Boards Tab

Since 1.7.0, stages are managed directly from the Boards tab - no separate screen or REST client needed. Click Manage next to a board's stage count to open its Pipeline Stages panel:

  • Create - fill in a stage name and pick a color, then click Add Stage. The new stage appears at the end of the list.
  • Rename - edit a stage's label inline in the stage list.
  • Recolor - change a stage's color with the color picker on its row.
  • Reorder - use the Move up / Move down controls on each stage row to change its position; the Kanban board renders columns in that order.
  • Delete - remove a stage with the Delete button on its row (asks for confirmation first).

Every change saves immediately - there is no separate "Save" step. Click Close to collapse the panel when you are done.

Stage Attributes

Every stage stores the following:

Attribute Notes
Label The stage name shown on the Kanban column and badges (for example "Technical Test").
Color A hex color for the stage badge. Defaults to #6366f1 if you do not pick one.
Sort order Determines left-to-right column position on the Kanban board.
Terminal When on, moving an application into this stage closes the application.
Terminal outcome For terminal stages only: hired or rejected.

The REST API Behind It

The Pipeline Stages panel above is a thin UI over the Pro REST API. The same routes are available for automation and integrations:

Action REST route
List a board's stages GET /wcb/v1/boards/{id}/stages
Create a stage POST /wcb/v1/boards/{id}/stages
Update a stage (label, color, sort order) PUT /wcb/v1/boards/{id}/stages/{stage_id}
Delete a stage DELETE /wcb/v1/boards/{id}/stages/{stage_id}

To reorder stages, update their sort_order values; the Kanban board renders columns in ascending sort_order.

Deleting a board removes all of that board's stages automatically.

Stage Colors

Each stage color is used for its Kanban column and its badge. Choose colors that give instant visual meaning:

  • Green tones for positive progress (Offer, Hired)
  • Red tones for rejections
  • Blue or gray tones for neutral stages (Screening, Interview)

Terminal Stages

A terminal stage ends the hiring process for an applicant. When you move a candidate into a terminal stage, the application's core status is automatically set to Closed and the wcb_application_status_changed action fires.

The terminal outcome you set (hired or rejected) is stored on the stage so reporting and any addons can distinguish a hire from a rejection. The plugin does not, on its own, send a rejection email or increment a hire counter when a terminal stage is reached. Those behaviors can be added by hooking the wcb_application_status_changed action.

Using the Kanban Board

The Kanban board is the Application Kanban block (wcb/application-kanban). Place it on any page or dashboard where reviewers manage applications.

Each column represents a stage for the relevant board. Drag and drop applicant cards between columns to move candidates through the pipeline. Moving a card calls:

PATCH /wcb/v1/applications/{id}/stage

which records the new stage and fires the wcbp_application_stage_changed action (the trigger that powers terminal-stage closing).

Since 1.7.0, each column loads its cards a page at a time (25 applications by default) instead of pulling the whole board in one request. A column's header shows its true total count, and a Load more button appears at the bottom of any column that has additional cards beyond the current page. This keeps large boards fast to open regardless of how many applications a stage has accumulated.

Permissions

  • Viewing the Kanban board requires the wcb/view-applications ability, so reviewers and HR users who can read applications can see the pipeline.
  • Moving a card to another stage requires the wcb/moderate-jobs ability, so read-only reviewers cannot silently advance candidates.

Extending the Kanban

Addons can reshape the board through the wcbp_kanban_card_columns filter, which receives the assembled columns and the owning job ID. Use it to inject extra columns (archived, on-hold, externally sourced) or to annotate existing columns with custom metadata.

Field Builder

Add custom fields to jobs, candidates, and applications.

Custom Field Builder - Overview

The Field Builder lets you add custom fields to jobs, companies, candidate profiles, and resumes - without writing any code. It ships with WP Career Board Pro 1.4.3.

Field Builder - Admin Interface

What You Can Do

  • Add fields to job listings (e.g., Remote Policy, Visa Sponsorship, Tech Stack)
  • Add fields to company profiles (e.g., Funding Stage, Glassdoor Rating, Benefits)
  • Add fields to candidate profiles (e.g., Preferred Work Style, Notice Period, Portfolio URL)
  • Add fields to resumes (e.g., Availability, Desired Salary, Certifications)
  • Group related fields into collapsible sections
  • Mark fields as required or optional
  • Control who can see a field with a visibility setting

How It Works

Fields created in the Field Builder are:

  1. Injected into the relevant form - the matching entity form picks them up through a filter (wcb_job_form_fields, wcb_company_form_fields, wcb_candidate_form_fields, wcb_resume_form_fields).
  2. Saved with the post - job field values are written when a job is created or updated (wcb_job_created / wcb_job_updated).
  3. Returned in the API - custom job field values are appended to the job REST response (wcb_job_response), so headless front-ends and integrations can read them.
  4. Stored in a dedicated table - values are saved in WP Career Board's wcb_field_values table, keyed by post ID and field key.

No custom database columns, no custom meta box hacks.

Boards and Field Scope

Custom fields are organized per board for job and company fields, and globally for candidate and resume fields:

  • Job Fields and Company Fields belong to a specific board. Each board can have its own field schema. You select the board first, then define its fields.
  • Candidate Fields and Resume Fields are global. They are not tied to a board and apply everywhere (stored under board 0 internally).

If you have not created a board yet, the Field Builder prompts you to create one before you can add job or company fields.

Field Groups

Within each entity type, fields are organized into groups. A group is a collapsible section with a label (e.g., "Compensation", "Requirements"). You can create multiple groups per entity type.

Visibility Rules

Each field has one of three visibility settings:

  • Public - visible to everyone, including guests (the default)
  • Employer Only - visible only in employer-facing contexts
  • Admin Only - visible only to site administrators

Storage Model

The Field Builder uses three Pro tables:

Table Holds
wcb_field_groups Group definitions (board, entity type, label, sort order)
wcb_field_definitions Individual field definitions (key, type, label, visibility, required)
wcb_field_values Saved values, keyed by post ID and field key

Field group and definition reads are cached in a 5-minute transient per board and entity type. The cache is cleared automatically whenever a group or field is saved or deleted, via the wcbp_field_group_saved, wcbp_field_group_deleted, wcbp_field_definition_saved, and wcbp_field_definition_deleted hooks.

When a field value is saved, the Field Builder also fires wcbp_field_value_saved, passing the owner post ID, the field key, and the submitted value. Addons hook this to sync field values to external systems (CRMs, ATSs, analytics pipelines) without wrapping the Pro REST endpoint.

Where to Next

Creating Custom Fields

Accessing the Field Builder

Go to WP Career Board -> Settings in wp-admin, then open the Field Builder tab. The direct URL is wp-admin/admin.php?page=wcb-settings&tab=field-builder.

If you have not created a board yet, the page shows an empty state with a "Create a Board" button. Job and company fields are organized per board, so create a board first, then return to the Field Builder.

Selecting a Board

At the top of the Field Builder you choose a Board from the selector. The board you pick determines which job and company fields you are editing. Candidate fields and resume fields are global and apply regardless of the selected board.

Entity Type Tabs

Inside the builder there are four entity tabs:

  • Job Fields - fields added to job listings (per board)
  • Company Fields - fields added to company profiles (per board)
  • Candidate Fields - fields added to candidate profiles (global)
  • Resume Fields - fields added to resumes (global)

Creating a Field Group

Before adding fields, create a group to hold them:

  1. Click Add Group
  2. Enter a group label (e.g., "Basic Info" or "Compensation Details")
  3. Click Create Group

The group appears as a collapsible section. You can create multiple groups to organize related fields. Rename a group with the Rename button or remove it with Delete Group.

Adding a Field to a Group

  1. Expand the group where you want to add the field
  2. Click + Add Field
  3. Configure the field:
Setting Description
Label The label shown on the form and public page
Field Type One of 17 types - see the Field Types Reference
Visibility Public, Employer Only, or Admin Only
Required Toggle on if the field must be filled in
  1. Click Add Field

Adding a Custom Field

Note: The add and edit form collects the label, type, visibility, and required toggle. A unique field key is generated automatically by the server and stays fixed across edits, so stored values remain linked even if you rename the field.

Editing and Deleting Fields

  • Click Edit on any field to change its label, type, visibility, or required state
  • Click the delete (x) button to remove the field definition

Note: Deleting a field removes the field definition from the form. Values already stored in wcb_field_values are not deleted - they remain in the table keyed by their field key.

Field and Group Order

Each group and field has a sort_order value. New fields are added to the end of their group.

Since 1.7.0, you can reorder fields directly in the builder: every field row has Move up and Move down controls that swap it with its neighbor within the group. There is no drag-and-drop - use the two buttons to step a field into position. A POST /wcb/v1/fields/reorder REST route is also available for scripted reordering.

Where Fields Appear

After saving, fields are injected automatically into the matching form through filters:

  • Job Fields -> the job posting form (wcb_job_form_fields filter); job values are saved on wcb_job_created / wcb_job_updated and appended to the job REST response (wcb_job_response)
  • Company Fields -> the company profile form (wcb_company_form_fields filter)
  • Candidate Fields -> the candidate profile form (wcb_candidate_form_fields filter)
  • Resume Fields -> the resume form (wcb_resume_form_fields filter)

REST API Reference

The Field Builder admin UI is driven entirely by the wcb/v1 REST API. The same routes are available for automation and integrations:

Method Route Purpose
GET /wcb/v1/fields/groups List groups for a board and entity type (board_id, entity_type query params)
POST /wcb/v1/fields/groups Create a group
PUT /wcb/v1/fields/groups/{id} Update a group
DELETE /wcb/v1/fields/groups/{id} Delete a group
GET /wcb/v1/fields/groups/{group_id}/fields List fields in a group
POST /wcb/v1/fields/groups/{group_id}/fields Add a field to a group
PUT /wcb/v1/fields/{id} Update a field
DELETE /wcb/v1/fields/{id} Delete a field
POST /wcb/v1/fields/reorder Reorder fields

All field-builder routes require the wcb/manage-boards ability (granted to administrators and employers).

Field Types Reference

WP Career Board Pro 1.4.3 supports 17 field types in the Field Builder. You choose the type from the Field Type dropdown when adding or editing a field.

When you add a field you set its Label, Field Type, Visibility, and Required state. The renderer turns each type into the appropriate HTML control on the front end.

Text

A single-line text input.

Use for: Short text values - job perks, tech stack keywords, notice period, LinkedIn handle.

Textarea

A multi-line text input.

Use for: Longer descriptions - benefits overview, ideal candidate description, company culture note.

Number

A numeric input (<input type="number">).

Use for: Salary figures, years of experience required, team size.

Email

A validated email address input (<input type="email">).

Use for: Secondary contact email, recruiter contact, HR inquiry address.

URL

A validated URL input (<input type="url">).

Use for: Portfolio link, GitHub, LinkedIn, external application link.

Date

A date picker (<input type="date">).

Use for: Application deadline (a custom per-job date rather than the global expiry), estimated start date.

Date Range

Stored as a date value and currently rendered as a single date input.

Use for: A start-date style field. Reserve for project duration or contract period once a dedicated range control is in place.

A single-choice dropdown. Choices are read from the field's stored options array.

Use for: Remote policy (On-site / Hybrid / Remote), visa sponsorship (Yes / No / Case by case), seniority.

Multi-Select

A dropdown that allows multiple selections (the multiple attribute is set and values are stored as a JSON array). Choices are read from the field's stored options array.

Use for: Tech stack (JavaScript, Python, Go), required certifications, industry sectors.

Checkbox

A single on/off checkbox that stores 1 when ticked.

Use for: "Actively hiring?", "Visa sponsorship available?", "Open to relocation?"

Radio

A group of mutually exclusive radio buttons. Choices are read from the field's stored options array.

Use for: Preference questions where you want all options visible at once rather than in a dropdown.

File Upload

A file input (<input type="file">).

Use for: Attaching a job description PDF, a company one-pager, a candidate portfolio document.

Video URL

A URL input (<input type="url">).

Use for: Company culture video, candidate introduction video, job preview clip.

Location

A location field. Currently rendered as a text input.

Use for: A free-text location or place name on a job or profile.

Salary Range

A salary-range field. Currently rendered as a text input.

Use for: Capturing a salary range as text. For structured pay data, use the dedicated salary fields elsewhere in WP Career Board.

Repeater Group

A repeating-group field. Currently rendered as a text input.

Use for: Reserved for repeating sets of values. Use Text or Multi-Select for now if you need multiple entries.

Conditional

A conditional field. Currently rendered as a text input.

Use for: Reserved for fields that show based on another field's value.

Notes on Options

Select, Multi-Select, and Radio fields read their choices from the field's stored options array. Options can be set through the POST/PUT field routes of the wcb/v1 REST API (#creating-fields-wcbp)).

Notes on Rules

Alongside options, a field can carry a rules array. It is accepted on the same POST/PUT field routes of the wcb/v1 REST API and stored with the field definition (as a JSON array). rules holds the conditional logic for a field - the conditions under which it shows or hides based on another field's value - and is what the Conditional field type is built on.

Tips for Choosing Field Types

  • Use Dropdown (Select) when options are mutually exclusive and the list is short (3-8 items).
  • Use Multi-Select when multiple selections are valid and expected.
  • Use Radio when you want all options visible without a dropdown.
  • Use Text for anything that does not fit a specific type - it is the most flexible.
  • Keep required fields to a minimum - every required field reduces form completion rates.

Resume Builder

Structured resume builder, single-page form, and resume search.

Resume Builder - Overview

The Resume Builder lets candidates create structured, multi-section resumes directly on your WordPress site - no PDF uploads, no external tools.

Resume Builder - Full View

What Candidates Can Build

A resume in WP Career Board Pro is made up of sections. Each section holds structured entries:

Section What it stores
Professional Summary A free-text overview paragraph
Work Experience Job title, company, employment type, location, dates, current role flag, description, skills used
School Education School name, certificate, field of study, dates, grade, description
College / University Institution, degree type, field of study, dates, GPA, description, achievements
Skills Skill name and optional proficiency level
Languages Language name and proficiency
Certifications Certificate name, issuing body, issue date, expiry date, credential ID and URL
Portfolio / Links Label (e.g., "GitHub") and URL

Candidates can expand, collapse, and reorder entries within each section.

How Resumes Connect to Applications

When a candidate applies for a job, they can select one of their saved resumes to attach to the application. The employer sees the full structured resume - not just a PDF attachment.

Multiple Resumes

Candidates can create more than one resume (e.g., one for software roles, one for design roles). The dashboard shows all resumes with their last-updated date.

Candidates can create up to the configured maximum (default 2, set under WP Career Board → Settings → Resumes → Max Resumes Per Candidate, range 1-20). When a limit is in effect the dashboard shows a usage indicator (for example "2/2 resumes used").

Resume Visibility

Each resume is private by default - only the candidate and employers who receive an application can view it. Every resume carries a Public toggle in the builder header. Switching it on publishes the resume profile at a public /resume/{slug}/ URL and lists it in the candidate directory (the Find Candidates / Find Resumes archive built from the wcb/resume-archive block).

Site owners control directory and profile exposure under WP Career Board → Settings → Resumes:

  • Public resume profiles - master switch that makes resume profiles viewable at /resume/{slug}/.
  • Candidate directory visibility - "Public - anyone can browse" or "Logged-in members only".
  • Single resume visibility - "Public - anyone can view" or "Logged-in members only".

The candidate's per-resume Public toggle and the site owner's visibility settings combine: a resume only appears in the directory when the candidate marks it public AND the site owner allows the chosen audience to see it.

Since 1.7.0, member blocking also filters resumes server-side. When an employer and a candidate have blocked each other, that candidate's resume is hidden from the employer in the directory, the single resume view, and the REST API - and the employer's listings are hidden from the candidate in return. The filtering runs on the server, so a blocked party cannot reach the content by guessing the URL.

Where to Next

Setting Up the Resume Builder

The Resume Builder is delivered as a Gutenberg block (wcb/resume-builder). Most sites do not need to create this page by hand - the Pro setup wizard provisions the candidate-facing pages for you. The steps below cover both the automatic path and the manual one.

Quickest path: the setup wizard

When you first activate WP Career Board Pro, the setup wizard creates the candidate-facing Pro pages and maps them in settings. The wizard creates a Find Candidates page (containing the wcb/resume-search-hero and wcb/resume-archive blocks) and wires the candidate dashboard so the My Resumes tab can open the builder.

If a page is ever missing, go to WP Career Board → Settings → Resumes. When a required page is not mapped, the tab shows a Create Missing Resume Pages button that recreates and assigns it.

Manual path: add the block yourself

  1. Go to Pages → Add New
  2. Set the title (e.g., "Resume Builder" or "Edit Resume")
  3. Add the Resume Builder block to the page body (it lives under the Widgets block category)
  4. Publish the page

Adding the Resume Builder Block

The Resume Builder loads the resume named in the URL's ?resume_id= parameter, so a single page serves every candidate's builder.

Find Resumes page setting (the candidate directory)

The only resume page registered under WP Career Board → Settings → Pages is the Find Resumes Page - the public candidate directory, not the builder:

  1. Go to WP Career Board → Settings → Pages
  2. Find the Find Resumes Page field (described as "Contains the wcb/resume-archive block. Public listing of candidate resumes.")
  3. Select the page that holds the wcb/resume-archive block
  4. Click Save Changes

The builder itself has no page-mapping setting; candidates reach it from the dashboard (see below). Directory and profile visibility are configured under Settings → Resumes (see Overview → Resume Visibility).

The Candidate Dashboard → My Resumes tab automatically shows:

  • An Edit button on each resume that opens the Resume Builder with that resume loaded (via the ?resume_id= parameter)
  • A Create New Resume action

No further configuration is needed.

Optional: Embedded vs Standalone Mode

Standalone mode (default): The Resume Builder lives on its own page. Clicking "Edit" navigates to that page with a ?resume_id= query parameter.

Embedded mode: If you add the Resume Builder block on the same page as the Candidate Dashboard, the builder loads inline within the dashboard (without a page change). The block detects the resume_id parameter and loads the correct resume.

To use embedded mode, add the Resume Builder block to your Candidate Dashboard page instead of a separate page.

Configuring the Block

The Resume Builder block (wcb/resume-builder) has no configurable attributes in the editor - all customization is handled via CSS or via the field filters. To add or change sections and fields, see Custom Resume Fields.

Resume Builder - Candidate Guide

This guide explains how candidates use the Resume Builder to create and manage their resumes.

Accessing the Resume Builder

From the Candidate Dashboard:

  1. Click the My Resumes tab
  2. Click Create New Resume - enter a title (e.g., "Software Engineer Resume") and click Save
  3. Click Edit on any existing resume

The Resume Builder page opens with your resume loaded.

My Resumes Tab - Dashboard

Creating a New Resume

When you create a resume, you give it a name. This is just for your own reference - the name appears in your dashboard list and is not visible to employers.

The Resume Builder Interface

The builder is organized into collapsible sections. Click any section header to expand it.

Resume Builder - Open Sections

Professional Summary

The summary is a free-text field at the top of the builder. Write a short 2-4 sentence overview of your background and goals.

Adding Entries (Experience, Education, etc.)

  1. Open any section (e.g., Work Experience)
  2. Click Add Entry - the section opens and the new entry enters inline-edit mode
  3. Fill in the fields
  4. Click Done to collapse the entry back to a compact row

The entry appears as a compact row. Click the row to expand and edit it again. You do not click a per-entry Save button - the builder saves your changes automatically (see Saving the Resume).

Entry Fields

Work Experience:

  • Job Title (required)
  • Company (required)
  • Employment Type (e.g., Full-time, Contract)
  • Location
  • Start Date / End Date
  • "Currently working here" checkbox (hides End Date)
  • Description
  • Skills Used

School Education:

  • School Name (required)
  • Certificate
  • Field of Study
  • Start Date / End Date
  • Grade
  • Description

College / University:

  • Institution (required)
  • Degree Type (required)
  • Field of Study
  • Start Date / End Date
  • GPA
  • Description
  • Achievements

Skills:

  • Skill name (required)
  • Proficiency level (optional: Beginner, Intermediate, Advanced, Expert)

Languages:

  • Language name (required)
  • Proficiency (e.g., Native, Fluent, Conversational)

Certifications:

  • Certificate name (required)
  • Issuing organization
  • Issue date
  • Expiry date
  • Credential ID
  • Credential URL

Portfolio / Links:

  • URL (required)
  • Label (e.g., "Portfolio", "GitHub")

Removing an Entry

Click Remove on any entry. You will be asked to confirm before it's deleted.

Saving the Resume

The Resume Builder saves in two ways, so your work is never lost:

  • Autosave - adding an entry, removing an entry, or finishing an inline edit schedules a debounced autosave a moment later.
  • Save Resume button - the builder header has a Save Resume button you can click at any time to save immediately.

While a save is in progress the button reads Saving..., and a "Saved" confirmation appears once it completes. Both paths write the full resume to the same endpoint (PUT /wcb/v1/resumes/{id}).

Deleting a Resume

  1. Go to Candidate Dashboard → My Resumes
  2. Click the Delete icon on the resume you want to remove
  3. Confirm in the prompt

Deleted resumes are removed permanently.

Visibility Toggle

Each resume has a Public toggle in the builder header. When it is off (the default, "Private"), only you and the employers you apply to can see the resume. Turning it on publishes your resume profile at a public /resume/{slug}/ URL and lists it in the site's candidate directory (the Find Candidates page).

The site owner can require visitors to sign in before browsing the directory or opening a profile (Settings → Resumes), but your per-resume toggle is always the first gate - a resume you keep private never appears, regardless of site settings.

Quick Resume Form (Single-Page)

The Quick Resume Form is a single-page sibling of the multi-section resume builder. Available in WP Career Board Pro 1.4.3.

When to use it

Use the Quick Resume Form when you need a candidate to capture a "good-enough-to-apply" profile in 60 seconds:

  • At signup - drop it into the registration confirmation page so candidates land with a usable profile already filled in.
  • In a modal over job listings - when somebody clicks "Apply" but doesn't have a resume yet, surface this form instead of bouncing them to a separate page.
  • In partner-site embeds - embed via shortcode on a partner careers landing page.
  • In your sidebar - the compact attribute makes the layout dense enough for a 320px sidebar.

Keep the multi-section Resume Builder on the dedicated "Edit My Resume" dashboard page for completeness - that's where candidates fill in education, experience, certifications, languages, and portfolio over time.

What it captures

  • Headline (e.g. "Senior Backend Engineer · 8 years · open to remote")
  • Professional summary (rich text, multiline)
  • Top skills (comma-separated chips, drives matching + search facets)
  • Years of experience (dropdown)
  • Location (free text or geocoded)
  • Open-to-work toggle (#member-directory-filters-wcbp))
  • Profile photo (optional - set showPhotoField="false" to hide)

Two ways to embed

Block

Insert Resume Form (Quick Profile) from the block inserter (under Widgets). Configure attributes from the sidebar inspector: showPhotoField, compact.

Shortcode

[wcbp_resume_form_simple]
[wcbp_resume_form_simple compact="true" showPhotoField="false"]

Full attribute reference: Pro Shortcodes.

Customising the fields

The same wcb_resume_form_fields filter that drives the multi-section builder also drives the Quick Resume Form. Add custom fields once, get them on both surfaces. The filter receives two arguments - the field groups (an empty array by default) and the resume post ID - and returns the groups:

add_filter( 'wcb_resume_form_fields', function ( $groups, $resume_id ) {
    $groups[] = array(
        'id'     => 'preferences',
        'label'  => __( 'Work preferences', 'my-theme' ),
        'fields' => array(
            array(
                'key'     => '_wcb_preferred_timezone',
                'label'   => __( 'Preferred timezone', 'my-theme' ),
                'type'    => 'select',
                'options' => array(
                    'utc' => 'UTC',
                    'est' => 'EST',
                    'pst' => 'PST',
                    'ist' => 'IST',
                ),
            ),
        ),
    );
    return $groups;
}, 10, 2 );

Each group is id / label / fields; each field is key / label / type (text, textarea, email, tel, url, number, date, select, checkbox, radio, multiselect) plus optional required, placeholder, description, and options (a value => label map). The schema matches Free's wcb_job_form_fields filter - see Custom Resume Fields for the full walkthrough.

Save semantics

Saving the form upserts the candidate's wcb_resume post (creating one on first save). The same fields are reachable from the multi-section builder afterwards - candidates can complete the rest of their profile when they're ready.

There is no "publish" step on this form by design. Quick Resume Form saves keep the resume in whatever public/private state the candidate last set; the candidate flips visibility from the dashboard.

Custom Resume Fields

Add custom fields to the Resume Builder + Quick Resume Form using the same declarative filter pattern as Free's wcb_job_form_fields. A field added once renders on both forms.

The filter

The filter receives two arguments - the field groups (an empty array by default) and the resume post ID - and returns the groups. Each group is id / label / fields; each field is key / label / type plus optional required, placeholder, description, and options:

add_filter( 'wcb_resume_form_fields', function( $groups, $resume_id ) {
    $groups[] = [
        'id'     => 'links',
        'label'  => __( 'Online presence', 'wp-career-board-pro' ),
        'fields' => [
            [
                'key'      => '_wcb_resume_github_url',
                'label'    => __( 'GitHub profile', 'wp-career-board-pro' ),
                'type'     => 'url',
                'required' => false,
            ],
            [
                'key'      => '_wcb_resume_linkedin_url',
                'label'    => __( 'LinkedIn profile', 'wp-career-board-pro' ),
                'type'     => 'url',
                'required' => false,
            ],
            [
                'key'      => '_wcb_resume_website_url',
                'label'    => __( 'Personal website', 'wp-career-board-pro' ),
                'type'     => 'url',
                'required' => false,
            ],
        ],
    ];
    return $groups;
}, 10, 2 );

After this filter is in place:

  • The full multi-section Resume Builder renders a new "Online presence" group of fields.
  • The single-page Resume Form and the Quick Resume Form render the same group inline.
  • Each value persists as resume post meta under the exact key you declared - so the example above writes _wcb_resume_github_url, _wcb_resume_linkedin_url, and _wcb_resume_website_url. Choose a prefixed, collision-safe key; there is no automatic prefix added for you.
  • Read a saved value back on the front end with get_post_meta( $resume_id, '_wcb_resume_github_url', true ).

Field types

Same field types as Free's Custom Fields filter:

text, textarea, email, tel, url, number, date, select, checkbox, radio, multiselect.

Initial state filter

For Interactivity-API-driven forms (the Resume Builder, the single-page Resume Form, and the Quick Resume Form), the initial state hydrates from a separate filter so you can pre-populate values from a partner site or referral context. The filter receives the state array and the resume post ID (not the user ID):

add_filter( 'wcb_resume_form_initial_state', function( $state, $resume_id ) {
    // Pre-fill from a partner integration keyed off the resume's author.
    $author_id    = (int) get_post_field( 'post_author', $resume_id );
    $partner_data = get_user_meta( $author_id, '_partner_profile_links', true );
    if ( $partner_data ) {
        $state['customFields']['_wcb_resume_github_url']   = $partner_data['github']   ?? '';
        $state['customFields']['_wcb_resume_linkedin_url'] = $partner_data['linkedin'] ?? '';
    }
    return $state;
}, 10, 2 );

The custom-field values live under the customFields key of the state object (keyed by the field key you registered), alongside built-in keys such as summary, sections, isPublic, and customFieldGroups.

Field Builder admin UI

If you'd rather configure custom fields without writing PHP, install the Field Builder admin page - it writes to the wcb_field_groups / wcb_field_definitions Pro tables and contributes to the wcb_resume_form_fields filter at runtime.

Persistence

Each value is saved as a single post meta row on the wcb_resume post, under the exact key you registered. There is no extra prefix and no bundled meta key.

Storage Key
Per-field post meta the key you declare (e.g. _wcb_resume_github_url)
Read on the front end get_post_meta( $resume_id, '<key>', true )
Save over REST PUT /wcb/v1/resumes/{id} with a custom_fields object in the body, keyed by field key

The default GET /wcb/v1/resumes/{id} response returns id, title, permalink, summary, and sections - it does not include custom fields out of the box. To add them to the response, hook the wcb_rest_prepare_resume filter and append your values.

Validation and sanitization

Each value is sanitized by its declared type before it is written to meta (there is no separate "required" enforcement layer):

  • email - sanitize_email()
  • url - esc_url_raw()
  • number - kept only if numeric, otherwise stored empty
  • date - kept only if it matches YYYY-MM-DD, otherwise stored empty
  • textarea - sanitize_textarea_field()
  • checkbox - stored as '1' when on, empty string when off
  • multiselect - collapsed to a comma-separated string of option values
  • everything else - sanitize_text_field()

To reject a value or transform it further, hook wcb_save_custom_field ($value, $key, $owner_id) and return null to skip persistence or a scalar to override it.

See also

  • Quick Resume Form - single-page form where these fields render
  • Resume Builder overview - multi-section editor where these fields also appear
  • Field Builder - point-and-click field configuration without PHP
  • Free's Custom Fields filter API - same pattern applied to job / company / candidate / application forms

Single-Page Resume Form (Pro)

WP Career Board Pro 1.4.3 ships three resume form variants so site owners can pick the candidate experience that fits their audience. All three write to the same wcb_resume post type and the same REST endpoint, so candidates can move between variants without losing data.

The three variants

Variant Block Shortcode What it is
Resume Builder (default) wcb/resume-builder [wcbp_resume_builder] Multi-step accordion: each section collapses, entries shown as compact cards with inline edit forms. Best for first-time candidates who want guided section-by-section completion.
Resume Form (Single-Page) wcb/resume-form [wcbp_resume_form] Single-page comprehensive form. Every section expanded on one scrolling page. Best for returning candidates editing an existing resume, partner integrations, sites that prefer a continuous-scroll feel.
Quick Profile wcb/resume-form-simple [wcbp_resume_form_simple] Minimal single-page profile: headline, summary, top skills, location, open-to-work, photo. Best for sidebars, modals, onboarding flows where a full resume is overkill.

All three operate on the resume the URL points at (?resume_id=N), require the resume to belong to the current user, and write to the same PUT /wcb/v1/resumes/{id} endpoint.

When to pick which

Default site: ship wcb/resume-builder on the resume page (the setup wizard does this for you). Candidates get the friendly stepped flow.

Returning-candidate edit page: switch the resume page to wcb/resume-form so candidates see everything at once and can edit freely without expanding accordions.

Onboarding card / sidebar / modal: use wcb/resume-form-simple for a quick-completion experience. Candidates can flesh out the full resume later via the builder.

Sections covered by wcb/resume-form

All seven sections defined by ResumeModule::get_groups():

  1. School - school_name, certificate, field_of_study, start/end dates, grade, description
  2. College / University - institution, degree_type, field_of_study, start/end dates, gpa, description, achievements
  3. Work Experience - company, job_title, employment_type, location, start/end dates, current_role checkbox, description, skills_used
  4. Certifications - cert_name, issuing_body, issue/expiry dates, credential_id, credential_url
  5. Skills - skill_name, proficiency
  6. Languages - language, proficiency
  7. Portfolio / Links - label, url

Each section has an Add [Section] button at the top right. Each entry has a Remove button at the bottom that prompts a confirm dialog before deleting.

Editor inserter

The block appears in the block inserter under the Widgets category. Two attributes:

Attribute Type Effect
compact bool Tighter padding (sidebars, modals, narrow columns). Default off.

Switching the resume page from builder to single-page

  1. Go to Pages → All Pages and find the page that hosts your resume editor (usually titled "My Resume" or similar - created by the Pro setup wizard).
  2. Edit the page in the block editor.
  3. Remove the Resume Builder block.
  4. Insert the Resume Form (Single-Page) block.
  5. Save.

Candidates who already have resume data see the same data - no migration needed. The two blocks share the database.

Compatibility note

The section filters - wcbp_resume_groups, wcbp_resume_textarea_fields, wcbp_resume_checkbox_fields, and wcbp_resume_date_fields - drive the two section-based variants: Resume Builder and Resume Form (Single-Page). An add-on that adds a "Patents" section or changes a field's input type via these filters affects both automatically.

The Quick Profile variant (wcb/resume-form-simple) does not render the repeater sections, so the section filters above do not apply to it. All three variants share the wcb_resume_form_fields custom-field filter (#custom-resume-fields-wcbp)), which is the right hook when you want a field to appear everywhere.

Where to go next

BuddyPress Integration

Group boards, activity stream, tiered credit pricing, and notifications.

BuddyPress Integration - Overview

WP Career Board Pro is built for community sites running on Reign or BuddyX Pro themes with BuddyPress underneath. Activate BuddyPress and most of the Pro plugin's seven integrations switch on automatically. Three are toggles in the BuddyPress settings card: Profile Tabs and Application Notifications are on by default, and Group Job Boards is off by default (turn it on when you want per-group boards).

Activation gate: the BuddyPress integrations register when the BuddyPress plugin is loaded (function_exists( 'buddypress' )). Individual integrations additionally check their own component - the activity stream needs bp_is_active( 'activity' ), the member chips need bp_is_active( 'members' ). Tiered credit pricing is the one exception - it loads outside the BuddyPress gate entirely so PMPro / MemberPress sites without BuddyPress still get per-membership pricing.

What you get

Integration What it does
Profile tabs Members get a My Jobs tab (employers) and a My Career tab (candidates) on their BuddyPress profile.
Group-scoped job boards Opt-in: enable it and every BuddyPress group gets a "Jobs" tab listing only that group's jobs (existing groups are backfilled in the background).
Activity stream entries Job approvals post to the activity feed; hires post a celebratory entry.
Candidate and employer notifications Application status changes appear in the candidate's BP notification bell; new applications notify the employer.
Member directory filters "Open to work" and "Hiring" chips on /members/.
Tiered credit pricing Different credit cost per BP member type / PMPro level / MemberPress membership.
Group-scoped moderation BP group admins approve/reject jobs on their group's board.

Where you configure it

Most integrations need no settings. Three have a switch at Career Board → Settings → Integrations, in the BuddyPress card:

  • Profile Tabs - on by default. Adds the My Jobs / My Career tabs to member profiles. Turn it off if your BuddyPress theme already provides equivalent tabs and you want to avoid duplication.
  • Application Notifications - on by default. Sends in-app BuddyPress bell notifications (not emails) for new applications and application status changes.
  • Group Job Boards - off by default. Turn it on to give each BuddyPress group its own job board and a "Jobs" tab; your existing groups are added as boards in the background. See Group-scoped job boards.

Tiered credit pricing is option-driven (no admin screen yet) - see its own page for how to set the matrix.

Why it matters

If you run a community-and-careers site (developer guild, alumni network, professional association), you already have engaged members inside groups. WP Career Board Pro turns each group into its own job board with the same chrome BuddyPress members are used to - no separate "jobs admin" experience to learn - and surfaces each member's jobs and applications right on their profile.

What you do not need

  • No additional plugins. Standard BuddyPress + WP Career Board Pro is enough.
  • No theme changes. The Jobs group tab, profile tabs, and member-directory chips are added via standard BP nav and query hooks - the active theme styles them automatically.
  • No data sync. A group board is a real wcb_board post linked to the group via group-meta - every Free + Pro feature (credits, moderation, REST, alerts) works on it without extra wiring.

Where to next

Group-scoped Job Boards

Turn any BuddyPress group into its own job board, with a "Jobs" tab in the group navigation that shows only that group's jobs. The main site-wide job board is unaffected.

This integration is off by default. Turn it on under WP Admin → Career Board → Settings → Integrations → BuddyPress → Group Job Boards. Leaving it off keeps your Boards list clean on communities with many groups.

Turning it on

When you switch Group Job Boards on:

  • Every new BuddyPress group gets a matching job board automatically.
  • All of your existing groups are added as boards in the background (see Adding existing groups below).
  • A "Jobs" tab appears in each group's navigation.

Turning it back off stops new group boards from being created and hides the Jobs tab. Boards already created are kept - nothing is deleted, so no jobs or data are lost, and re-enabling picks up where it left off.

How it works

When the integration is on and a BuddyPress group is created, BpGroupBoards listens on groups_created_group and creates a matching wcb_board CPT post. The two are linked through meta:

  • wcb_board post → _wcbp_group_id post-meta = BP group id
  • BP group → wcbp_board_id group-meta = wcb_board post id

The board's title stays in sync with the group's name automatically (groups_group_after_save), and the board is moved to trash when the group is deleted (groups_group_deleted).

What members see

A new Jobs tab appears in the group navigation at nav position 60. Inside the tab is the standard Free job-listings block scoped to the group's board - same search, chip filters, infinite scroll, and bookmarking as the public job board. The tab is rendered with:

do_blocks( '<!-- wp:wp-career-board/job-listings {"boardId":<board_id>} /-->' );

If the group has no board yet, the tab shows "No jobs board for this group yet."

Adding existing groups

The first time you switch the integration on, WP Career Board Pro adds a board for every group you already have. This runs in the background in small batches (50 groups at a time) so it stays fast and safe even on communities with thousands of groups - you do not need to visit each group or run anything by hand.

The backfill is resumable and skips any group that already has a board, so it never creates duplicates. While it is still working through your groups, any group whose page is visited also gets its board created on the spot (register_group_nav calls on_group_created when no board is linked yet).

Posting a job to a group

There is no dedicated "Post a Job" button on the group Jobs tab itself. Members post through the global "Post a Job" page, and the board they choose is filtered to the groups they belong to: Pro hooks wcb_board_options_for_employer (restrict_boards_to_user_groups) so a member only sees group-backed boards for groups they are a member, mod, or admin of. Boards that are not linked to a BP group (the site-wide default board, admin-created boards) always stay in the picker. Site admins (manage_options) see every board.

So a member of "Acme Engineering" can pick that group's board when posting, and the job lands on the group's Jobs tab rather than the site-wide board.

Group admins can moderate their own jobs

By default only users with the global wcb_moderate_jobs ability can approve / reject jobs. Group admins and mods get an exemption for jobs that live on their own group's board - see Group-scoped moderation.

If you already have a manually-created wcb_board and want to link it to a group instead of letting the integration auto-create one, write both meta keys so the integration can resolve the link in either direction:

add_action( 'init', function () {
    // Match the constants in BpGroupBoards exactly.
    update_post_meta( $existing_board_id, '_wcbp_group_id', $group_id );        // BOARD_GROUP_META
    groups_update_groupmeta( $group_id, 'wcbp_board_id', $existing_board_id );  // GROUP_BOARD_META
});

The group nav, activity routing, board picker, and group moderation all read these two keys.

Activity Stream Entries

Job and hiring milestones automatically post to the BuddyPress activity stream so the community can see what is happening across the careers side of the site. This integration boots only when the BuddyPress Activity component is active (bp_is_active( 'activity' )).

What posts

Trigger Activity type Component Where it shows
Job approved on the site-wide board wcb_job_posted wcb Site-wide activity feed
Job approved on a group's board wcb_job_posted groups The group's activity tab + site-wide feed
Application moves to Hired wcb_member_hired wcb Site-wide activity feed

Two action types are registered in the activity directory dropdown so visitors can filter the stream by them: Job posted (wcb_job_posted) and Member hired (wcb_member_hired).

Job approvals route to groups; hires do not. When an approved job lives on a group board (its board has _wcbp_group_id set and the BP Groups component is active), the entry is re-pointed at that group so it appears in the group's activity tab. The "hired" entry always posts to the site-wide wcb component - it is not routed into a group.

What the entry looks like

  • Job approval: "Acme Inc posted a new job: Senior Backend Engineer." - the company name links to the user profile, the title links to the job single page.
  • Hire: "🎉 Sarah Chen was hired for Senior Backend Engineer." - the candidate name links to their profile, the title links to the job.

What does not post

By design the integration does not post on:

  • Pending jobs - only approved jobs trigger wcb_job_approved, so the moderation queue stays private.
  • Application submitted / under review / shortlisted / not selected - only the "hired" status posts publicly. Every other status change is private to the candidate (#notifications-wcbp)) so a public stream doesn't shame people through the funnel.

Disabling activity entries

To keep the site quiet - for example a careers extension on a community site where members don't want a job firehose in their feed - remove the two actions. Match the method names on the BpActivity class exactly:

remove_action( 'wcb_job_approved',               array( 'WCB\Pro\Integrations\Buddypress\BpActivity', 'on_job_approved' ) );
remove_action( 'wcb_application_status_changed', array( 'WCB\Pro\Integrations\Buddypress\BpActivity', 'on_status_changed' ) );

Because both are registered against an object instance, the most robust way to detach them is to remove the whole filter on a class you can reach, or use remove_all_actions() carefully. If you only want to suppress the public entry without touching the rest of the integration, short-circuit at the source instead - e.g. skip the underlying wcb_job_approved / wcb_application_status_changed events upstream.

Customising activity copy

The activity text is built inside BpActivity and wrapped in __() with the wp-career-board-pro text domain, so you can translate it with Loco Translate, WPML, or Polylang the same way you translate any other plugin string. There is no dedicated content filter for the activity wording in this release; rewrite the strings through translation, or remove the default actions (above) and re-add your own bp_activity_add() callbacks on wcb_job_approved / wcb_application_status_changed.

Candidate and Employer Notifications

WP Career Board Pro pushes two kinds of in-app notification into the BuddyPress notification bell:

  • To candidates - when their application's status changes.
  • To employers - when a new application is received for one of their jobs.

These sit alongside the email notifications the plugin already sends. The bell badge nudges people back to your community while they're already on-site (where they're more likely to apply for another job, complete a profile, or post in a group).

Both flows can be turned off with one switch - see Turning it off.

What candidates see

When an application's status changes, the candidate gets a bell notification. The text is " - ":

New status Verb shown
submitted Application received
reviewing Your application is under review
shortlisted You were shortlisted
rejected Application not selected
hired You were hired!
any other / custom status Application status updated

Clicking the notification takes the candidate to My Applications in their My Career profile tab.

What employers see

When a candidate applies, the job's author gets a bell notification:

  • One application: "New application received for "
  • Several stacked: " new applications received"

Clicking it lands the employer on the Applications screen inside their My Jobs profile tab. Employers are not notified about their own applications (a candidate applying to a job they posted is skipped) or about guest applications where there is no candidate id.

Where the strings live

Every verb and label is wrapped in __() with the text domain wp-career-board-pro. Translate them via Loco Translate, WPML, or Polylang the same way you'd translate any other plugin string. There is no separate verb-registration filter in this release - custom and unknown application statuses fall back to the generic "Application status updated" verb.

How it is wired

  • Component name: wcbp. Career Board registers wcbp as a BuddyPress notification component (bp_notifications_get_registered_components) so the rows actually surface in the header bell. (Before 1.4.3 the rows were written but filtered out of the bell because the component wasn't registered - that is now fixed.)
  • Employer alerts use the component action wcb_new_application.
  • Candidate alerts use wcb_app_status_<status> (for example wcb_app_status_hired).
  • Notification text is rendered through bp_notifications_get_notifications_for_user.

Turning it off

Both flows are controlled by a single setting at Career Board → Settings → IntegrationsBuddyPressApplication Notifications (on by default). It writes the option wcbp_bp_notifications. To force it off in code:

add_filter( 'pre_option_wcbp_bp_notifications', '__return_zero' );

To detach a single flow instead, remove its handler from the BpProIntegration instance:

// Stop candidate status-change bell notifications.
remove_action( 'wcb_application_status_changed', array( 'WCB\Pro\Integrations\Buddypress\BpProIntegration', 'notify_candidate_status_change' ) );

// Stop employer new-application bell notifications.
remove_action( 'wcb_application_submitted', array( 'WCB\Pro\Integrations\Buddypress\BpProIntegration', 'notify_employer' ) );

Member Directory Filters

Three chips appear above the member list at /members/:

  • All members - clears the filter.
  • Open to work - candidates with a published resume marked open-to-work.
  • Hiring - members who own at least one currently published job.

Clicking a chip filters the BP member loop in place - same chrome, same paging, same theme styling. No new pages, no new templates. The integration boots only when the BuddyPress Members component is active (bp_is_active( 'members' )).

How it works

BpMemberFilters does two things:

  1. Renders the chips on bp_members_directory_member_types (the slot BP themes reserve for member-type tabs), each chip a link that sets or clears the wcbp_status query arg.
  2. Hooks bp_after_has_members_parse_args to read $_GET['wcbp_status'] and narrow the underlying BP_User_Query with an 'include' => array of matching user ids.

The two chip values:

  • open - authors of published wcb_resume posts whose _wcb_resume_open_to_work meta = 1. The scan is bounded to 500 published resumes.
  • hiring - distinct authors of published wcb_job posts, read in one SELECT DISTINCT post_author query.

If a chip matches no users, the query is forced to an empty result set ('include' => array( 0 )) rather than silently dropping the filter, so the directory honestly shows "no members" instead of every member.

What does not happen

  • No new database tables. The chips read existing post meta and post-author relationships.
  • No new menu items. The chips render in the BP member-type slot so the active theme styles them automatically.
  • No registration prompts. Logged-out visitors see the chips and can use them - the filters work on public, published data only.

Customising the chips

The chips and their queries are not exposed through dedicated filters in this release - there is no per-chip visibility filter and no chip registry hook. To change them, work at the query level instead. For example, to add your own member filter, hook the same BP arg filter and read your own query arg:

add_filter( 'bp_after_has_members_parse_args', function ( array $args ): array {
    if ( isset( $_GET['my_filter'] ) && 'mentors' === sanitize_key( wp_unslash( $_GET['my_filter'] ) ) ) {
        $args['include'] = get_users( array(
            'meta_key'   => 'open_to_mentor',
            'meta_value' => '1',
            'fields'     => 'ID',
        ) );
    }
    return $args;
} );

To suppress the built-in chips entirely, remove the render callback:

remove_action( 'bp_members_directory_member_types', array( 'WCB\Pro\Integrations\Buddypress\BpMemberFilters', 'render_filter_chips' ) );

Tiered Credit Pricing

Charge different credit costs to different members by applying a percentage multiplier to the base cost. A premium PMPro level might post jobs at 0% (free); a basic member pays 100% (full price); a discounted tier pays 50%. The rules live in one option, keyed by membership source.

It is a multiplier, not a flat price. The matrix stores a percentage (0-100, or higher to mark up). The final cost is round( base_cost * multiplier / 100 ), floored at 0. A multiplier of 0 means free for that tier; 100 means full price; 50 means half.

Loaded outside the BuddyPress gate. This integration runs on PMPro / MemberPress sites without BuddyPress too - it reads whichever source the user belongs to and applies the lowest matching multiplier.

How it works

BpTieredCreditCost hooks the wcbp_consumer_cost filter, which Career Board fires for the job_post consumer (base = the board's per-board credit_cost) and the featured_upgrade consumer (base = the wcbp_featured_upgrade_cost option). It reads a single option, wcbp_credit_cost_matrix, keyed by:

consumer -> source -> key -> multiplier (percentage)

When a user posts a job (or upgrades a job to featured), the integration checks every source the user belongs to, collects the matching multipliers, and applies the lowest one. If the base cost is 0 or there is no logged-in user, the base cost passes through unchanged.

The three sources

Source key What the inner key is Resolved by
bp_type BuddyPress member-type slug bp_get_member_type()
pmpro Paid Memberships Pro level id (integer) pmpro_getMembershipLevelForUser()
memberpress MemberPress membership id (integer) MeprUser::active_product_subscriptions( 'ids' )

A source only contributes a multiplier when its plugin is active and the user actually belongs to a listed tier. PMPro and MemberPress are keyed by numeric membership id, not by slug.

The matrix shape

update_option( 'wcbp_credit_cost_matrix', array(
    'job_post' => array(
        'bp_type' => array(
            'mentor'  => 0,   // BP member type "mentor" posts free
            'company' => 100, // company members pay full price
            'staff'   => 25,  // staff pay a quarter
        ),
        'pmpro' => array(
            2 => 0,   // PMPro level id 2 posts free
            3 => 50,  // PMPro level id 3 pays half
        ),
        'memberpress' => array(
            7 => 0,   // MemberPress membership id 7 posts free
        ),
    ),
    'featured_upgrade' => array(
        'pmpro' => array(
            2 => 0,   // level id 2 upgrades to featured for free
        ),
    ),
) );

If a user is both pmpro level 3 (multiplier 50) and bp_type mentor (multiplier 0), the integration applies 0 because the lowest multiplier wins.

Free posting for paid members

The most common configuration: free job posts for paying members, full price for everyone else.

  1. Leave the board's base credit_cost at your full price (say 10).
  2. Add a 0 multiplier for the paid level(s) in the matrix.
update_option( 'wcbp_credit_cost_matrix', array(
    'job_post' => array(
        'pmpro' => array(
            2 => 0,   // Premium: free
            3 => 50,  // Pro: half price (5 credits on a 10 base)
        ),
    ),
) );

Members with no listed tier just pay the base cost.

Resolution rules

  1. If base cost is 0 or there is no logged-in user, return the base cost.
  2. Read the rules for this consumer (job_post / featured_upgrade); if none, return the base cost.
  3. Collect the lowest multiplier from each source the user belongs to (BP member types, PMPro level, MemberPress memberships).
  4. If no source matched, return the base cost. Otherwise apply round( base * min(multipliers) / 100 ), floored at 0.

No admin screen yet

This release has no settings UI for the matrix. Set wcbp_credit_cost_matrix with update_option() (a small mu-plugin or a WP-CLI wp option update --format=json call works well). The BuddyPress card under Career Board → Settings → Integrations covers the profile tabs and notification toggles only, not tiered pricing.

Programmatic tweaks

wcbp_consumer_cost runs at priority 10 inside the integration, so a higher priority overrides it:

// Always charge full price for one tagged employer, regardless of their tier.
add_filter( 'wcbp_consumer_cost', function ( $cost, $user_id, $item_id, $board_id, $consumer ) {
    if ( 'job_post' === $consumer
        && in_array( 'enterprise_customer', wp_get_object_terms( $user_id, 'employer_tag', array( 'fields' => 'slugs' ) ), true )
    ) {
        return 10;
    }
    return $cost;
}, 20, 5 );

Group-scoped Moderation

By default, only users with the global wcb_moderate_jobs ability can approve or reject pending jobs. On a community site that's usually just admins.

When a BP group has its own job board, that gate is too strict - group admins want to review jobs posted to their group without you having to grant them site-wide moderator powers.

This integration adds an exemption: BP group admins and moderators can approve / reject pending jobs on their group's board only.

How it works

Free exposes a hookable ability check that passes the result and the job being acted on:

$allowed = apply_filters( 'wcb_moderate_jobs_ability_check', $allowed, $job_id );

Pro hooks this filter via BpGroupBoards::allow_group_admins_to_moderate() with two arguments ($allowed, $job_id). For each pending-job moderation request it:

  1. Returns early if access is already allowed, the job id is 0, or the BP Groups functions are unavailable.
  2. Reads the current user with get_current_user_id() - bails for logged-out users.
  3. Loads the job's _wcb_board_id post-meta - bails if 0.
  4. Loads that board's _wcbp_group_id post-meta - bails if 0 (the board isn't linked to a group).
  5. Returns true if the user is an admin or mod of that group (groups_is_user_admin / groups_is_user_mod).

The check is job-scoped through the job's own board, so a group admin in group A cannot moderate jobs posted to group B.

What's covered

Action Group admin can do it?
Approve / publish a pending job on their group's board Yes
Reject a pending job on their group's board Yes
Moderate a job on a different group's board No
Moderate a site-wide pending job (not on any group's board) No

The exemption only loosens the wcb_moderate_jobs ability check for group-board jobs; it does not create a new admin screen. Group admins moderate the same way any moderator does - through the moderation surface Free provides - but the ability check now says yes for jobs on their own group's board.

Disabling the exemption

To keep moderation strictly global (only users with wcb_moderate_jobs can approve, even group admins), remove the filter from the BpGroupBoards instance:

remove_filter( 'wcb_moderate_jobs_ability_check', array( 'WCB\Pro\Integrations\Buddypress\BpGroupBoards', 'allow_group_admins_to_moderate' ) );

Auditing approvals

Approvals fire wcb_job_approved regardless of which user approved. To log who approved a group-board job, read the same two meta keys the integration uses:

add_action( 'wcb_job_approved', function ( $job_id ) {
    $approver_id = get_current_user_id();
    $board_id    = (int) get_post_meta( $job_id, '_wcb_board_id', true );
    $group_id    = (int) get_post_meta( $board_id, '_wcbp_group_id', true );
    if ( $group_id ) {
        // Log: $approver_id approved $job_id on group $group_id
    }
} );

Profile Tabs - My Jobs and My Career

WP Career Board Pro adds career context straight onto each BuddyPress member profile. Employers get a My Jobs tab; candidates get a My Career tab. Each tab shows the right thing to the right viewer and reuses the same Free blocks the rest of the site uses.

This integration is on by default and can be switched off at Career Board → Settings → IntegrationsBuddyPressProfile Tabs (option wcbp_bp_profile_tab). Turn it off if your BuddyPress theme already provides equivalent tabs.

My Jobs (employer tab)

Appears on a profile when the displayed user can post jobs (wcb_post_jobs capability) and either has at least one job or is viewing their own profile. Sub-tabs:

Sub-tab Who sees it What it shows
Posted Anyone who can see the tab The user's jobs, via the wp-career-board/job-listings block scoped to the author. The count in the tab label is a live COUNT, not a full fetch.
Applications Profile owner (and bp_moderate) only Applications received across all the owner's jobs, via the wcb/my-applications block.

The owner also sees a "+ Post a New Job" button (links to the configured Post a Job page) on the Posted and Applications screens.

My Career (candidate tab)

Appears when the displayed user can apply for jobs (wcb_apply_jobs capability) and either owns the profile or has a published resume. Sub-tabs:

Sub-tab Who sees it What it shows
My Applications Profile owner (and bp_moderate) only The user's submitted applications, via wcb/my-applications scoped to the author.
Resume Owner always; visitors only when a published resume exists The resume via the wcb/resume-archive block. Owners see private content; visitors see the published version only.
Saved Profile owner (and bp_moderate) only Bookmarked jobs, via wp-career-board/job-listings filtered to the user's saved jobs.

Owners get contextual action buttons too - "Browse Jobs" on My Applications, "Edit Resume" on Resume, "Browse More Jobs" on Saved.

Visibility rules at a glance

  • Tabs are capability-gated, not role-gated, so administrators and custom role setups still see them, while a plain candidate never sees My Jobs (no wcb_post_jobs cap) and a plain employer never sees My Career.
  • The owner-only screens (Applications, My Applications, Saved) check bp_is_my_profile() or the bp_moderate capability before rendering - candidates cannot see each other's applicants, and employers cannot see a candidate's private application list.
  • Counts in the tab labels (Posted, Applications, My Applications, Saved) are computed with request-memoized COUNT queries so they stay accurate on large sites without materialising every row.

Styling and assets

On BuddyPress member and group pages the tab content is rendered through render_block() after wp_head, so the integration force-enqueues the shared Free block stylesheets plus its own bp-integration.css (RTL-aware) to guarantee the job-listings, resume, and shared primitives render correctly. No theme work is required.

Turning it off

Use the Profile Tabs toggle under Settings → Integrations, or force it off in code:

add_filter( 'pre_option_wcbp_bp_profile_tab', '__return_zero' );

Multi-Board

Run unlimited independent job boards from one site.

Multi-Board Engine

The Multi-Board Engine lets you run multiple independent job boards from one WordPress install. Each board has its own set of jobs, employers, and settings - all managed from a single admin.

What You Get

  • Multiple boards - create as many boards as you need (e.g. "Tech Jobs," "Marketing Jobs," "Remote Only")
  • Board isolation - jobs posted to one board don't appear on others
  • Board filter chips - the engine adds a board filter to the Job Filters so visitors can narrow the Job Listings block to one board
  • Per-board settings - each board can have its own pipeline stages, credit pricing, currency, moderation, expiry, map provider, and custom fields

Creating a Board

Boards are the wcb_board post type. Manage them from the Boards tab in Pro settings:

  1. Go to Career Board → Settings → Boards in wp-admin
  2. Click Add Board (or Add Your First Board when the list is empty)
  3. This opens the standard board editor. Enter:
Field Description
Title The board name, visible to employers when posting a job (e.g. "Tech Jobs")
Content Optional description for the board
Board Settings meta box Per-board credit cost, moderation, expiry, currency, map provider, and AI toggle (see Per-Board Settings below)
  1. Click Publish

The Boards tab lists every board with its job count, pipeline stage count, and credit cost, and provides Edit and Delete actions. Deleting a board removes its pipeline stages and unlinks (but does not delete) the jobs assigned to it.

Assigning Jobs to a Board

When an employer posts a job, they see a Board dropdown in the job form (if more than one board exists). The job is assigned to their selected board and the choice is stored in the _wcb_board_id post meta.

Admins can also assign or reassign boards from the job's edit screen in wp-admin.

Filtering Listings by Board

There is no separate "Board Switcher" block. Instead, the Multi-Board engine feeds every published board into the Job Filters via the wcb_job_listings_board_options filter, so visitors can narrow the Job Listings block to a specific board using the board filter alongside category, type, and location - all without a page reload.

Default Board

The Free plugin auto-creates a default board on activation, so a job posted without an explicit board selection still lands on a valid board. There is no "Set as Default" action in the Boards admin - the default is the one Free provisions.

Per-Board Settings

Each board has its own settings, configured from the Board Settings meta box on the board's edit screen (Career Board → Settings → Boards → Edit). Settings are stored in the _wcb_board_settings post meta:

Setting Default Description
Credit Cost Per Job 0 (Free) Credits required to post a job on this board. 0 means free posting.
Moderation Use global default Per-board override for whether jobs auto-publish or require approval. "Use global default" follows the site-wide Auto-publish jobs setting.
Job Expiry (days) 0 (inherit) Per-board expiry override. 0 follows the site-wide Default expiry (days) setting.
Currency Site default Currency for salary display on this board. Defaults to the site-wide salary currency.
Map Provider OpenStreetMap Geocoding provider used when jobs on this board are geocoded.
Enable AI Features Off Enable AI features for jobs on this board.

Jobs on a premium board (credit cost > 0) are automatically marked as Featured, giving them priority sorting and the Featured badge in listings.

Per-Board Pipeline Stages

Application pipeline stages are stored per board (the wcb_application_stages table carries a board_id column), so the Application Kanban resolves each job's columns from its board's stages. The Boards tab shows the stage count for each board. See Application Pipeline for how stages are configured.

Employer Posting

When more than one board exists, employers choose which board to post to from the Board dropdown on the job form. Boards are not access-restricted per employer; any employer can post to any board they are offered in that dropdown.

Job Map

Interactive map view of job listings by location.

Job Map

The Job Map block displays an interactive map of job locations alongside your job listings. As candidates filter jobs, the map updates in real time to show only the matching pins.

How It Works

The Job Map block shares the same search state as the Job Listings and Job Filters blocks. When a visitor searches by keyword, filters by category, or selects a job type, the map instantly updates to show only jobs that match - no page reload.

Since 1.6.0, the map narrows its pins to the same result set the Job Listings block just fetched: after every search or filter change, the map keeps only the pins for jobs that are currently in the list, and restores all pins once the search or filter is cleared. Place a Job Map alongside a Job Listings block on the same page for this to take effect - a map with no Job Listings block on the page has no result set to narrow against and shows every geocoded pin.

Clicking a pin on the map opens a small popup with the job title and a "View Job" link.

Requirements

  • Jobs must have a Location taxonomy term set (city, region, or full address)
  • The location is geocoded automatically when an admin creates the job
  • A job with no latitude/longitude is simply left off the map. Existing jobs without coordinates can be backfilled from the Jobs admin.

Adding the Job Map

  1. Open your jobs page in the WordPress editor
  2. Click + and search for "Job Map"
  3. Insert the block - recommended placement is beside or below the Job Listings block

Recommended layout:

[ Job Search ] [ Job Filters ]
[ Job Map     ] [ Job Listings ]

This two-column layout (Job Map on the left, Job Listings on the right) gives candidates both the spatial and list view simultaneously.

Block Settings

In the block sidebar:

Setting Default Description
Map Height 480px Height of the map canvas in pixels

Map Provider (Geocoding)

WP Career Board Pro supports three geocoding providers. The provider setting controls how a job's location text is turned into latitude/longitude coordinates. You can set a global default or override it per board.

Provider API Key Required Notes
OpenStreetMap (Leaflet) No Default. Works out of the box. Geocoding via Nominatim.
Google Maps Yes Requires a Google Maps Platform API key (Geocoding API enabled)
Mapbox Yes Requires a Mapbox access token

The on-screen map always renders with Leaflet and OpenStreetMap tiles, regardless of the selected provider. Choosing Google or Mapbox changes only the geocoding service that resolves a location into coordinates - it does not swap the rendered map tiles.

Setting the Global Map Provider

  1. Go to Career Board → Settings → Integrations
  2. Find the map provider field and choose your provider
  3. Paste your API key (Google or Mapbox only)
  4. Click Save

API keys are stored in the wcbp_map_settings option (google_api_key / mapbox_api_key).

Setting a Per-Board Provider

Each board can override the global provider. See Board Settings to configure a different geocoding provider for a specific board.

Geocoding

When a job is created, WP Career Board Pro reads the job's wcb_location taxonomy term(s), geocodes the resulting address with the configured provider, and stores the coordinates as post meta (_wcb_lat, _wcb_lng). This happens automatically - no manual coordinates needed.

Job Alerts

Keyword and location alerts for candidates.

Job Alerts

Job Alerts let candidates subscribe to saved searches. When new jobs matching their saved filters are posted, they receive an email - instantly, daily, or weekly.

How Candidates Create and Manage Alerts

All alert subscription and management happens through one block: Job Alerts (wcb/job-alerts). Add this block to a job search page or candidate dashboard page.

The block has two parts:

  1. Get Job Alerts - a frequency selector (Instant / Daily / Weekly) and a Subscribe button. Subscribing saves the current search filters as a new alert. A confirmation message ("Alert created. We will email you when matching jobs are posted.") appears on success.
  2. Active Alerts - a list of the candidate's existing alerts. Each row shows the alert and a control to delete it.

The block is only shown to logged-in users; an alert is always owned by the candidate who created it.

Frequency Options

Frequency When emails send
Instant As soon as a matching job is published
Daily Once per day (default), morning digest
Weekly Once per week, Monday morning

Daily is the default frequency when an alert is created without an explicit choice. Candidates can change an alert's frequency at any time from the block.

What Gets Matched

Each alert stores criteria that are checked against every candidate job. A job must satisfy all of the criteria that are set on the alert:

  • Keyword - matched (case-insensitive substring) against the job title
  • Category - job must be in the alert's wcb_category term
  • Job Type - job must be in the alert's wcb_job_type term
  • Location - job must be in the alert's wcb_location term
  • Salary minimum - the job's maximum salary must not fall below the alert's minimum
  • Salary maximum - the job's minimum salary must not exceed the alert's maximum
  • Remote - when set, only jobs flagged remote match

Empty criteria are ignored, so an alert with only a keyword matches on that keyword alone.

Delivery Channels

By default, alerts are delivered by email. Delivery runs through a channel registry (the wcbp_alert_dispatch_channels filter), so addons can add Slack, Discord, SMS, or push delivery alongside email without modifying the alerts module. The built-in email channel fires the wcbp_send_alert_email action.

Setting Up (Admin)

Email Delivery

Job alert emails use WP Career Board's standard email system:

  1. Go to WP Career Board → Settings → Notifications
  2. Set your From Name and From Email

For reliable delivery, configure an SMTP plugin so alert emails are not flagged as spam.

Cron Schedule

Instant alerts fire in real time when a job is created (on the wcb_job_created action). Daily and weekly digests run on WP-Cron:

  • Daily digest is scheduled for 8:00 AM (server time)
  • Weekly digest is scheduled for Monday 8:00 AM (server time)

If your site has low traffic, WP-Cron may run late. Configure a real server cron that calls wp cron event run --due-now to keep digests on time.

The digest sweep only emails jobs published since each alert's last send, so candidates are not sent duplicates.

Data Model

Alerts are stored in the wcb_job_alerts table. Each row records the owning user, an optional board scope (0 = all boards), the saved keyword, a JSON blob of filters, the frequency, and the last-sent timestamp. The REST routes that power the block are:

Action REST route
List the current user's alerts GET /wcb/v1/alerts
Create an alert POST /wcb/v1/alerts
Update an alert's frequency PUT /wcb/v1/alerts/{id}
Delete an alert DELETE /wcb/v1/alerts/{id}

Creating and listing require a logged-in user. Updating and deleting require the alert's owner (or a user with the wcb/manage-alerts ability).

Job Feed

RSS, JSON, and XML feeds for every board.

Job Feed - Overview

The Job Feed publishes all your live jobs as an XML feed at a fixed URL. Submit this URL to Indeed, LinkedIn, and other job aggregators to automatically syndicate your listings.

Feed URL

https://yoursite.com/wcb-jobs.xml

The feed is disabled by default. Enable it in Career Board → Settings → Job Feed.

Feed Format

The feed uses the Indeed XML format, which is also accepted by Glassdoor, LinkedIn, and most other major job aggregators. The document root is a single <source> element that opens with two publisher fields, followed by one <job> element per published job.

Source-level fields

Field Source
<publisher> Your site name (Settings → General → Site Title)
<publisherurl> Your site home URL

Per-job fields

Each <job> entry contains:

Field Source
<title> Job post title
<date> Publication date, RFC-822 GMT format
<referencenumber> WordPress post ID
<url> Public permalink
<company> Company name meta field (_wcb_company_name)
<city> First term from the wcb_location taxonomy
<country> Always emitted, currently empty
<description> Job description (HTML stripped, wrapped in CDATA)
<salary> Formatted min-max range in the job's own currency, e.g. $80,000 - $120,000 / yearly
<jobtype> First term from the wcb_job_type taxonomy
<email> Contact email from feed settings
<expirationdate> Deadline meta field (_wcb_deadline)

The salary uses each job's stored currency symbol (USD, EUR, INR, JPY, etc.), not a hardcoded dollar sign, so multi-currency boards export the correct symbol to every aggregator.

Setup

Step 1: Enable the feed

  1. Go to Career Board → Settings → Job Feed
  2. Toggle Enable Feed on
  3. The feed URL appears immediately below the toggle

Step 2: Set the contact email

Enter the email address to include in the <email> field of every job entry. This is the address aggregators and candidates use to contact you about listings. It defaults to the WordPress admin email.

Step 3: Submit to Indeed

  1. Log in to the Indeed Employer Portal
  2. Go to Integrations → Job Feed
  3. Enter your feed URL: https://yoursite.com/wcb-jobs.xml
  4. Indeed re-fetches the feed every 24 hours

Pagination

The feed returns up to 200 jobs per page (a safe default; Indeed accepts up to 1,000 per page). If you have more than 200 published jobs, append a start parameter to retrieve additional pages:

https://yoursite.com/wcb-jobs.xml?start=0    ← jobs 1-200
https://yoursite.com/wcb-jobs.xml?start=200  ← jobs 201-400
https://yoursite.com/wcb-jobs.xml?start=400  ← jobs 401-600

Caching

Each feed page is cached for one hour using WordPress transients, and the response sends a Cache-Control: public, max-age=3600 header so CDNs and aggregators can edge-cache it.

Cache invalidation is handled with a version number rather than deletion. Whenever any job is saved or updated, an internal feed version (the wcbp_feed_version option) is bumped. Because the transient key includes that version, the next request builds a fresh feed immediately while the old cached copies expire on their own after an hour. There is nothing to clear manually.

Disabling the Feed

Toggle Enable Feed off. The URL then serves your theme's standard 404 page with an HTTP 404 status instead of XML. Aggregators that poll the URL will stop receiving new listings.

Analytics

Credit, job, and applicant analytics with CSV export.

Analytics - Overview

The Analytics module gives you a single-screen snapshot of your job board's activity - jobs, applications, users, views, and credit flow.

Since 1.6.0, this lives on its own Analytics tab (Career Board -> Settings -> Analytics) rather than being folded into another settings screen, so the numbers below are always one click away.

What Is Tracked

Metric Description
Total Jobs Count of all published wcb_job posts
Total Applications Count of all submitted applications
Total Employers Number of users with the wcb_employer role
Total Candidates Number of users with the wcb_candidate role
Job Views (30 days) Page view events logged in the wcb_job_views table during the last 30 days
Top 5 Jobs The five most-viewed jobs, with individual view counts
Application Rate Average number of applications per published job
Credits Issued Lifetime sum of all topup entries in the credit ledger
Credits Spent Lifetime sum of all deduction entries in the credit ledger

Job view tracking is provided by WP Career Board (free). All other metrics are computed directly from WordPress post counts, user roles, and the credit ledger table.

Where to Find Analytics

Go to Career Board → Analytics in your WordPress admin.

The stat totals are served from a short five-minute cache, so opening the page repeatedly does not re-run every query. Credit totals are an exception: they are refreshed immediately whenever credits are topped up or consumed, so the Credits Issued and Credits Spent figures are always current.

CSV Export

You can export the full credit ledger as a CSV file for accounting or auditing purposes. The export is served by a REST endpoint and streamed directly as a download:

GET /wcb/v1/analytics/credits.csv

The downloaded file is named wcb-credits-YYYY-MM-DD.csv. Access requires the wcb/manage-credits ability (granted to site administrators and any user explicitly authorized to manage credits), so ledger data is not exposed to general staff.

CSV Columns

Column Source
ID Ledger row ID
Employer WordPress user ID on the ledger row (user_id)
Amount Credit amount (positive for top-ups, negative for deductions)
Type Entry type: topup, hold, deduction, or refund
Job Associated item ID, normally the job post ID (item_id)
Note Human-readable note attached to the entry
Date Timestamp the entry was created

Rows are exported newest first.

Transactions (native checkout purchases and refunds)

Since 1.7.0, the Analytics tab also has a Transactions card - a paginated, filterable list of every native Stripe/PayPal credit purchase and refund, with an inline-confirm refund action. See Admin Transactions view for the full details.

Notes

  • The credit ledger is append-only - no entries are ever edited or deleted, so the export is a reliable audit trail
  • Job view data requires the Free plugin's wcb_job_views table; if the table is absent, view metrics show 0
  • Employer and candidate counts use a count-only user query, so the page stays fast even on boards with thousands of users

AI Features

AI-assisted job description writing.

AI Features

WP Career Board Pro 1.4.3 ships a full set of AI-powered hiring tools. They are optional - the plugin works completely without them - but once you add a provider key they light up across both the candidate and employer sides:

  • Job embeddings + AI Chat Search - semantic, natural-language job search.
  • AI candidate matching - "Recommended for you" jobs for each candidate.
  • AI applicant ranking - 0-100 fit score and a one-line reason per applicant.
  • Applicant TL;DR summaries - a one or two sentence summary of each candidate.
  • AI cover-letter writer - candidates generate a tailored cover letter in the apply panel.
  • AI Description Writer - drafts a job description on the post-a-job form.

The hands-on AI documentation lives in the Free plugin's docs section. That section covers Free's hook surface and Pro's feature set in one place so customers see the upgrade path right where they browse. This page is the Pro-side index - for setup, usage, blocks, hooks, and troubleshooting, follow the links below.

The full AI guide

Topic Where to read
What AI does + the shipping list docs/ai-features/overview
Picking a provider + setup walkthrough docs/ai-features/setup-and-providers
Candidate-side: AI Chat Search, matches, cover letters docs/ai-features/for-candidates
Employer-side: Description Writer + ranking + TL;DR docs/ai-features/for-employers
Block + REST + filters for add-on authors docs/ai-features/blocks-and-developers
Troubleshooting docs/ai-features/troubleshooting

At a glance

Feature Surface
Job embeddings on publish Auto - hooks wcb_job_created (needs an embedding provider)
AI Chat Search Block wcb/ai-chat-search, shortcode [wcbp_ai_chat_search], POST /wcb/v1/ai/match
Candidate "Recommended for you" matches POST /wcb/v1/ai/match (current user) and GET /wcb/v1/candidates/{id}/matches
AI applicant ranking One-click on the Employer Dashboard + GET /wcb/v1/ai/ranked-applications/{job_id}
Applicant TL;DR summary Shown in the Employer Dashboard list and detail (returned alongside the fit score)
AI cover-letter writer "Generate" in the candidate apply panel + POST /wcb/v1/jobs/{job_id}/ai-cover-letter
AI Description Writer "Generate with AI" on the post-a-job form + POST /wcb/v1/jobs/ai-description
Auto-score on submit Optional toggle (Settings > AI Settings) - scores each new application in the background
Index existing jobs "Index existing jobs" button (Settings > AI Settings) backfills embeddings

Providers are chosen per task

Since 1.4.0, the analysis provider and the embedding (matching) provider are configured independently, each with its own API key and model. This lets you, for example, run Claude for analysis copy while using OpenAI for embeddings.

Task Allowed providers Why
Analysis & ranking (ranking, TL;DR, cover letters, descriptions, chat) Anthropic Claude, OpenAI, Ollama Completion models. Claude runs on Sonnet by default.
Matching (embeddings) OpenAI, Ollama Embedding models. Claude has no embeddings API, so it is not listed here.

Each provider also exposes a model selector so you can trade quality for cost: Claude (Haiku / Sonnet / Opus), OpenAI (GPT-4o mini / GPT-4o plus the embedding model), and Ollama (model names as installed on your server).

Provider When to pick it
OpenAI Fastest setup. One key covers both analysis and matching.
Anthropic Claude Strong analysis copy. Needs OpenAI or Ollama alongside for embeddings.
Ollama Self-hosted, no key, free. No data leaves your server. Requires server CPU / GPU.

Configure under WP Admin > Career Board > Settings > AI Settings. The fastest path is a single OpenAI key set as both the Analysis and the Matching provider.

Caching and cost control

AI fit scores, reasons, and summaries are cached per application in post meta (_wcbp_ai_fit_score, _wcbp_ai_fit_reason, _wcbp_ai_summary, _wcbp_ai_scored_at). Re-opening the Employer Dashboard never re-bills the model - ranking computes only the applications that are missing a score. There is also a per-user rate limit of 30 AI requests per hour on the AI REST endpoints.

Since 1.7.0, AI applicant ranking is asynchronous. Clicking "Rank applicants" returns already-scored applicants immediately and queues any unscored ones for background scoring instead of making the request wait on a live model call per applicant. Newly scored applicants appear, ranked, the next time you open or refresh the dashboard. This keeps a first-time "rank everyone" click on a job with many applicants from timing out. AI candidate matching is similarly bounded and cached for large job catalogs.

AI Chat Search (1.5.0 and later) correctly honors the query you type and renders the matched jobs - an earlier defect could leave the results panel empty even though matching jobs existed.

What does NOT ship

Listed honestly so expectations are calibrated:

  • No resume parser - the filter wcbp_candidate_resume_data resolves a candidate's resume into text for matching, ranking, and cover letters, but Pro core does not parse uploaded resume files.
  • No "Test connection" button - validate a provider by running a real generation (for example, generate a description on the post-a-job form).
  • No WP-CLI wp wcb ai * commands.

If any blocks your use case, file on the support board.

Why the docs live in Free

Most WP Career Board users start on Free and decide whether to upgrade after seeing what Pro adds. Documenting Pro AI features inside Free's docs means the upgrade story is visible right where users browse - no second site to discover, no separate index to maintain. This is deliberate, not a doc-organisation accident.

For all hands-on detail (setup, flows, hooks, troubleshooting), open the Free links above.

Notifications

Email and BuddyPress notification templates.

Notifications - Overview

WP Career Board Pro extends the Free plugin's notification system in three ways: it feeds extra events into the in-app notification bell that employers and candidates see inside their dashboards, it adds Pro email types for job alerts, credit top-ups, and low credit balance warnings, and (since 1.7.0) it sends the same events as native mobile push notifications to any companion app the member has signed into.

In-App Notification Bell

The notification bell itself - the badge, the dropdown, and the unread count - is rendered by the Free plugin in the Employer Dashboard and Candidate Dashboard blocks. Pro's role is to write notification rows on additional career-board events, so the bell shows them automatically.

Events Pro Adds to the Bell

Event Who receives it Message example
Application submitted Employer "Jane Doe applied for Senior PHP Developer"
Application submitted Candidate "Your application for Senior PHP Developer was submitted"
Application status changed Candidate "Your application for Senior PHP Developer is now Shortlisted"
Job approved Employer "Your job 'Senior PHP Developer' has been approved"
Job rejected Employer "Your job 'Senior PHP Developer' was not approved"
Job expired Employer "Your job 'Senior PHP Developer' has expired"

All notifications are stored in the wcb_notifications database table. The is_read flag is set to 0 on insert. The bell badge count reflects the number of unread rows for the current user.

Native Mobile Push Notifications

Since 1.7.0, every event that writes a row to the in-app notification bell (a new application, a status change, a job approval or rejection, an expiry) also fans out as a push notification to the member's phone, if they have a companion app installed and signed in.

  • There is no separate push-only event list to configure - push reuses the exact same notification event as the bell, so a member never gets a push about something that isn't also in their dashboard notifications.
  • A member's device is registered the first time they sign into a companion app; a shared device signing in as a different member re-registers to the new account instead of leaving a stale registration behind.
  • Deleting a member's account removes their registered devices, so a deleted account never keeps receiving pushes.
  • Push delivery happens in the background after the notification event fires, so posting a job, reviewing an application, or any other action that triggers a notification is never slowed down waiting on push delivery.

This feature has no settings of its own to configure on the website side - it activates automatically once a member has a companion app registered.

Pro Email Notifications

The Pro notifications module registers three additional transactional emails into the Free plugin's email registry (via the wcb_registered_emails filter), so they appear alongside the Free emails. You can customise the subject line and enable or disable each one from Career Board → Settings → Emails.

Job Alert Digest

  • Email ID: job-alert
  • Recipient: Candidate
  • Trigger: wcbp_send_alert_email action - fired when the Job Alerts module finds new jobs matching a candidate's saved search
  • Content: A list of matching job titles with direct links

Credit Top-Up Confirmation

  • Email ID: credit-topup
  • Recipient: Employer
  • Trigger: wcbp_credits_topped_up action - fired whenever credits are added to an employer's account (purchase, admin top-up, or mapped product/subscription)
  • Default subject: "Credits added to your account"
  • Content: Confirmation of the credits added and updated balance

Low Credit Balance Warning

  • Email ID: low-balance
  • Recipient: Employer
  • Trigger: wcbp_credits_low action - fired when an employer's balance drops to the configured low-balance threshold
  • Default subject: "Your credit balance is low"
  • Content: Balance warning and a prompt to purchase more credits

Email Template Customisation

All Pro emails use the same templating system as Free emails, so customisation works the same way:

  • Theme override (recommended): Drop a file at wp-content/themes/your-theme/wp-career-board/emails/{slug}.php, where {slug} is the email ID above (for example wp-career-board/emails/credit-topup.php). The theme override always wins.
  • Plugin templates: The Pro defaults ship in modules/notificationspro/templates/emails/ and are added to the lookup chain via the wcb_email_template_dirs filter. You can register your own directory with the same filter.

Configuring Email Settings

  1. Go to Career Board → Settings → Emails
  2. Toggle any email on or off
  3. Edit the subject line for each email type
  4. Click Save

Changes take effect immediately for the next triggered email.

Migration

Import from WP Job Manager and CSV.

Migration & CSV Import - Overview

WP Career Board Pro adds a CSV importer that bulk-imports jobs from a spreadsheet. It appears as an extra card on the Free plugin's Import screen, next to the built-in WP Job Manager migration cards.

By default, imported jobs land as Pending for editorial review, but the CSV status column lets you publish or draft rows directly.

Since 1.6.0, the CSV importer is idempotent: re-running the same file (or an updated export of it) updates the matching jobs instead of creating duplicates. Imported jobs are also geocoded automatically, and by default notify candidates whose saved job alerts match them - see Idempotent Re-Imports and Geocoding & Candidate Alerts below.

CSV Import

Finding the Import Screen

Go to Career Board → Import and look for the CSV → Jobs card (marked Pro). The CSV importer requires the wcb_manage_settings capability.

Download the Sample File

Click Download Sample CSV to get a correctly structured template with two example rows. Use it as a starting point for your data.

CSV Column Reference

Required

Column Description
title Job title - the only required column

Content

Column Description
description Full job description (HTML allowed)
status pending (default), publish, or draft
deadline Application deadline - any parseable date format, stored as YYYY-MM-DD

Salary

Column Accepted values
salary_min Integer (e.g. 80000)
salary_max Integer (e.g. 120000)
salary_currency Any currency code in the plugin's currency catalog (e.g. USD, EUR, GBP, CAD, AUD, INR, SGD). Codes are matched case-insensitively.
salary_type yearly, monthly, or hourly

If salary_min is greater than salary_max, the importer swaps them automatically and records a warning.

Flags

Column Accepted values
remote yes, no, 1, or 0
featured yes, no, 1, or 0

Company

Column Description
company Company name text
company_id WordPress post ID of an existing company post

Application

Column Description
apply_url External application URL
apply_email Application contact email

Taxonomies

Separate multiple values with commas or pipes. Terms are created automatically if they do not exist.

Column Taxonomy
categories Job category
job_types Job type (e.g. `Full-time
locations Location
experience Experience level
tags Job tags

Geo and Board

Column Description
lat Latitude (decimal)
lng Longitude (decimal)
board_id WordPress post ID of the target job board (Multi-Board)

Custom Fields

Add any field key from the Field Builder as a column header. Values are mapped to the corresponding custom field on each imported job.

Running the Import

  1. Select your CSV file using the file picker
  2. Click Import
  3. A results summary shows counts for Imported, Updated, Skipped, and Warnings. Per-row errors are listed under a "View row errors" toggle.
  4. Use the Review pending jobs link to review the Pending listings before publishing

Idempotent Re-Imports

Re-importing a CSV never creates duplicate jobs. Each row is matched against jobs already imported by:

  • An external_id column, if your CSV provides one (recommended - ties each row to a stable ID from your source system), or
  • A natural key derived from the row's title, company, and board when no external_id is given.

A row that matches an existing import updates that job in place (title, description, status, and all mapped fields) and counts as Updated. A row with no match creates a new job and counts as Imported. This means you can re-run the same file - or a refreshed export from your source system - on a schedule without piling up duplicate listings.

Geocoding & Candidate Alerts

Every imported job (new or updated) is geocoded automatically the same way a job posted through the frontend is - no separate step needed. See Job Map - Geocoding for how location text is resolved into coordinates.

Newly imported jobs also notify candidates whose saved job alerts match them, by default - the same matching used for jobs posted normally. A bulk historical import does not trigger the pending-review email or activity-stream posts that a fresh frontend submission would; only the candidate-alert matching runs. Developers can turn off alert matching on import with the wcbp_alerts_notify_on_import filter (return false).

Error Handling

Condition Result
File not found or unreadable Import stops with an error message - no rows processed
Empty file or no header row Import stops with an error message - no rows processed
Missing title column Import stops with an error message - no rows processed
Row has wrong column count Row skipped, error logged in summary
Empty title on a row Row skipped, error logged in summary
Invalid currency or salary type Field skipped for that row
Invalid date Field skipped for that row
salary_min greater than salary_max Values swapped automatically, counted as a warning

WP Job Manager Migration

The one-click WP Job Manager migration lives on the same Career Board → Import screen, in the built-in WP Job Manager → Jobs card. That card is provided by the Free plugin and is the supported way to migrate from WP Job Manager - you do not need Pro for it.

The migration copies job_listing posts to wcb_job posts, preserving title, content, author, and publish date. It is safe to run multiple times; already-migrated records are detected and skipped, and the card shows live "total" and "migrated" counts.

A note for developers

Pro also ships a standalone \WCB\Pro\Modules\Migration\WpjmImporter helper class. It is a programmatic helper only - it is not wired to any admin button or WP-CLI command, so for everyday migrations use the Free Import screen above. If you call the Pro helper directly in custom code, its import() method maps the following:

WPJM meta key WCB meta key
_job_location _wcb_location_text
_job_salary _wcb_salary_text
_company_name _wcb_company_name
_job_expires _wcb_deadline
_remote_position _wcb_remote
job_listing_type terms wcb_job_type taxonomy

Published WPJM jobs are imported as publish; any other status becomes pending. Each migrated original is flagged with _wcb_imported_from_wpjm so subsequent runs skip it, and the method returns [ 'imported' => N, 'skipped' => N, 'errors' => [...] ].

PWA

Progressive Web App with installable manifest and service worker.

PWA - Overview

The PWA module turns your job board into an installable Progressive Web App. Candidates can add it to their phone's home screen and browse job listings even with a poor connection.

What the PWA Provides

Feature Description
Install prompt Browsers prompt candidates to install the job board as a home screen app
Offline browsing Previously visited job listings load from cache when the device is offline
Network-first forms Application forms and dashboards always fetch fresh data - never served stale from cache
Branded splash screen The app name, theme color, and icon match your site's branding
VAPID key pair Generated automatically on plugin activation for future push notification support

How It Works

The module serves two files from your site's root:

  • /wcb-manifest.json - Web App Manifest describing the app name, icon, and display mode
  • /wcb-service-worker.js - Service worker that intercepts fetch events on WCB pages

The service worker uses stale-while-revalidate for job listing pages (/jobs/, /companies/, /candidates/): the cached version loads instantly while a fresh copy is fetched in the background. Application forms and dashboard pages use network-first: they always try the network and fall back to cache only if the network is unavailable.

The manifest and service worker are only injected on WCB-related pages (job archives, single job pages, and the configured Employer/Candidate Dashboard pages).

Setup

Step 1: Configure the theme color

  1. Go to Career Board → Settings → Integrations
  2. Find the PWA Settings card
  3. Pick a Theme Color - this is the brand color shown in the browser toolbar and on the splash screen when the app launches
  4. Click Save

The default theme color is #4f46e5 (indigo).

After first activating Pro, go to Settings → Permalinks and click Save Changes. This registers the rewrite rules needed to serve /wcb-manifest.json and /wcb-service-worker.js.

Browser Support

The PWA install prompt and service worker work in:

  • Chrome and Edge (Android and desktop)
  • Safari 16.4+ (iOS and macOS)
  • Firefox (service worker only - no install prompt on Firefox for Android)

Browsers that do not support service workers continue to work normally - the module degrades gracefully.

Verifying Installation

Open your job listings page in Chrome on Android. After a few seconds, Chrome displays an Add to Home screen banner at the bottom of the browser. Tap it to install. The app opens in standalone mode (no browser toolbar) with your chosen theme color.

On desktop Chrome, look for the install icon in the address bar.

Pro Blocks Reference

Every Pro block, its attributes, and example uses.

Pro Blocks Reference

WP Career Board Pro adds 16 blocks to the WordPress block inserter. All Pro blocks are registered in the wcb/ namespace and appear in the block inserter only when WP Career Board Pro is active. Every Pro block is listed under the Widgets category in the inserter.

Blocks at a Glance

Block Name in Inserter What It Does
wcb/ai-chat-search AI Chat Search Natural language job search powered by AI
wcb/application-kanban Application Kanban Drag-and-drop Kanban board for pipeline stages
wcb/credit-balance Credit Balance Employer credit balance with transaction history
wcb/featured-candidates Featured Candidates Sidebar widget listing featured public resumes
wcb/featured-companies Featured Companies Sidebar widget listing featured companies
wcb/job-alerts Job Alerts Subscription form for saved job search alerts
wcb/job-map Job Map Interactive map of job locations
wcb/my-applications My Applications Candidate's submitted applications list
wcb/open-to-work Open to Work Sidebar widget listing candidates open to work
wcb/resume-archive Find Resumes Public archive of candidate resumes
wcb/resume-builder Resume Builder Multi-step resume editor for candidates
wcb/resume-form Resume Form (Single-Page) Continuous-scroll resume form, every section expanded
wcb/resume-form-simple Resume Form (Quick Profile) Short profile-completion form for candidates
wcb/resume-map Resume Map Map showing candidate locations
wcb/resume-search-hero Resume Search Hero Full-width resume search form
wcb/resume-single Resume Single Public-facing view of a single candidate resume

Note: There is no Board Switcher block. Visitors filter the Job Listings block by board using the board filter chips that the Multi-Board engine adds to the standard Job Filters - see Multi-Board Engine.

Where to Add Each Block

Block Recommended Page
AI Chat Search Jobs page - in place of or above the standard Job Search block
Application Kanban Employer Dashboard page (auto-added when Pipeline is enabled)
Credit Balance Employer Dashboard or any employer-facing page
Featured Candidates Homepage sidebar, company pages
Featured Companies Homepage sidebar, jobs page sidebar
Job Alerts Jobs page - below or beside the Job Listings block
Job Map Jobs page - alongside Job Listings and Job Filters
My Applications Candidate Dashboard page
Open to Work Homepage sidebar, employer-facing pages
Find Resumes Any public page - e.g., "Find Candidates"
Resume Builder Candidate Dashboard page or a dedicated Resume page
Resume Form (Single-Page) A dedicated resume page for site owners who prefer one long form over the multi-step builder
Resume Form (Quick Profile) Sidebars, modals, partner pages, onboarding flows
Resume Map Find Candidates page - alongside Find Resumes
Resume Search Hero Find Candidates page - above Find Resumes
Resume Single The wcb_resume post template (auto-assigned by setup)

Block Details


Natural language job search powered by semantic AI. Candidates type a conversational query - "remote product manager role with equity" - and get semantically matched results using vector similarity.

Attributes:

Attribute Type Default Description
placeholder string "Describe your ideal job…" Input placeholder text

Usage notes: Requires the AI module to be configured with a provider key. Go to WP Career Board → Settings → AI Settings to connect your AI provider (OpenAI, Claude, or Ollama). Uses the WordPress Interactivity API.


wcb/application-kanban - Application Kanban

A drag-and-drop Kanban board showing applicant cards organized by pipeline stage. Employers drag cards between columns to advance or reject candidates.

Attributes:

Attribute Type Default Description
jobId integer 0 Scope the board to a specific job. 0 shows all jobs.

Usage notes: Automatically added to the Employer Dashboard when the Application Pipeline module is enabled. See Application Pipeline. Uses the WordPress Interactivity API.


wcb/credit-balance - Credit Balance

Displays the logged-in employer's current credit balance, a Buy Credits button, and a recent transaction history panel.

Attributes:

Attribute Type Default Description
showHistory boolean true Show or hide the recent transaction list
historyCount integer 5 Number of recent transactions to display

Usage notes: Reads the logged-in employer's balance automatically. Shows nothing to non-employer users. Uses the WordPress Interactivity API.


A static sidebar widget listing featured public candidate resumes. Useful for drawing employer attention to available talent.

Featured Candidates block

Attributes:

Attribute Type Default Description
count integer 5 Number of candidates to display (1-20)
title string "" Optional heading above the list
showViewAll boolean true Show a "View All" link below the list
viewAllUrl string "" URL for the "View All" link

Usage notes: Displays candidates who have at least one public resume. Set viewAllUrl to your Find Candidates page URL. Skill pills render directly from the wcb_resume_skill taxonomy and fall back to _wcb_resume_skills meta values when the taxonomy is empty (1.2.0+) - candidates whose skills-sync hook never ran still display correctly.


A static sidebar widget listing featured companies with their open role counts.

Attributes:

Attribute Type Default Description
count integer 5 Number of companies to display (1-20)
title string "" Optional heading above the list
showViewAll boolean true Show a "View All" link below the list
viewAllUrl string "" URL for the "View All" link

Usage notes: Shows companies with at least one active job. Each company card shows the company logo, name, and open job count.


wcb/job-alerts - Job Alerts

A subscription form that reads the current search state and lets logged-in candidates save their active filters as a job alert.

Attributes: None

Usage notes: Shares the same wcb-search Interactivity API store as Job Listings and Job Filters. When a candidate clicks Save Alert, the current keyword, category, type, location, salary range, and remote filter are saved. Frequency (instant/daily/weekly) is set from the Candidate Dashboard. See Job Alerts.


wcb/job-map - Job Map

An interactive Leaflet map showing job pin locations, synced with the Job Listings block search state. When a visitor filters jobs, the map updates in real time.

Attributes:

Attribute Type Default Description
height integer 480 Map canvas height in pixels

Usage notes: Jobs must have a Location taxonomy term set so they can be geocoded. Coordinates are geocoded automatically when an admin creates the job. Jobs without latitude/longitude are not shown on the map - they can be backfilled from the Jobs admin. The map itself always renders with Leaflet and OpenStreetMap tiles; the Google/Mapbox provider setting changes only the geocoding service, not the rendered map. See Job Map.


wcb/my-applications - My Applications

Renders the current user's submitted job applications as a semantic table with Job / Status / Submitted column headers (1.2.0+). On narrow viewports it collapses to a card layout via data-label attributes so mobile reads the same structure without horizontal scroll.

Candidate view (default) - shows the current user's own applications:

My Applications block - candidate view

Employer view - set the employerId attribute to surface a Job + Applicant view of every application across that employer's posted jobs:

My Applications block - employer view with Applicant column

Attributes:

Attribute Type Default Description
authorId integer 0 Show another user's applications (admin use only). 0 = current user.
employerId integer 0 Switch to employer view. 0 = candidate view. Permission-gated: only the employer themselves or a wcb_manage_settings admin can view another employer's pipeline.
perPage integer 20 Applications per page

Usage notes: The Status column always renders a badge (defaults to "Submitted" if the application's _wcb_status meta is empty), so the column is never blank. Status badges use the same .wcb-status-badge colour tokens as the candidate dashboard. Place on the Candidate Dashboard page (candidate view) or any employer-scoped page (employer view).


wcb/open-to-work - Open to Work

A static sidebar widget listing candidates who have set their profile status to "Open to Work."

Open to Work block

Each candidate card shows (1.2.0+):

  • Avatar - pulled from the resume form's uploaded photo (_wcb_resume_photo_id), with an initial-letter chip in the brand colour as the fallback. Never falls back to the BuddyPress / Gravatar avatar - the resume form is the canonical source for a candidate's professional photo on this block.
  • Name - display name
  • Headline - the current/most-recent experience entry's job title
  • Location - resume-level _wcb_resume_location meta, or the current experience entry's location when the resume-level field is blank
  • Years of experience - computed from the earliest start_date across all experience entries (e.g. "8+ years")
  • Skill pills - top 3 from the wcb_resume_skill taxonomy, with the same _wcb_resume_skills meta fallback as the Featured Candidates block

Attributes:

Attribute Type Default Description
count integer 5 Number of candidates to display (1-20)
title string "" Optional heading above the list
showViewAll boolean true Show a "View All" link below the list
viewAllUrl string "" URL for the "View All" link

Usage notes: Useful on employer-facing pages to surface actively available candidates. Candidate order is randomised on every load so the widget rotates exposure across everyone who is open to work, rather than always showing the same most-recently-saved set.


wcb/resume-archive - Find Resumes

A paginated, filterable archive of publicly visible candidate resumes. Displays avatar, name, job title, skills, and location for each resume card.

Attributes:

Attribute Type Default Description
perPage integer 12 Resumes per page
authorId integer 0 Scope to a specific user. 0 = all public resumes.
includePrivate boolean false Include private resumes (admin only)

Usage notes: Pair with wcb/resume-search-hero on the same page for the full "Find Candidates" experience. The Pro Setup Wizard creates this page automatically.


wcb/resume-builder - Resume Builder

A section-by-section resume editor for candidates covering work experience, education (school and college), skills, languages, certifications, and portfolio links. Supports multiple resumes per candidate.

Attributes: None

Usage notes: Requires the candidate to be logged in. Can run in standalone mode (dedicated page with ?resume_id= parameter) or embedded mode (same page as the Candidate Dashboard). See Resume Builder - Setup.


wcb/resume-form - Resume Form (Single-Page)

A single-page version of the Resume Builder. Every section (School, College, Experience, Certifications, Skills, Languages, Portfolio) is rendered expanded on one continuous-scroll screen instead of the multi-step wizard. It writes to the same resume data as the Resume Builder, so site owners can pick whichever layout they prefer.

Attributes:

Attribute Type Default Description
compact boolean false Render the form in a tighter, narrower layout

Usage notes: Requires the candidate to be logged in. Use this as an alternative to the multi-step Resume Builder when you want the whole form visible at once. See Single-Page Resume Form.


wcb/resume-form-simple - Resume Form (Quick Profile)

A short profile-completion form for candidates covering only the essentials: headline, summary, top skills, location, open-to-work status, and an optional photo. Designed for sidebars, modals, partner pages, and onboarding flows where the full resume builder would be too heavy.

Attributes:

Attribute Type Default Description
showPhotoField boolean true Show or hide the profile photo upload field
compact boolean false Render the form in a tighter, narrower layout

Usage notes: Requires the candidate to be logged in. Saves to the candidate's resume so the headline, location, skills, and open-to-work status feed the Open to Work and Featured Candidates blocks. See Quick Resume Form.


wcb/resume-map - Resume Map

An interactive Leaflet map showing candidate locations. Clicking a pin navigates to that candidate's public resume profile.

Attributes:

Attribute Type Default Description
height integer 480 Map canvas height in pixels

Usage notes: Pair with wcb/resume-archive on the Find Candidates page to give employers a spatial view of talent. Candidate coordinates come from their profile location. Uses the WordPress Interactivity API.


wcb/resume-search-hero - Resume Search Hero

A full-width search form for the candidate archive. Supports keyword search plus optional skill filter and open-to-work filter.

Attributes:

Attribute Type Default Description
layout string "horizontal" Form layout - horizontal or vertical
placeholder string "" Search input placeholder text
buttonLabel string "" Search button label
showSkillFilter boolean true Show the skill filter dropdown
showOpenToWorkFilter boolean false Show the open-to-work toggle filter

Usage notes: Place above wcb/resume-archive on your Find Candidates page. The Pro Setup Wizard adds both blocks to the Find Candidates page automatically.


wcb/resume-single - Resume Single

The public-facing resume view for a single candidate. Renders all resume sections in a formatted layout.

Attributes:

Attribute Type Default Description
resumeId integer 0 Show a specific resume by ID. 0 resolves the resume from the current wcb_resume post being viewed.

Supports: wide and full alignment.

Usage notes: Place on the wcb_resume post type template. The Pro setup assigns this block to the resume template automatically - you typically do not need to add it manually. Does not accept custom HTML.


Adding a Pro Block

  1. Open any page in the WordPress editor
  2. Click + to add a block
  3. Search for the block name (e.g., "Job Map")
  4. Click to insert

All Pro blocks appear in the Widgets category in the block inserter.

Developer Guide

Hooks reference, REST API, and extending Free.

Developer Guide - Overview

This guide covers WP Career Board Pro 1.7.0.

WP Career Board Pro is built as an extension of WP Career Board (Free), not a fork. Every Pro feature consumes Free's hooks; Free never knows specifically about Pro. The runtime guard (wcbp_free_active()) makes sure Pro degrades gracefully when Free is deactivated.

Use this guide when:

  • You're writing an addon that consumes Pro features (resume builder, application kanban, AI hiring tools, credit SDK).
  • You're auditing the Free/Pro coupling contract.
  • You're writing a custom credit adapter or payment gateway.

For Free-side dev surface (the hooks, REST endpoints, and CLI commands every site is built on), start with the Free developer guide.

Free/Pro coupling contract

Pro extends Free via four invariants (plan/INVARIANTS.yaml A-group). The local-CI gate (bin/architecture-checks.sh) automatically enforces A1-A3 on every push; A4 is a review-enforced rule:

ID Title What it guards
A1 Lockstep version WCBP_VERSION always equals WCB_VERSION. Half-installs (one updated, the other not) are impossible.
A2 Dependency guard Pro defines wcbp_free_active() and uses it to gate boot. Deactivating Free does not fatal Pro.
A3 REST namespace shared, paths disjoint Both register under wcb/v1; Free and Pro routes never collide.
A4 No source modification Pro hooks Free via documented filters only. Never patches Free's classes.

If you're shipping your own Pro-side addon, follow the same invariants - they're the operational contract that lets Pro and the addon coexist without one breaking the other.

Architecture at a glance

Layer Where Purpose
Blocks blocks/<name>/render.php + view.js 16 Pro blocks (kanban, alerts, resume builder/search, credit balance, AI chat search, job/resume maps, etc.)
REST API api/endpoints/class-*-endpoint.php 35 Pro routes under wcb/v1/* extending Free's WCB\Api\REST_Controller (via Pro's WCB\Pro\Api\Pro_REST_Controller); see 02-extending-free.md for the full list
Modules modules/<area>/ Pro features: ai, alerts, analytics, boards, credits, feed, fields, maps, migration, notificationsbell, notificationspro, pipeline, pwa, resume
SDK libs/wbcom-credits-sdk/ Wbcom Credits SDK (bundled in libs/, not vendor/, so it ships in release zips). Adapters: WooCommerce, WC Subscriptions, WC Memberships, PMPro, MemberPress. Gateways: Stripe, PayPal
Core core/class-*.php Lifecycle: ProInstall, ProPlugin, License, FreeCoordination

Contents

Doc What's inside
02-extending-free.md The canonical Pro-extends-Free contract - dependency guard, REST sharing, lockstep
03-hooks-reference.md Pro's own actions and filters
04-credits-sdk.md The Wbcom Credits SDK - registration, consumers, adapters, gateways, ledger
05-ai-providers.md The AI hiring tools - REST endpoints, provider drivers, and how to add a 4th provider

Working against Pro from a third-party plugin

To build an addon that depends on Pro:

  1. Declare Pro as a Requires Plugins: header in your main file.
  2. Gate your runtime hooks behind a defined( 'WCBP_VERSION' ) check.
  3. Verify your minimum Pro version with version_compare() against WCBP_VERSION.
  4. Hook into Pro's documented actions/filters from 03-hooks-reference.md. Never reach into internal classes - those aren't part of the public contract.

The full template (including composer arch-checks for your own addon) is in the Free repo's bin/architecture-checks.sh - copy it, change the namespace, add your invariants.

Extending Free - The Canonical Pro Contract

Pro is a worked example of "how to extend WP Career Board Free without forking." Every pattern here is something a third-party addon can copy verbatim.

The four invariants

The architecture-checks gate enforces these on every Pro commit. Your addon should aim for the same:

A1 - Lockstep version

Pro's WCBP_VERSION constant matches Free's WCB_VERSION at every commit. The pre-commit hook checks both files and fails the build on drift. Why: shipping one updated and the other not means a customer has a half-built release; cross-plugin hook signatures go out of sync.

For an addon, your equivalent is "what's the minimum Free version I work against?" Declare it as a constant (MYADDON_MIN_WCB = '1.4.3'), check at boot:

if ( ! defined( 'WCB_VERSION' )
     || version_compare( WCB_VERSION, MYADDON_MIN_WCB, '<' ) ) {
    add_action( 'admin_notices', 'myaddon_min_wcb_notice' );
    return;
}

A2 - Dependency guard

Pro defines wcbp_free_active() and uses it inside the boot path:

function wcbp_free_active(): bool {
    return defined( 'WCB_VERSION' );
}

if ( ! wcbp_free_active() ) {
    add_action( 'admin_notices', 'wcbp_missing_free_notice' );
    return;
}

The guard runs on plugins_loaded@20 - Free uses default priority 10, so by the time Pro's check fires Free has already booted. If a customer deactivates Free via WP-CLI (which bypasses the Requires Plugins: header), Pro detects it and gracefully skips its hooks instead of fataling.

A3 - REST namespace shared, paths disjoint

Both plugins register under the same wcb/v1 namespace - that's intentional so the API surface stays cohesive to consumers. Disjointness is what matters:

Free:  /jobs, /jobs/{id}, /applications/{id}, /candidates/{id} ...
Pro:   /resumes, /boards/{id}, /pipeline, /alerts ...

The architecture-checks gate (Pro's check_A3) reads both manifests' .rest.endpoints[].route and fails if any path appears in both. If you're adding routes from an addon, pick a unique sub-path and document it.

A4 - No source modification

Pro never patches Free's classes, never calls function_alias, never monkey-patches. All extension goes through documented filters and actions. The contract is one-way: Free exposes the hooks; Pro and other addons consume them.

Pro REST route reference

Pro registers 35 routes under the shared wcb/v1 namespace (all extend WCB\Pro\Api\Pro_REST_Controller, one Endpoint class per group in api/endpoints/). License status never gates these - license drives automatic updates only (see 01-overview.md).

Group Routes Controller
Notifications bell GET /notifications · PUT /notifications/{id}/read · PUT /notifications/read-all · DELETE /notifications/{id} NotificationsBellEndpoint
Job alerts GET,POST /alerts · PUT,DELETE /alerts/{id} AlertsEndpoint
AI hiring tools POST /ai/match · GET /ai/ranked-applications/{job_id} · POST /jobs/{job_id}/ai-cover-letter · POST /jobs/ai-description · GET /candidates/{id}/matches AiEndpoint (see 05-ai-providers.md)
Boards + pipeline stages POST,PUT,PATCH,DELETE /boards/{id}/stages/{stage_id} · GET /boards/{id} · GET,POST /boards/{id}/stages BoardsProEndpoint
Application pipeline (Kanban) PUT /applications/{id}/stage · GET /jobs/{id}/kanban PipelineEndpoint
Resumes GET /resumes · GET,POST /candidates/{id}/resumes · GET,PUT,DELETE /resumes/{id} · GET /resumes/{id}/pdf · POST /resumes/{id}/bookmark · POST /resumes/photo-upload ResumeEndpoint
Field builder GET,POST /fields/groups · POST,PUT,PATCH,DELETE /fields/groups/{id} · GET,POST /fields/groups/{group_id}/fields · POST,PUT,PATCH,DELETE /fields/{id} · POST /fields/reorder FieldsEndpoint
Credits GET /employers/{id}/credits - balance + last 50 ledger rows. Own balance, or wcb/manage-credits ability for any employer. CreditsEndpoint
Analytics GET /analytics/credits.csv - CSV download of the credit ledger, gated on the wcb/manage-credits ability AnalyticsEndpoint
Geocoding GET /geocode GeocodeEndpoint
Native push (mobile/companion app, 1.7.0) POST /push/register-device · DELETE /push/register-device PushEndpoint
Setup wizard (admin only) POST /wizard/activate-license · POST /wizard/setup-credits · POST /wizard/create-pro-pages ProSetupWizard (the one documented carve-out that calls register_rest_route() directly - see docs/HOOKS.md for the rationale)

GET /wcb/v1/employers/{id}/credits is the real balance route. A /credits/balance path referenced in a Pro_REST_Controller docblock is illustrative only and is never registered - do not build against it.

GET /wcb/v1/resumes (the public archive) takes an optional public author int param (default 0 = all authors), added in 1.5.1 so an external consumer - the mobile app, a BuddyNext profile tab - can fetch one member's public resumes through the same visibility rules the archive already applies, without a new route.

Mobile/companion-app push routes (1.7.0)

POST and DELETE /wcb/v1/push/register-device back the native (Expo) push feature for the mobile app. Both require a logged-in member (is_logged_in()) and are not wrapped in pro_check() - like the notification bell, a member's own device registration is a delivery surface that must keep working regardless of license status.

// POST /wcb/v1/push/register-device
// Body: { "expo_push_token": "ExponentPushToken[xxxxxxxx]", "platform": "ios"|"android", "device_name": "..." }
// -> 201 { "registered": true }  |  400 wcb_invalid_push_token  |  500 wcb_push_register_failed

// DELETE /wcb/v1/push/register-device
// Body: { "expo_push_token": "ExponentPushToken[xxxxxxxx]" }
// -> 200 { "unregistered": true }

The token is validated against Expo's ExponentPushToken[...] / ExpoPushToken[...] shape before it's stored. PushModule does not author its own notifications or keep a separate queue - it listens on the existing wcb_notification_created action and fans each message out to the caller's registered devices via the shared AsyncScheduler. See 03-hooks-reference.md.

The SDK's own checkout/webhook/refund routes (a separate wbcom-credits/v1 namespace, not wcb/v1) are documented in 04-credits-sdk.md.

How Pro consumes Free's hooks

The cleanest examples in the codebase:

Returning Pro's status to Free's gate filters

Free fires apply_filters( 'wcb_pro_active', false ) to check whether Pro is running. Pro registers:

// In core/class-free-coordination.php
add_filter( 'wcb_pro_active', '__return_true' );

Pro registers all of these in core/class-free-coordination.php: wcb_pro_active, wcb_pro_licensed, wcb_pro_version, wcb_pro_ai_enabled, wcb_pro_alerts_enabled, wcb_pro_resumes_enabled, and wcb_pro_settings_saved_notice. Each returns a value Pro alone can authoritatively answer.

Reading credit balances and pricing from the SDK

The credit system is owned by the Wbcom Credits SDK, not by a Free placeholder filter. Pro's blocks and endpoints read the balance directly:

$balance = \Wbcom\Credits\Credits::get_balance( 'wp-career-board', $user_id );
$url     = \Wbcom\Credits\Credits::get_purchase_url( 'wp-career-board' );

The one extension point Pro exposes for pricing is the wcbp_consumer_cost filter, applied inside each consumer's cost callback when Pro registers with the SDK:

// Args: ( int $base_cost, int $user_id, int $item_id, int $board_id, string $consumer_slug )
add_filter( 'wcbp_consumer_cost', function ( $cost, $user_id, $item_id, $board_id, $consumer ) {
    if ( 'job_post' === $consumer && current_user_can( 'wcb_employer_pro_tier' ) ) {
        return max( 0, (int) ( $cost / 2 ) );
    }
    return $cost;
}, 10, 5 );

See 04-credits-sdk.md for how Pro registers its consumers, adapters, and gateways with the SDK.

Hooking the board picker to filter by group membership

Free's job-form template fires apply_filters( 'wcb_board_options_for_employer', $options, $user_id ). Pro's BP-groups integration consumes it to drop boards whose linked BuddyPress group the employer is not a member of:

add_filter( 'wcb_board_options_for_employer',
    array( BpGroupBoards::class, 'restrict_boards_to_user_groups' ),
    10, 2
);

This is the canonical pattern for "Pro adds a constraint to a Free control surface."

How Pro extends Free's blocks

Free's blocks render server-side. Pro extends them via two mechanisms:

1 - Form field injection

Free's forms expose declarative field-schema filters that Pro's field builder hooks to inject custom field groups: wcb_job_form_fields, wcb_company_form_fields, wcb_candidate_form_fields, and wcb_resume_form_fields. Each passes the current field array plus a context id (board id, or resume id):

add_filter( 'wcb_job_form_fields', function ( array $fields, int $board_id ) {
    $fields['my_group'] = array( /* field definitions */ );
    return $fields;
}, 10, 2 );

Pro persists the submitted values on the wcb_job_created / wcb_job_updated actions.

2 - REST response filtering

REST responses go through wcb_rest_prepare_* filters. Pro adds Pro-specific fields to the board, board-stage, resume, and notification responses (wcb_rest_prepare_board, wcb_rest_prepare_board_stage, wcb_rest_prepare_resume, wcb_rest_prepare_notification):

add_filter( 'wcb_rest_prepare_resume', function ( $row, $resume, $request, $context ) {
    $row['my_extra_field'] = get_post_meta( $resume->ID, '_my_extra', true );
    return $row;
}, 10, 4 );

These two patterns cover most of Pro's UI extensions. Anything they can't handle is a real gap in Free's hook surface - file a Free PR to add the hook, then consume it from Pro.

How Pro adds new database tables

Pro owns 9 tables (wcb_credit_ledger, wcb_field_groups, wcb_field_definitions, wcb_field_values, wcb_job_boards, wcb_job_alerts, wcb_application_stages, wcb_ai_vectors, wcb_notifications). All creation goes through dbDelta() in core/class-pro-install.php (the wcb_credit_ledger table is created by the Credits SDK's Ledger::maybe_create_table('wcb'), which Pro does not duplicate):

private static function create_field_groups_table( $wpdb ): void {
    $table_name = $wpdb->prefix . 'wcb_field_groups';
    $charset    = $wpdb->get_charset_collate();
    $sql = "CREATE TABLE {$table_name} ( ... ) {$charset};";
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta( $sql );
}

The pattern (one private method per table) makes the schema greppable. Schema version is tracked in wcbp_db_version option.

How Pro extends the credit-purchase flow

Pro registers everything (slug, consumers, and settings) with the Wbcom Credits SDK through its wbcom_credits_sdk_registry action in wp-career-board-pro.php:

add_action( 'wbcom_credits_sdk_registry', function ( \Wbcom\Credits\Registry $registry ) {
    $registry->register( array(
        'slug'      => 'wp-career-board',
        'prefix'    => 'wcb',
        'version'   => WCBP_VERSION,
        'file'      => WCBP_FILE,
        'user_type' => 'employer',
        'consumers' => array( /* job_post, featured_upgrade */ ),
        'settings'  => array( /* low_threshold, purchase_url, admin_settings_hook */ ),
    ) );
} );

The SDK ships the e-commerce adapters (WooCommerce, WC Subscriptions, WC Memberships, PMPro, MemberPress); each adapter listens for that plugin's "order completed" event and writes a topup row to the ledger. Adapters self-discover when the host plugin is active - Pro does not register them one by one. To add support for a new e-commerce plugin, write a new adapter class that implements AdapterInterface. See 04-credits-sdk.md.

Pre-commit + pre-push checks

bin/architecture-checks.sh runs every gate (U1..U6, A1, A2, A3) on every push. If you're authoring against Pro:

composer arch-checks   # Run the gate manually anytime
composer ci            # Run the full pipeline (PHPStan, PHPCS, arch, journeys)

The pre-push git hook (one-time composer install-hooks activates it) runs composer ci:no-journeys before every push. Bypass for emergencies only: SKIP_LOCAL_CI=1 git push.

When the contract doesn't fit

If you find yourself wanting to do something the four invariants don't allow (e.g. modify Free source, register a colliding REST path), STOP and either:

  1. Open a PR against Free to add the missing extension point, or
  2. Build the feature inside Pro using a different mechanism, or
  3. Talk to the team - there's usually a third option we'd rather ship than break the contract.

The contract exists because we've shipped a paired plugin set for years; the four invariants are the things that broke when we tried to "just patch it this once." They're not bureaucracy - they're scar tissue.

Pro Hooks Reference

This reference covers WP Career Board Pro 1.7.0.

WP Career Board Pro fires its own wcbp_* actions and filters (in addition to the Free hooks documented in the Free developer guide). Pro also participates in some wcb_* (Free namespace) hooks where it shares the contract surface with Free. The audit manifest (audit/manifest.json) tracks 71 hook firings across 52 unique names as of the 1.7.0 refresh; a source grep for this revision turned up 4 more call sites the manifest generator misses because they sit inside multi-statement expressions (wcbp_kanban_stage_limit, wcb_job_board_id, wcbp_ai_match_scan_cap, wcbp_alerts_notify_on_import) - this doc covers all of them (75 firings / 56 unique names). Also summarised in docs/HOOKS.md.

Actions Pro fires

Hook Args Use to
wcbp_application_stage_changed $app_id, $old_stage, $new_stage React when an application moves through the Kanban pipeline. Args are stage IDs (integers); old comes before new. Fired from api/endpoints/class-pipeline-endpoint.php.
wcbp_board_deleted $board_id Cleanup when a Pro board is removed. Cascades to applications and stage data.
wcbp_credit_consumed $user_id, $amount, $item_id A deduction row was written to the credit ledger. Use for audit logging or per-purchase notifications. Fired from modules/credits/class-credit-reconciler.php.
wcbp_credits_low $user_id, $balance The SDK reported a credit balance at or below the configured low threshold (re-emitted from the SDK's wbcom_credits_low, scoped to this plugin).
wcbp_credits_topped_up $user_id, $amount, $note The SDK recorded a credit top-up (re-emitted from the SDK's wbcom_credits_topped_up, scoped to this plugin).
wcbp_field_group_saved $group_id A field-builder group was created or updated.
wcbp_field_group_deleted $group_id A field-builder group was deleted.
wcbp_field_definition_saved $field_id, $group_id A field-builder field definition was created or updated ($group_id is 0 on update-by-id).
wcbp_field_definition_deleted $field_id A field-builder field definition was deleted.
wcbp_field_value_saved $post_id, $key, $value A custom field value on a job/company/candidate/resume was saved via the field builder.
wcbp_resume_published $resume_id, $post A candidate's resume went public. $post is the WP_Post. Use for search indexing or notifications.
wcbp_send_alert_email $alert, $jobs Pro is about to send a job-alert email. $alert is the alert row, $jobs the matched jobs. Hook to send a parallel SMS, Slack, etc.
wcbp_open_to_work_before_item $resume, $user, $user_id Render extra markup before each Open-to-Work listing item.
wcbp_open_to_work_after_item $resume, $user, $user_id Render extra markup after each Open-to-Work listing item.
wcbp_my_applications_table_header $is_employer_view Render extra <th> cells at the end of the My Applications table header. $is_employer_view is true on the employer-facing view, false on the candidate view. Fired from blocks/my-applications/render.php.
wcbp_my_applications_table_row $application, $job_id, $is_employer_view Render extra <td> cells at the end of each My Applications row. $application is the application WP_Post. Pair with wcbp_my_applications_table_header so column counts line up. Fired from blocks/my-applications/render.php.
wcbp_credit_hold_reconciled $employer_id, $post_id, $amount, $orphan The daily wcbp_reconcile_credit_holds cron auto-refunded a credit hold that never received a matching deduction or refund within 24 hours (crashed request, dropped REST call). $amount is the original hold amount (positive int); $orphan is the raw ledger row. Fired from modules/credits/class-credit-reconciler.php. Hook here to notify an admin or employer.
wcbp_ai_score_application $app_id Internal single-event cron action. Pro schedules it (via wp_schedule_single_event, or AsyncScheduler::enqueue_async() when an explicit rank request finds unscored applicants) to score a new application in the background when "Auto-score applicants on submit" is on, or on demand from AiModule::rank_applications(). See 05-ai-providers.md.

Pro also fires Free hooks in places where the action semantically belongs to Pro but the contract is shared:

Hook Where Pro fires it Why
wcb_application_status_changed modules/pipeline/class-pipeline-module.php When a pipeline move reaches a terminal stage, Pro closes the application and fires this with ($app_id, $old_status, 'closed') so Free's status listeners (notifications, BuddyPress) run.
wcb_resume_form_simple_extra_fields blocks/resume-form-simple/render.php Extension point for adding fields to the quick resume form. Passes $attributes.
wcb_add_more_info_hero_actions blocks/resume-single/render.php Extension point for adding buttons to the resume hero.
wcb_job_csv_imported modules/migration/class-csv-importer.php Fired once per imported job row with ($post_id, $data).
wcb_notification_created modules/notificationsbell/class-notifications-bell-module.php Fired after a bell notification row is inserted, with one array arg { user_id, event_type, message, link, id } (id = inserted row id), so a centralised notification center (BuddyNext) can mirror it. Shared wcb_ name so one listener covers Free + Pro; Free fires the same hook from its email send. Since 1.7.0, Pro's PushModule also consumes this same event to fan native push notifications out to Expo - see "Push notifications" below.
wcb_job_board_id api/endpoints/class-pipeline-endpoint.php (stage_belongs_to_application(), since 1.7.0) Free-owned filter (fired by wp-career-board's Jobs REST endpoint; Pro's BoardsProModule::resolve_board_id() supplies the multi-board answer). Pro's pipeline endpoint re-applies it to resolve a job's owning board before writing _wcb_stage_id, so a stage move can't be written to a stage id borrowed from another employer's board. ($board_id, $job_id).
wcb_board_credit_cost wp-career-board-pro.php Free-owned filter (Free's Jobs REST endpoint applies it when posting/republishing a job). Pro's job-form billing bridge consumes it to resolve the canonical per-board credit_cost setting, and Pro's republish-billing listener (below) re-applies it to compute the hold amount for a republish. ($cost, $board_id).
wcb_job_republish_credit_cost wp-career-board-pro.php (on Free's wcb_job_republished action) Free-owned filter, fired by Free when a republish transition is processed. Pro's credit-billing listener on wcb_job_republished re-applies the same filter to compute how many credits to hold for the republish, defaulting to the board's normal cost - so one callback can discount both the transition and the bill. ($cost, $post, $previous_status).
wcb_shortcode_attr_aliases core/class-pro-plugin.php Free-owned camelCase-attribute remap (shortcode attributes lowercase, block.json schemas don't). Pro's shortcode-to-block bridge re-applies it so Pro's shortcodes ([wcb_featured_candidates], etc.) share the same attribute vocabulary as Free's. ($aliases).

Filters Pro fires

AI

Hook Returns
wcbp_ai_provider_drivers array - registered AI provider drivers (Anthropic Claude, OpenAI, Ollama, custom)
wcbp_ai_provider_requires_api_key bool - does the active provider need a key?
wcbp_candidate_resume_data array - the resume data shape sent to AI for ranking/matching
wcbp_ai_candidate_matches array - the AI candidate-match results before they are returned (args: $matches, $user_id)
wcbp_ai_ranked_applications array - the AI applicant-ranking results before they are returned (args: $ranked, $job_id)
wcbp_ai_claude_model string - the Claude model id used for analysis/ranking (default reads the wcbp_ai_anthropic_model option)
wcbp_ai_match_scan_cap int - number of newest published jobs scored per candidate-match search (default 2000). Bounds the nearest-neighbor scan in AiModule::find_nearest_jobs() so a large catalog doesn't brute-force every stored vector on each request.

wcbp_ai_anthropic_model is an option (set on the AI settings screen), not a filter. It stores the chosen Claude model id and is read with get_option() as the default value the wcbp_ai_claude_model filter then applies. Override the model at runtime through that filter; change the saved default through the option.

Alerts

Hook Returns
wcbp_alert_dispatch_channels array - which channels to send job alerts through (email, in-bell-notification, future SMS/Slack)
wcbp_alerts_notify_on_import bool - whether a CSV-bulk-imported job fans out instant alert emails like a fresh posting (default true). Args: $notify, $job_id. Return false to suppress alert emails for a one-off historical backfill import.

Credits SDK

Hook Returns
wcbp_consumer_cost int - actual credits charged for a consumer. Args: $base_cost, $user_id, $item_id, $board_id, $consumer_slug. Tier-pricing happens here (e.g. BpTieredCreditCost).

Kanban / pipeline

Hook Returns
wcbp_kanban_card_columns array - extra columns shown on each Kanban card
wcbp_kanban_stage_limit int - cards fetched per stage per page on the Kanban board (default 25, clamped to 1-100). Since 1.6.0.

PWA

Hook Returns
wcbp_pwa_manifest array - the PWA manifest before it's served at /wp-json/wcb/v1/pwa-manifest

Resume builder

Hook Returns
wcbp_resume_groups array - the resume-form field groups (Personal, Experience, Education, Skills, etc.)
wcbp_resume_textarea_fields array - fields rendered as textareas vs single-line inputs
wcbp_resume_checkbox_fields array - fields rendered as checkboxes
wcbp_resume_date_fields array - fields formatted as date inputs
wcbp_resume_proficiency_scale array of {value, percent} - the skill/language proficiency level table for a repeater group. Args: $scale, $group_key. Drives both the input dropdown values and the resume-single progress bar. Since 1.4.4.
wcbp_resume_experience_bands array of {slug, label, min, max} - the years-of-experience band table used to resolve a candidate's experience-level slug.
wcbp_resume_primary_fields array - the field keys shown on each resume section's compact card (e.g. experience => [job_title, company]), before the first-2-fields fallback. Rendered in blocks/resume-builder/render.php.
wcbp_resume_field_label string - override the label of a specific field
wcbp_resume_pdf_renderer callable - swap out the resume-to-PDF rendering engine

Pro also extends these Free filters:

Hook What Pro returns
wcb_rest_prepare_board Adds Pro-specific board metadata to the boards response.
wcb_rest_prepare_board_stage Pipeline-stage data shape.
wcb_rest_prepare_notification Notifications bell response shape.
wcb_rest_prepare_resume Resume profile shape. Runs after the 1.7.0 privacy gate (ResumeEndpoint::can_read_resume()), so a private resume's data never reaches this filter for a caller without a real relationship (owner, admin, or an employer the candidate applied to).
wcb_job_form_fields Injects custom job field groups (field builder).
wcb_company_form_fields Injects custom company field groups.
wcb_candidate_form_fields Injects custom candidate field groups.
wcb_resume_form_fields Injects custom resume field groups.
wcb_resume_form_initial_state array - the resume builder's initial Interactivity-API state. Args: $state, $resume_id. Extend for a custom section's state keys bound by a wcb_resume_form_fields field renderer.
wcb_resume_form_simple_initial_state array - the quick resume form's initial Interactivity-API state. Args: $state, $attributes (block attributes, not a resume id - the simple form can render for a not-yet-created resume).
wcb_board_options_for_employer Restricts the board picker to the employer's BuddyPress groups.

Per-board credit cost is not a Free filter. The cost is resolved inside Pro's SDK consumer cost callback from BoardSettings::get() and then run through wcbp_consumer_cost.

Push notifications (1.7.0)

Native (Expo) push, added in modules/push/class-push-module.php, does not introduce a new hook. PushModule::boot() consumes the existing wcb_notification_created action - the same event the in-app notification bell listens to - and fans each payload out to the user's registered Expo devices via the shared AsyncScheduler. There is one notification event and one source of truth; a site adding a channel doesn't need to learn a push-specific hook, only wcb_notification_created.

Device registration/unregistration is REST-only, not hook-based - see POST/DELETE /wcb/v1/push/register-device in 02-extending-free.md. PushModule exposes two static helpers other Pro code can call directly: PushModule::register_device( $user_id, $token, $platform, $device_name ) and PushModule::unregister_device( $token ).

Listening patterns

Send a Slack message when a pipeline reaches a terminal stage

wcb_application_status_changed fires with ($app_id, $old_status, $new_status). Pro fires it with a final status of closed when a Kanban move lands on a terminal stage:

add_action( 'wcb_application_status_changed', function ( $app_id, $old_status, $new_status ) {
    if ( 'closed' !== $new_status ) {
        return;
    }
    $job_id    = (int) get_post_meta( $app_id, '_wcb_job_id', true );
    $job_title = get_the_title( $job_id );
    wp_remote_post( SLACK_WEBHOOK, array(
        'body'    => wp_json_encode( array( 'text' => "Application closed: {$job_title}" ) ),
        'headers' => array( 'Content-Type' => 'application/json' ),
    ));
}, 10, 3 );

Add a custom group to the resume builder

add_filter( 'wcbp_resume_groups', function ( $groups ) {
    $groups['portfolio'] = array(
        'label'  => __( 'Portfolio links', 'my-addon' ),
        'fields' => array(
            'github'    => array( 'type' => 'url', 'label' => 'GitHub' ),
            'linkedin'  => array( 'type' => 'url', 'label' => 'LinkedIn' ),
            'dribbble'  => array( 'type' => 'url', 'label' => 'Dribbble' ),
        ),
    );
    return $groups;
});

Notify an employer when a stale credit hold auto-refunds

wcbp_credit_hold_reconciled fires once per orphaned hold the daily reconciler cron refunds (a hold left open more than 24h with no matching deduction or refund - typically a crashed request):

add_action( 'wcbp_credit_hold_reconciled', function ( $employer_id, $post_id, $amount, $orphan ) {
    wp_mail(
        get_userdata( $employer_id )->user_email,
        __( 'Credits refunded for an interrupted job post', 'my-addon' ),
        sprintf( '%d credits were auto-refunded for job #%d.', $amount, $post_id )
    );
}, 10, 4 );

Tier the credit cost by user role

add_filter( 'wcbp_consumer_cost', function ( $cost, $user_id, $item_id, $board_id, $consumer ) {
    if ( 'job_post' === $consumer && user_can( $user_id, 'wcb_employer_pro_tier' ) ) {
        return max( 0, (int) ( $cost / 2 ) );  // 50% off for Pro-tier employers
    }
    return $cost;
}, 20, 5 );

How to confirm an arg signature

grep -rn "do_action\\s*(\\s*'wcbp_credit_consumed'" wp-content/plugins/wp-career-board-pro/

The result is the file:line where Pro fires the hook; read the surrounding lines for the parameter shapes.

  • Free hooks reference: ../../../wp-career-board/docs/website/developer-guide/02-hooks-reference.md
  • Credits SDK adapters: 04-credits-sdk.md
  • The Free/Pro coupling contract: 02-extending-free.md

Wbcom Credits SDK

This reference covers WP Career Board Pro 1.7.0, bundling Wbcom Credits SDK 1.4.2 (WBCOM_CREDITS_SDK_VERSION, defined in libs/wbcom-credits-sdk/wbcom-credits-sdk.php).

The credit system is built on the Wbcom Credits SDK - a bundled library at libs/wbcom-credits-sdk/ (kept in libs/, not vendor/, so it always ships in the release zip). It provides:

  • An append-only ledger (the {prefix}_credit_ledger table; for this plugin the prefix is wcb, so wp_wcb_credit_ledger).
  • A consumer pattern (entities that spend credits - job posting, featured upgrade).
  • An adapter pattern for e-commerce plugins (WooCommerce, WC Subscriptions, WC Memberships, PMPro, MemberPress).
  • A gateway pattern for direct payment processors (Stripe, PayPal).

This doc covers the contract for registering a slug and writing your own consumer, adapter, or gateway.

Architecture

            +-------------------------------------------+
            |  Wbcom Credits SDK (libs/)                |
            |                                           |
            |  +--------------+                         |
            |  | Ledger       |  <- single source       |
            |  | (DB writes)  |     of truth            |
            |  +--------------+                         |
            |        ^                                  |
            |   +----+----+----------+                  |
            |   |         |          |                  |
            | Consumers  Adapters  Gateways             |
            | (job_post, (Woo,     (Stripe,             |
            |  featured)  WCS, WCM, PayPal -            |
            |             PMPro,   direct)              |
            |             MemberPress)                  |
            +-----^----------^----------^---------------+
                  |          |          |
              hold/deduct  on order  on webhook
              on actions   completed verified

Registering your slug with the SDK

A plugin registers everything (slug, prefix, consumers, settings) in one call on the wbcom_credits_sdk_registry action, which fires before the SDK loads. Pro does this in wp-career-board-pro.php:

add_action( 'wbcom_credits_sdk_registry', function ( \Wbcom\Credits\Registry $registry ) {
    $registry->register( array(
        'slug'      => 'wp-career-board',
        'prefix'    => 'wcb',              // table prefix: {wp}_wcb_credit_ledger
        'version'   => WCBP_VERSION,
        'file'      => WCBP_FILE,
        'user_type' => 'employer',
        'consumers' => array( /* see below */ ),
        'settings'  => array(
            'low_threshold'       => 5,
            'purchase_url'        => '',
            'admin_settings_hook' => 'wcb_admin_settings_tabs',
        ),
    ) );
} );

Consumers - what can spend credits

A "consumer" is something the SDK debits credits FOR. Each consumer declares three lifecycle actions: hold_on (reserve credits), deduct_on (settle the hold permanently), and refund_on (release the hold). The SDK adds the listeners; you fire the actions. The cost callback receives the item id and returns the credit cost.

Pro registers two consumers inside the consumers array of its register() call:

'consumers' => array(
    array(
        'id'        => 'job_post',
        'label'     => __( 'Job Posting', 'wp-career-board-pro' ),
        'cost'      => static function ( int $item_id ): int {
            $board_id = (int) get_post_meta( $item_id, '_wcb_board_id', true );
            $base     = $board_id
                ? (int) ( ( new \WCB\Pro\Modules\Boards\BoardSettings() )->get( $board_id )['credit_cost'] ?? 0 )
                : 0;
            return (int) apply_filters( 'wcbp_consumer_cost', $base, get_current_user_id(), $item_id, $board_id, 'job_post' );
        },
        'hold_on'   => 'wcb_job_created',     // Hold credits when this action fires
        'deduct_on' => 'wcb_job_approved',    // Settle the hold when this fires
        'refund_on' => 'wcb_job_rejected',    // Release the hold when this fires
    ),
    array(
        'id'        => 'featured_upgrade',
        'label'     => __( 'Featured Upgrade', 'wp-career-board-pro' ),
        'cost'      => static function ( int $item_id ): int {
            $base = (int) get_option( 'wcbp_featured_upgrade_cost', 10 );
            return (int) apply_filters( 'wcbp_consumer_cost', $base, get_current_user_id(), $item_id, 0, 'featured_upgrade' );
        },
        'hold_on'   => 'wcb_featured_upgrade_requested',
        'deduct_on' => 'wcb_featured_upgrade_completed',
        'refund_on' => 'wcb_featured_upgrade_failed',
    ),
),

To add your own consumer, append another entry to the consumers array in your own register() call (or call $registry->register() again for a separate slug). The cost callback signature is function ( int $item_id ): int. Fire your hold_on / deduct_on / refund_on actions when the relevant lifecycle events happen in your code - the SDK takes care of the ledger writes.

Adapters - automatic credit grants from e-commerce plugins

An "adapter" listens for a specific plugin's "order completed" event and writes a topup ledger row. The SDK ships five adapters, which self-register through the SDK's adapter registry when their host plugin is active:

Adapter File Listens to
WooCommerce libs/wbcom-credits-sdk/src/Adapters/WooCommerce.php woocommerce_order_status_completed
WC Subscriptions libs/wbcom-credits-sdk/src/Adapters/WooSubscriptions.php WC Subscriptions renewal/payment events
WC Memberships libs/wbcom-credits-sdk/src/Adapters/WooMemberships.php WC Memberships grant events
Paid Memberships Pro libs/wbcom-credits-sdk/src/Adapters/PMPro.php PMPro membership-change / payment events
MemberPress libs/wbcom-credits-sdk/src/Adapters/MemberPress.php MemberPress transaction-completed event

Each adapter implements AdapterInterface (libs/wbcom-credits-sdk/src/Adapters/AdapterInterface.php). Note the methods are instance methods, not static:

interface AdapterInterface {
    public function get_id(): string;
    public function get_label(): string;
    public function is_available(): bool;             // host plugin active?
    public function register_hooks( string $slug ): void;
    public function get_mappable_items(): array;       // products/levels for the admin mapping UI
}

To add a new adapter (e.g. for Easy Digital Downloads), implement the interface, hook the host plugin's purchase event in register_hooks(), and write a topup row with Credits::topup():

namespace MyAddon\Credits;

use Wbcom\Credits\Adapters\AdapterInterface;
use Wbcom\Credits\Credits;

class EDD implements AdapterInterface {

    public function get_id(): string {
        return 'edd';
    }

    public function get_label(): string {
        return 'Easy Digital Downloads';
    }

    public function is_available(): bool {
        return class_exists( 'Easy_Digital_Downloads' );
    }

    public function register_hooks( string $slug ): void {
        add_action( 'edd_complete_purchase', function ( $payment_id ) use ( $slug ) {
            $user_id = (int) edd_get_payment_user_id( $payment_id );
            foreach ( edd_get_payment_meta_cart_details( $payment_id ) as $item ) {
                $credits = (int) $this->credits_for_product( $slug, (int) $item['id'] );
                if ( $credits > 0 ) {
                    // Signature: topup( $slug, $user_id, $amount, $note )
                    Credits::topup( $slug, $user_id, $credits, 'EDD order #' . $payment_id );
                }
            }
        });
    }

    public function get_mappable_items(): array {
        // Return EDD products in the shape the admin mapping UI expects.
        return array();
    }

    private function credits_for_product( string $slug, int $product_id ): int {
        $mappings = (array) get_option( "{$slug}_credit_mappings", array() );
        foreach ( $mappings as $row ) {
            if ( 'edd' === ( $row['adapter'] ?? '' ) && (int) $row['item_id'] === $product_id ) {
                return (int) $row['credits'];
            }
        }
        return 0;
    }
}

SDK REST routes

The SDK registers its own REST surface per registered slug, under the SDK's own wbcom-credits/v1 namespace - separate from the plugin's wcb/v1 namespace (Pro's GET /wcb/v1/employers/{id}/credits, documented in 02-extending-free.md, is a thin Pro-side read of the same ledger, not part of this surface).

Registry::register() wires the REST class (src/REST.php), which registers three routes:

GET   /wbcom-credits/v1/wp-career-board/balance   Current user's balance (or ?user_id= for an admin).
GET   /wbcom-credits/v1/wp-career-board/history   Paginated ledger entries (limit/offset, default limit 50).
POST  /wbcom-credits/v1/wp-career-board/topup     Admin manual top-up: { user_id, amount, note }.

balance and history are gated by check_balance_permission() (the caller reading their own balance, or an admin reading anyone's via user_id); topup is gated by check_admin_permission().

Gateways - direct payment processors

A "gateway" is for selling credits directly without an e-commerce plugin in between (Stripe Checkout, PayPal). The SDK ships two gateways: Stripe and PayPal (libs/wbcom-credits-sdk/src/Gateways/).

The checkout and webhook flow is reachable today. The SDK's Webhook_Controller registers three more REST routes per slug, same wbcom-credits/v1 namespace:

POST  /wbcom-credits/v1/wp-career-board/checkout/{gateway}   Create a hosted checkout session.
POST  /wbcom-credits/v1/wp-career-board/webhook/{gateway}    Public, provider-signed. Adjusts the ledger.
POST  /wbcom-credits/v1/wp-career-board/refund/{gateway}     Refund a prior checkout.

The webhook is what actually credits the ledger - it is verified by the gateway's verify_signature() before any ledger write, so a forged callback can't grant credits. Refunds initiated in the provider dashboard also flow back through the webhook.

The browser side of checkout/{gateway} doesn't need a hand-rolled fetch(): the SDK ships a reusable helper, assets/js/checkout.js, registered as the wbcom-credits-checkout script handle (once per request, regardless of consumer count) and localized with wbcomCreditsCfg = { restRoot, nonce }. Enqueue it where you render a buy button and call the global it exposes:

wp_enqueue_script( 'wbcom-credits-checkout' ); // Where the buy button renders.
// JS: on click
window.wbcomCreditsCheckout( {
    slug:      'wp-career-board',
    gateway:   'stripe',
    pack_id:   'pack_50',
    credits:   50,
    returnUrl: window.location.href,
} );
// POSTs to /{slug}/checkout/{gateway} with the REST nonce, then redirects
// the browser to the hosted checkout URL the SDK returns.

If you're writing a custom gateway, implement GatewayInterface (libs/wbcom-credits-sdk/src/Gateways/GatewayInterface.php). Abstract_Gateway provides a default handle_webhook() you can extend:

interface GatewayInterface {
    public function get_id(): string;
    public function get_label(): string;
    public function is_available(): bool;
    public function get_settings_fields(): array;
    public function create_checkout( string $slug, int $user_id, int $credits,
                                     int $price_cents, string $currency = 'USD',
                                     ?string $return_url = null ): string;
    public function verify_signature( string $raw_body, array $headers ): bool;
    public function normalize_event( array $payload ): ?Gateway_Event;
    public function handle_webhook( string $slug, array $payload ): \WP_REST_Response;
    public function refund( string $slug, string $session_id, ?int $amount_cents = null ): bool;
}

Once registered via Gateway_Registry, the admin Credits settings UI auto-discovers it.

The ledger

Every credit movement writes one row. The table is named {wp_prefix}{prefix}_credit_ledger (for this plugin, wp_wcb_credit_ledger); the SDK creates one ledger table per registered prefix, so the table itself scopes the data and there is no slug column. Schema:

Column Type Notes
id bigint unsigned PK Auto-increment
user_id bigint unsigned The credit holder (employer)
item_id bigint unsigned The consumed entity (job, etc.); 0 for top-ups/adjustments
entry_type varchar(20) One of topup, hold, deduction, refund
amount int Signed: positive for topup/refund, negative for hold/deduction
note varchar(255) Free-form context string
created_at datetime Defaults to CURRENT_TIMESTAMP

Indexes: idx_user_id (user_id), idx_entry_type (entry_type). The balance is SUM(amount) for the user. The ledger is append-only; the only physical DELETE is cancel_hold().

To read the ledger:

$balance = \Wbcom\Credits\Credits::get_balance( 'wp-career-board', $user_id );
$rows    = \Wbcom\Credits\Credits::get_ledger( 'wp-career-board', $user_id, 50 );
// or directly: \Wbcom\Credits\Ledger::get_history( 'wcb', $user_id, $limit, $offset )

Never INSERT/UPDATE the ledger directly - use the Credits helpers, which use these exact signatures:

Credits::topup(  string $slug, int $user_id, int $amount, string $note = '' ): int|false;
Credits::hold(   string $slug, int $user_id, int $amount, int $item_id, string $note = '' ): int|false;
Credits::deduct( string $slug, int $user_id, int $amount, int $item_id, string $note = '' ): bool;
Credits::refund( string $slug, int $user_id, int $amount, int $item_id, string $note = '' ): int|false;
Credits::cancel_hold( string $slug, int $user_id, int $item_id ): void;
Credits::adjust( string $slug, int $user_id, int $amount, string $note = '' ): int|false;
Credits::get_cost( string $slug, string $consumer_id, int $item_id = 0 ): int;
Credits::get_purchase_url( string $slug ): string;

Note the 4th argument to topup() is a string note, not an array. When a consumer settles, Ledger::deduct_with_hold_release() releases the outstanding hold (writing a refund row for the held amount) and writes a deduction row for the actual cost in one transaction-safe step.

Where to read further

  • libs/wbcom-credits-sdk/src/ - the SDK source (bundled, not loaded over the network).
  • audit/journeys/security/license-required-for-pro-rest.md - the contract that Pro REST (including credit endpoints) stays operational regardless of license status (license gates updates only, never runtime features).
  • Pro hooks for credits in 03-hooks-reference.md: wcbp_consumer_cost (cost filter), wcbp_credit_consumed, wcbp_credits_low, and wcbp_credits_topped_up (the last two re-emitted from the SDK's wbcom_credits_low / wbcom_credits_topped_up).

WP Career Board Pro - Shortcode Reference

Every frontend block in Pro ships as a shortcode so site owners can drop them into Elementor / Beaver / Bricks / Divi / classic editor / theme template hooks without leaving their page-builder surface. Same attribute-forwarding contract as Free - see wp-career-board/docs/SHORTCODES.md for the casting rules.

Pro requires the Free plugin. Pro shortcodes only register when the Free plugin is loaded and a valid Pro license is active.


All Pro shortcodes (1.1.0 - full block-to-shortcode coverage)

Shortcode Block Common attributes
[wcbp_resume_form_simple] wcb/resume-form-simple (see detailed section below)
[wcbp_resume_archive] wcb/resume-archive perPage, boardId, orderBy
[wcbp_resume_search_hero] wcb/resume-search-hero headline, subheadline, bgImage
[wcbp_resume_builder] wcb/resume-builder (auto-scoped to current candidate)
[wcbp_resume_single] wcb/resume-single resumeId (override post context)
[wcbp_resume_map] wcb/resume-map boardId, radius, centerLat, centerLng
[wcbp_credit_balance] wcb/credit-balance showHistory, showBuyButton
[wcbp_job_alerts] wcb/job-alerts mode (subscribe|manage)
[wcbp_job_map] wcb/job-map boardId, radius, centerLat, centerLng
[wcbp_application_kanban] wcb/application-kanban jobId (required)
[wcbp_my_applications] wcb/my-applications (auto-scoped to current candidate)
[wcbp_open_to_work] wcb/open-to-work (renders the toggle for the current user)
[wcbp_board_switcher] wcb/board-switcher style (select|tabs|pills)
[wcbp_featured_candidates] wcb/featured-candidates limit, boardId, showSkills
[wcbp_featured_companies] wcb/featured-companies limit, showJobCount
[wcbp_ai_chat_search] wcb/ai-chat-search placeholder, boardId

Numeric strings cast to int, "true"/"false" cast to bool, every- thing else stays a string. So [wcbp_application_kanban jobId="123"] produces the same output as the block with {jobId: 123}.


[wcbp_resume_form_simple] - Quick Profile Form

Single-page sibling of the multi-section resume builder. Captures the high-signal profile fields on one screen so candidates can complete a useful profile in 60 seconds and apply to jobs straight away.

Captures: headline, summary, top skills, years of experience, location, open-to-work toggle, and profile photo.

Attributes

Name Type Default What it does
showPhotoField bool true Show the profile-photo upload. Set false for sidebar embeds.
compact bool false Dense layout (smaller paddings) for sidebar embeds.

Examples

[wcbp_resume_form_simple]
[wcbp_resume_form_simple showPhotoField="false" compact="true"]

Hooks

  • wcb_resume_form_fields - declarative custom-field schema (shared with the multi-section resume builder).
  • wcb_resume_form_simple_initial_state - extend the Interactivity API state.
  • wcb_resume_form_simple_extra_fields - render custom inputs after the built-in fields.

When to use this vs. the multi-section builder

  • Multi-section builder - best on the dedicated "Edit My Resume" page. Long-form, broken into 7 sections (summary, experience, education, skills, certifications, languages, portfolio), good for completeness.
  • Single-page ([wcbp_resume_form_simple]) - best at the moment of signup, in modals over job-listing pages, in onboarding flows, on partner-site embeds. Optimised for "good enough to apply", not completeness.

[wcbp_resume_archive] - Find Candidates

Public "Find Candidates" listing page. Displays avatar, name, headline, top skills, location, and an Open-to-work badge for every publicly visible resume.

Mirrors the chip-bar / sort / search pattern of the Free [wcb_job_listings] block, so candidate search and job search feel identical.

Attributes

Name Type Default What it does
perPage int 12 Page size for the resume grid.
authorId int 0 Limit to a single candidate (pre-filtered embed).
includePrivate bool false Include resumes the candidate has marked private. Only respected for users with the wcbp_view_all_resumes ability.

Examples

[wcbp_resume_archive]
[wcbp_resume_archive perPage="9"]
[wcbp_resume_archive perPage="6" authorId="42"]

[wcbp_credit_balance] - Employer Credit Balance

Employer-facing card showing the current credit balance, a "Buy Credits" button (links to the configured purchase URL - WooCommerce, PMPro, or MemberPress), and an optional recent-transaction history.

Renders nothing for users without the wcb_post_jobs ability.

Attributes

Name Type Default What it does
showHistory bool true Show the recent-transaction list under the balance.
historyCount int 5 How many history rows to show when showHistory is true.

Examples

[wcbp_credit_balance]
[wcbp_credit_balance showHistory="false"]
[wcbp_credit_balance historyCount="10"]

[wcbp_job_alerts] - Job Alerts Subscription

Renders the "Subscribe to alerts for these filters" form when placed above/below a job-listings block - picks up the active filters from the shared Interactivity store. Below the form, lists the candidate's active alerts with a frequency picker (instant / daily) and a delete control.

Renders nothing for logged-out users (links to the registration form).

Attributes

This shortcode takes no attributes today.

Examples

[wcbp_job_alerts]

Existing Pro blocks (no shortcode)

These Pro blocks are inserter-only in 1.1.0 (they're typically reached via the dashboards that already host them):

  • wcb/application-kanban - ATS-style application pipeline (admin / employer dashboard).
  • wcb/featured-candidates - homepage strip of featured candidates.
  • wcb/featured-companies - homepage strip of featured companies.
  • wcb/my-applications - candidate's own applications list.
  • wcb/open-to-work - toggle pill for the candidate dashboard.
  • wcb/resume-builder - multi-section resume builder.
  • wcb/resume-search-hero - search hero above the resume archive.
  • wcb/resume-single - single-resume public view.

If you need any of these as a shortcode, file an issue - adding one is a two-line change to register_shortcodes().


Shortcode attributes - what gets cast where

Same contract as the Free plugin. See wp-career-board/docs/SHORTCODES.md.

WP Career Board Pro - Hook Reference

Pro extends Free's hook surface. Most theme integrators will only need the shared wcb_* family documented in wp-career-board/docs/HOOKS.md - those filters work the same whether the form is in Free or Pro.

This document covers Pro-only hooks (wcbp_*). They power Pro's internal modules (credits, resumes, alerts, AI, kanban) and may change between Pro releases - prefer wcb_* hooks where possible.

Pro-side form-field filters

Resume Builder + Resume Form Simple both read the shared wcb_resume_form_fields filter (see the Free hooks doc for the schema). That's the recommended way to add custom resume fields - one filter declaration covers both forms.

If you specifically need Pro-only behaviour:

Filter Purpose
wcb_resume_form_fields (shared with Free family) Declarative custom fields for resume-builder + resume-form-simple
wcb_resume_form_initial_state Pre-load Interactivity state on resume-builder
wcb_resume_form_simple_initial_state Pre-load state on resume-form-simple
wcbp_resume_primary_fields Override the 1-2 fields shown in the compact card view per resume section (Pro-internal)

Credits SDK consumer cost

Pro registers two consumers with the Wbcom Credits SDK: job_post and featured_upgrade. Tier-aware pricing hooks the shared wcbp_consumer_cost filter:

add_filter( 'wcbp_consumer_cost',
    function( $cost, $user_id, $item_id, $board_id, $consumer ) {
        if ( 'featured_upgrade' === $consumer && my_theme_user_is_premium( $user_id ) ) {
            return 0; // free for premium members
        }
        return $cost;
    }, 10, 5
);

The BpTieredCreditCost integration already hooks this filter to read the wcbp_credit_cost_matrix option (BP member type / PMPro level / MemberPress membership rules) - your filter runs alongside.

Lifecycle actions

Action Args When fired
wcb_featured_upgrade_requested (int $job_id, int $user_id) Hold credits for an upgrade
wcb_featured_upgrade_completed (int $job_id, int $user_id) Deduct credits, set _wcb_featured
wcb_featured_upgrade_failed (int $job_id, int $user_id, string $reason) Refund credits
wcbp_credits_topped_up (int $user_id, int $amount, string $source) Credits added
wcbp_credits_low (int $user_id, int $balance) Balance below threshold
wcbp_send_alert_email (int $alert_id, int $user_id, array $matches) Job-alert digest
wcbp_resume_published (1.1.0) (int $post_id, WP_Post $post) Resume transitions to publish status
wcbp_field_value_saved (1.1.0) (int $post_id, string $field_key, mixed $value) Custom field value persisted
wcbp_credit_consumed (1.1.0) (int $user_id, int $amount, int $item_id) Credit deduction committed (mirrors SDK's wbcom_credits_deducted under the Pro namespace)

Extension surface for addons (1.1.0)

Filters that let addon vendors plug into Pro the same way Pro plugs into Free.

Filter Args Purpose
wcbp_ai_provider_drivers (array $registry, string $api_key, string $base_url) Map of provider_slug => factory_callable. Add Cohere / Mistral / vLLM / etc. without touching the AI module's match.
wcbp_alert_dispatch_channels (array $channels) Map of channel_slug => callable($alert, $job_ids). Default is email (which fires wcbp_send_alert_email); addons add Slack / Discord / SMS / push.
wcbp_resume_pdf_renderer (callable|null $renderer, int $post_id) Return a callable accepting ($html, $post_id) to swap DomPDF for mPDF / Prince / a service.
wcbp_kanban_card_columns (array $columns, int $job_id) Mutate the Kanban column array before /jobs/(id)/kanban returns it - add archived / on-hold columns or annotate existing ones.
// Example: register a 4th AI provider via the wcbp_ai_provider_drivers filter.
add_filter( 'wcbp_ai_provider_drivers', function ( array $registry, string $api_key ): array {
	$registry['mistral'] = static fn() => new My\Addon\MistralDriver( $api_key );
	return $registry;
}, 10, 2 );

REST response filters (wcb_rest_prepare_*)

Pro extends the canonical wcb_rest_prepare_* pattern that Free established (see Free hook reference - REST response filters). Each prepared Pro resource fires a sibling filter so addons can decorate responses without touching Pro internals.

Filter Resource Args Purpose
wcb_rest_prepare_resume Resume (array $data, WP_Post|null $resume, WP_REST_Request $request, string $context) Decorate the prepared resume response. context is single, list, archive (public archive), create, save, or pdf_replace.
wcb_rest_prepare_board Board (array $data, WP_Post $board, WP_REST_Request $request) Decorate the prepared board response (GET /boards/{id}).
wcb_rest_prepare_board_stage Board stage (array $stage, WP_REST_Request $request) Decorate each prepared stage row in GET /boards/{id}/stages.
wcb_rest_prepare_notification Notification (array $data, object $row) Decorate each prepared notifications-bell row.

Example - flag the candidate's primary resume in the dashboard list:

add_filter( 'wcb_rest_prepare_resume', function ( array $data, $resume, $request, string $context ): array {
    if ( 'list' !== $context || ! $resume instanceof WP_Post ) {
        return $data;
    }
    $data['is_primary'] = (bool) get_post_meta( $resume->ID, '_my_primary_resume', true );
    return $data;
}, 10, 4 );

REST endpoints

Pro adds the following endpoints under wcb/v1/:

  • GET/POST /alerts, GET/PUT/DELETE /alerts/(id)
  • POST /ai/match, GET /candidates/(id)/matches, GET /ai/ranked-applications/(job_id), POST /jobs/ai-description
  • PUT /applications/(id)/stage, GET /jobs/(id)/kanban
  • GET /credits/packages, GET /employers/(id)/credits
  • GET/POST /fields/groups, GET/PUT/DELETE /fields/groups/(id), GET/POST /fields/groups/(group_id)/fields, GET/PUT/DELETE /fields/(id), POST /fields/reorder
  • GET /geocode
  • GET/PUT /boards/(id), GET/POST /boards/(id)/stages, PUT/DELETE /boards/(id)/stages/(stage_id)
  • GET/POST /resumes, GET /candidates/(id)/resumes, GET/PUT/DELETE /resumes/(id), GET /resumes/(id)/pdf
  • GET /notifications, POST /notifications/(id)/read, POST /notifications/read-all

Same permission_callback pattern as Free - abilities API only, no current_user_can('manage_options') fallbacks.

BuddyPress integrations

Pro adds these BP-specific extension points:

Hook Purpose
wcbp_bp_profile_tab (option, not hook) Toggle the candidate/employer profile tabs
wcb_moderate_jobs_ability_check (shared) Grant moderation rights for a specific job; BpGroupBoards uses this to give group admins moderation rights for jobs on their group's board

Convention

  • wcb_* - customer-facing extension surface, shared with Free. Stable.
  • wcbp_* - Pro-internal hooks. May change between Pro releases.

When extending a form, prefer the shared wcb_* family unless you specifically need a Pro-internal behaviour.

For the field-group schema and form-fields filter list, see Free hook reference.

REST controller carve-outs

Pro endpoints extend WCB\Pro\Api\Pro_REST_Controller and ship as classes under WCB\Pro\Api\Endpoints\, registered through the central register_rest_routes() loop in core/class-pro-plugin.php.

One Pro class carves out: admin/class-pro-setup-wizard.php (lines 167, 184, 206) registers /wizard/activate-license, /wizard/setup-credits, and /wizard/create-pro-pages directly via register_rest_route(). It still extends WCB\Api\RestController, but its routes live with the admin wizard rather than in api/endpoints/ because they're step handlers for that wizard.

The full carve-out rationale and the matching Free exceptions (setup wizard + moderation module) are documented in Free hook reference - REST controller carve-outs. New Pro routes should still ship as Endpoint classes.

AI Providers and Endpoints

This reference covers WP Career Board Pro 1.7.0.

The AI hiring tools (applicant ranking, fit scores, applicant TL;DR summaries, candidate-to-job matching, AI job-description and cover-letter writing) live in modules/ai/. They are driver-based: one driver per provider, all behind a common interface, with a filter to register a fourth provider from an addon.

Built-in providers: Anthropic Claude, OpenAI, and self-hosted Ollama. Providers are chosen per task - an analysis and ranking provider (Claude, OpenAI, or Ollama) and an embedding/matching provider (OpenAI or Ollama) - each with its own key, under Settings > AI Settings.

REST endpoints

All AI routes live under the shared wcb/v1 namespace and are registered by api/endpoints/class-ai-endpoint.php. Every route is gated by the Pro REST permission wrapper plus a capability check (license never gates them - see the licensing note below).

Method Route Permission Purpose
POST /wcb/v1/ai/match logged in Score how well a resume matches a job.
GET /wcb/v1/candidates/{id}/matches own or admin "Recommended for you" jobs for a candidate (needs an embedding provider).
GET /wcb/v1/ai/ranked-applications/{job_id} can view that job's applications AI-ranked applicant list for a job, best fit first.
POST /wcb/v1/jobs/ai-description can post jobs Generate a job description.
POST /wcb/v1/jobs/{job_id}/ai-cover-letter logged in Generate a tailored cover letter from the candidate's resume and the job.

Fit scores, reasons, and summaries are cached per application (in the _wcbp_ai_fit_score, _wcbp_ai_fit_reason, and _wcbp_ai_summary meta family), so re-opening the Employer Dashboard never re-bills the model; ranking computes only what is missing.

Auto-scoring on submit

When "Auto-score applicants on submit" is enabled (Settings > AI Settings, option wcbp_ai_auto_rank), Pro schedules a single cron event to score each new application in the background:

wp_schedule_single_event( time() + 30, 'wcbp_ai_score_application', array( $app_id ) );

The handler AI_Module::run_scheduled_scoring() listens on the wcbp_ai_score_application action. To trigger scoring yourself, fire that action with an application id.

The driver interface

Every provider implements AiDriverInterface (modules/ai/class-ai-driver-interface.php):

interface AiDriverInterface {
    public function embed( string $text ): array|\WP_Error;     // vector for matching
    public function complete( string $prompt ): string|\WP_Error; // text generation
    public function provider_name(): string;
}

The built-in drivers are ClaudeDriver, OpenAiDriver, and OllamaDriver, all in modules/ai/.

Adding a fourth provider

The driver registry is a provider_slug => factory_callable map run through the wcbp_ai_provider_drivers filter. Add a slug to ship your own provider:

add_filter( 'wcbp_ai_provider_drivers', function ( array $drivers, string $credential ) {
    $drivers['my_llm'] = static function () use ( $credential ): \WCB\Pro\Modules\Ai\AiDriverInterface {
        return new \MyAddon\Ai\MyLlmDriver( $credential );
    };
    return $drivers;
}, 10, 2 );

Then make your provider selectable by registering it in the AI settings UI, or set the relevant option (wcbp_ai_completion_provider / wcbp_ai_embedding_provider) to your slug.

If your provider does not need an API key (for example a self-hosted endpoint), tell Pro via the wcbp_ai_provider_requires_api_key filter:

add_filter( 'wcbp_ai_provider_requires_api_key', function ( bool $requires, string $provider ) {
    return 'my_llm' === $provider ? false : $requires;
}, 10, 2 );

Model selection

Each provider's model is configurable so you can trade quality for cost. For Claude, the model is resolved through the wcbp_ai_claude_model filter (default reads the wcbp_ai_anthropic_model option). To force a model:

add_filter( 'wcbp_ai_claude_model', fn() => 'claude-3-5-haiku-latest' );

Output and input filters

Filter Args Use to
wcbp_candidate_resume_data $user_id Override the grouped resume data Pro feeds the model when scoring or matching a candidate. Connecting this is what makes fit scores real instead of zero.
wcbp_ai_candidate_matches $matches, $user_id Post-process the candidate match results before they are returned.
wcbp_ai_ranked_applications $ranked, $job_id Post-process the applicant ranking before it is returned.

Licensing

Like every Pro feature, the AI tools are not gated on the license. The license drives automatic updates only; once Pro is installed and a provider key is set, the AI endpoints work regardless of license status. See 02-extending-free.md for the contract.

Something unclear? Open a support ticket → · Refund policy

Buy WP Career Board Pro