---
title: "Workable application form on WordPress"
description: "Set up the Workable application form on WordPress: the fields it shows, what happens on submit, the Submissions log and the developer hooks."
source: https://workable.w4dev.com/docs/on-site-applications/
---

With an API token, candidates apply on your job pages and arrive in Workable as candidates on
that job.

## Turn on on-site applications

1. [Add a Workable API token](/docs/installation/#4-optional-add-a-workable-api-token) with the
   `r_jobs` and `w_candidates` scopes.
2. Leave **Workable → Settings → Applications** on **Automatic**, or choose **On-site
   application form**.

The form replaces the **Apply on Workable** button on every job page. Each job's `JobPosting`
markup then includes `directApply: true`, which tells Google that candidates can apply on the
page.

<figure class="ak-shot">
  <img src="/screenshots/job-detail-apply.webp" alt="Job page apply area showing the Apply on Workable button used when no API token is configured" width="1200" height="844" loading="lazy" decoding="async" />
  <figcaption>Without a token, job pages show the Apply on Workable button; once you save a token, the on-site application form appears in its place.</figcaption>
</figure>

## What the Workable application form asks

Each job asks the questions you set up for it in Workable. The fields appear in this order:

1. **First name, Last name, Email, Phone.** Always shown. Name and email are required; phone
   only when the job asks for it in Workable.
2. **Resume and Cover letter.** Offered after your details unless the job's Workable form
   places them; then Workable decides the position and whether they are required.
3. **The job's other profile fields**, such as headline, summary, address, experience and
   education.
4. **The job's screening questions.**
5. **The consent checkbox**, when turned on under
   [**Workable → Settings → Privacy**](/docs/settings/#privacy-consent-checkbox).

Workable field and question types become these form inputs:

| Workable type | Form input |
|---|---|
| Short text | Text box |
| Free text | Text area |
| Multiple choice, one answer | Radio buttons |
| Multiple choice, several answers | Checkboxes |
| Dropdown | Dropdown |
| Yes/No | A checkbox, or Yes and No radio buttons when the question is required |
| Number | Number box |
| Date | Date picker |
| File | File upload |
| Experience, Education | A group of fields per entry, with **Add another** and **Remove** buttons, up to 5 entries when Workable allows more than one |

Two things are not shown: the profile photo, and file fields inside an experience or education
entry.

Forms are cached for an hour. After you change a job's questions in Workable, use **Clear
cache** under **Workable → Settings → Tools** to show the change straight away.

### File uploads

A file field accepts the file types and size Workable lists for it. When Workable does not say,
the defaults are PDF, DOC, DOCX, TXT, RTF and ODT files up to 5 MB. Your server's PHP upload
limit applies on top, so check `upload_max_filesize` if large files fail.

Uploaded files are read into the application and deleted from your server before it is sent to Workable.

## What happens when someone applies

The form submits in the background and shows the result without reloading the page. Without
JavaScript it submits as a normal form and works the same way.

Before anything is sent to Workable, the plugin checks:

- a rate limit of 10 attempts per 10 minutes from one IP address, or one IPv6 /64 network.
  Every attempt counts, including ones that fail a check. Behind a proxy, set the real visitor
  address with the `wkjb_client_ip` filter, or all applicants share one allowance;
- that the job is still open on your site;
- a hidden honeypot field that bots tend to fill in;
- the [Turnstile or reCAPTCHA](/docs/settings/#spam-protection-turnstile-or-recaptcha) answer,
  when one is configured;
- required fields, file types and file sizes.

Then the application goes to Workable, and the applicant sees one of these outcomes:

| Outcome | What the applicant sees |
|---|---|
| Workable accepts it | "Thank you — your application has been submitted." |
| Workable rejects a field | The fields Workable named are highlighted, with "Please correct the highlighted fields and try again." |
| Workable is over its rate limit | "Workable is busy right now. Please try again in a moment." |
| Anything else | A general error. The details are recorded in **Submissions**. |

Once Workable accepts it, the job's **Applications** count goes up by one, and the
[application emails](/docs/settings/#application-emails-hiring-team-and-applicant) go out if you
turned them on.

## When the token stops working

The plugin uses the token for two things only: fetching a job's application form when someone
opens the job, and sending an application. Job lists and job pages never use it.

Workable allows each token 10 requests every 10 seconds. The hourly form cache keeps a busy job
page to about one request an hour.

If Workable rejects the token, for example after it expires:

- **While loading a form**, that job shows the **Apply on Workable** button instead, so
  candidates can still apply.
- **While sending an application**, the form stays up but the application fails.

Either way, an admin notice names the problem and the fix: generate a new token with the
`r_jobs` and `w_candidates` scopes and save it under **Workable → Settings**. After a rejection
the plugin waits five minutes before asking Workable again. Other failed form loads, such as
Workable being busy or unreachable, are retried after a minute.

## The Submissions log

**Workable → Submissions** lists the last 100 application attempts, newest first, while **Log
submissions** is on under **Workable → Settings → Features**. Each entry shows:

- the time, the job and its shortcode;
- the applicant's name and email;
- **Delivered**, **Failed** or **Email failed**, with the message;
- the applicant's IP address, shortened;
- for a delivered application, a **View candidate in Workable** link.

Answers and files are never stored on your site; they go only to Workable. You can filter the
list by status, and it counts attempts the honeypot or the rate limit blocked. **Clear log** empties it.

Entries are included in WordPress's **Tools → Export Personal Data** and **Tools → Erase
Personal Data**, matched by the applicant's email.

If the WP Logs plugin is active, each attempt is also written to its log, without the
applicant's name or email.

## Developer hooks

| Hook | Type | Use it to |
|---|---|---|
| `wkjb_application_fields` | filter | Change the whole form: add, remove or reorder groups of fields. Receives the groups and Workable's raw form. |
| `wkjb_map_application_field` | filter | Change how one Workable profile field becomes a form input. |
| `wkjb_map_application_question` | filter | Change how one Workable question becomes a form input. |
| `wkjb_candidate_payload` | filter | Change the candidate data sent to Workable. Receives the payload, the field groups and the submitted values. |
| `wkjb_allowed_file_types` | filter | Change the default file extensions, used when Workable does not list its own. |
| `wkjb_max_upload_size` | filter | Change the default maximum upload size, in bytes. Default 5242880 (5 MB). |
| `wkjb_application_validation_errors` | filter | Add your own field checks. |
| `wkjb_application_success_message` | filter | Change the thank-you message. |
| `wkjb_application_rate_limit` | filter | Change the number of application attempts allowed per IP address in each window. Default 10; `0` turns the limit off. |
| `wkjb_application_rate_window` | filter | Change the rate limit window, in seconds. Default 600, minimum 60. |
| `wkjb_application_form_ttl` | filter | Change how long a job's form is cached, in seconds. Default 3600. |
| `wkjb_application_form_failure_ttl` | filter | Change how long a rejected token is remembered before Workable is asked again, in seconds. Default 300. |
| `wkjb_application_form_error_ttl` | filter | Change how long any other failed form load is remembered, in seconds. Default 60. |
| `wkjb_repeater_max_entries` | filter | Change how many experience or education entries the form accepts. Default 5. |
| `wkjb_submission_log_max` | filter | Change how many attempts the Submissions log keeps. Default 100. |
| `wkjb_client_ip` | filter | Change how the applicant's IP address is detected, for example behind a proxy. |
| `wkjb_recaptcha_score_threshold` | filter | Change the minimum reCAPTCHA v3 score. Default 0.5. |
| `wkjb_application_submitted` | action | Run code after Workable accepts an application. Receives the job shortcode, the job, Workable's candidate record and the applicant's name, email and phone. |
| `wkjb_application_logged` | action | Run code after any attempt is logged. Receives the log entry. |
| `wkjb_admin_notification_email` | filter | Change the hiring team email, or return `false` to skip it. Receives the `to`, `subject`, `message` and `headers`, the job, the applicant and Workable's candidate record. |
| `wkjb_applicant_confirmation_email` | filter | The same for the applicant's confirmation. |

Example: post to a Slack channel whenever Workable accepts an application.

```php
add_action( 'wkjb_application_submitted', function ( $job_id, $job ) {
	wp_remote_post(
		'https://hooks.slack.com/services/XXXX/XXXX/XXXX',
		array(
			'headers' => array( 'Content-Type' => 'application/json' ),
			'body'    => wp_json_encode( array( 'text' => 'New application: ' . $job['title'] ) ),
		)
	);
}, 10, 2 );
```