Skip to content
VakanssiDocs
VakanssiDocs
Get Vakanssi
esc

Shortcodes & block

Reference for the three Vakanssi shortcodes ([vakanssi_jobs], [vakanssi_job_details], [vakanssi_job_apply]) and the Job Listings block, with every attribute verified against the source.

On this page(16)

Vakanssi registers exactly three shortcodes plus one Gutenberg block. [vakanssi_jobs] renders a filterable job listing anywhere; [vakanssi_job_details] and [vakanssi_job_apply] embed a single job’s facts block and apply call-to-action outside its own page; and the Job Listings block (vakanssi/jobs) is a no-build editor wrapper over [vakanssi_jobs].

Sites running Elementor or Divi also get a native Vakanssi Jobs element for that builder; Elementor also gets native Application Form and Job Details widgets. All output is server-rendered HTML (see Asset loading).

If you just want a careers page and do not care about attributes, read the beginner walkthrough at Your careers page instead. The built-in /jobs/ archive already renders the default listing with zero setup.

[vakanssi_jobs]

Renders a listing of published jobs, by default the 20 newest, excluding jobs whose closing date has passed. Since v0.35.3, password-protected jobs are always left out too: a listing has no way to unlock one per visitor. This is the same renderer the /jobs/ careers archive and the Job Listings block use internally, so everything below also describes those surfaces.

Attributes

All 18 attributes, with their type and default:

AttributeTypeDefaultWhat it does
departmentstring (CSV of term slugs)""Shows only jobs assigned any of these Department term slugs (OR between slugs). Combined with location, jobs must match both. Matches the taxonomy, not any text field.
locationstring (CSV of term slugs)""Shows only jobs assigned any of these Location term slugs. This matches the Location taxonomy, not the job’s address fields. See the note below.
limitint20Number of jobs shown, clamped to 1 through 100 (values of 0 or less become 1, values over 100 become 100). Since v0.33.7, this is the page size when the listing paginates (see paginate below); with paginate="false" it is the fixed cap it always was.
paginatebool stringtrueSince v0.33.7. On a static Page, splits the listing into pages of limit roles with pagination controls below the list, instead of stopping silently at limit. See Pagination below.
orderbystringdateSort key: date, title, menu_order, or rand. Any other value falls back to date. date adds a post-ID tiebreaker in the same direction so the order is deterministic; rand ignores order.
orderstringDESCSort direction. Only an exact (case-insensitive) ASC sorts ascending; every other value means DESC.
includestring (CSV of post IDs)""Shows only these job IDs. Each is cast to a positive integer; empty entries are dropped.
excludestring (CSV of post IDs)""Removes these job IDs from the listing. Same sanitization as include.
searchstring""Keyword search across job title and content (standard WordPress s query).
include_expiredbool stringfalseBy default only jobs with no closing date, an empty one, or a closing date of today or later (site timezone) are listed. Set true to list expired jobs too. Their pages stay up, but the apply call-to-action there reads “This position is now closed.” Since v0.33.3, when the result actually includes closed roles, the count line reads for example “8 roles, 2 closed” instead of calling all of them open roles, and keeps saying so as a visitor narrows the list with the toolbar.
show_locationbool stringtrueShows the per-row location fact: the job’s display label, otherwise “locality, country”. A remote job reads “Remote” here only when show_workplace="false"; with the workplace badge on, the badge says Remote and this fact shows the real place, if any. Hidden when there is nothing to show.
show_typebool stringtrueShows one employment-type label per row (the first recognized type when a job has several). Also hidden on every row when Employment type is unchecked under Job details in Settings, then Jobs & Careers.
show_workplacebool stringtrueAdds a Remote or Hybrid badge to a row’s facts when the job is workplace type remote or hybrid. On-site jobs get no badge (the location already says where; labeling every office job “On-site” is noise). With the badge showing, it takes over the “Remote” accent that the location fact used to carry alone; with show_workplace="false", a remote job’s location fact still reads “Remote” exactly as before.
layoutstringlistOnly the exact value grid adds the vakanssi-jobs--grid class to the <ul>. Both layouts share identical markup; the difference is CSS only.
filtersbool string, or comma listtrueRenders the search / team / remote / sort command bar above the list. true (and any legacy truthy string) shows all four controls; false omits the toolbar markup entirely. A comma-separated list of search, department (alias team), remote, sort shows only those, in that fixed order regardless of how you list them. The bar is server-rendered and always visible; with JavaScript off its controls do nothing, but every role is still listed. See Choosing which filter controls appear below.
empty_messagestring""Shown (escaped) instead of the default “There are no open positions right now.” when no jobs match.
archivebool stringfalseInternal: set archive="main" (or any truthy value) to make the listing consume WordPress’s own paginated main query instead of building a fixed-size one of its own, but only when the shortcode is actually rendering on the /jobs/ archive. This is what lets /jobs/page/2/ work; the built-in archive template and the Job Listings block already set it for you (see below). Anywhere else, this attribute has no effect.
apply_buttonbool string"" (unset, follows the site setting)Since v0.33.3. Adds an Apply control to every eligible row. Left unset, it follows Hiring, then Settings, then Jobs & Careers, then Careers listing; set true or false to override that setting for this listing only. See The optional Apply button below.

Boolean attributes: use only true or false

The boolean attributes that affect output (include_expired, show_location, show_type, show_workplace, archive, paginate, and a set apply_button) are parsed with WordPress’s wp_validate_boolean(). The strings false (any case), 0, and "" are false. Any other non-empty string is true, including no. So show_type="no" still shows the employment type. Stick to true and false.

filters follows its own, looser rule: any recognized control name (or a name it doesn’t recognize, like a typo) still narrows or shows the bar as described above; only an explicit falsy string (false, 0, no, off, none, or empty) turns it off entirely.

Choosing which filter controls appear

The command bar holds four controls, and filters decides which of them render:

NameControlAlias
searchSearch boxnone
departmentTeam pickerteam
remote”Remote only” togglenone
sortSort (Newest / A-Z / By team)none

They always render in that order, whatever order you list them in. Unrecognized names are dropped rather than fatal, so filters="search,serach" costs you one control, not the page. A value with nothing recognizable in it (filters="y", or a lone typo) falls back to showing the whole bar, the same as it always has, since versions before 0.29.0 read any non-empty, non-falsy string as “show it”.

Two behaviors worth knowing:

  • The team picker only appears when the listing spans more than one team, whether or not you ask for it. Asking for department alone on a single-team listing renders no bar at all, rather than an empty one. On a paginated listing, “spans more than one team” counts every page it can reach, per Pagination below.
  • The controls filter the rendered rows in the browser. With JavaScript disabled the bar still renders but its controls do nothing; every role is still listed, since the list itself is server-rendered.

The Job Listings block and the Elementor widget expose the same choice as checkboxes; see below and Vakanssi Jobs (Elementor and Divi).

[vakanssi_jobs filters="search,sort"]

To decide it programmatically instead, per template, per language, or site-wide, use the vakanssi/jobs/bar_controls filter documented below.

location matches the Location taxonomy, not the address fields

A job carries two different kinds of location data, and the location attribute reads only one of them:

  • Location taxonomy terms, the coarse labels (“Remote”, “Tel Aviv”, “London”) you assign in the Locations panel of the job editor. This is what location matches, by term slug.
  • Address fields, the free-text City / locality, Region, Country, and optional Display label in the Job details box. These feed the visible facts block and the Google for Jobs schema, but are never queried by this attribute.

So [vakanssi_jobs location="tel-aviv"] matches jobs assigned the “Tel Aviv” Location term. A job whose City says “Tel Aviv” but has no Location term assigned will not match. The same distinction applies to department and Department term slugs. A term’s slug is normally the lowercased, hyphenated form of its name (“Tel Aviv” becomes tel-aviv).

The optional Apply button (apply_button)

Since v0.33.3, apply_button="true" adds an Apply control to each eligible row, so a candidate can go straight to applying without opening the job first.

It renders as a sibling of the row link rather than nested inside it (an <a> inside an <a> is invalid markup), on class vakanssi-job-apply, inside a row that gains the vakanssi-job--apply class; the row’s usual chevron is dropped for that row, since offering two “go” affordances at once is clutter.

The control reads the same eligibility check as the job page’s own apply call-to-action (JobEligibility::apply_state()), so the listing can never disagree with the page: an external apply_url renders an “Apply” button to that URL, opened in a new tab.

A job with the built-in intake form links straight to #vakanssi-apply on its own page; a role that is closed, or not yet published, or has no apply path at all renders no control, just the plain row.

[vakanssi_jobs apply_button="true"]

The Job Listings block and the Elementor and Divi widgets do not expose this attribute; both always follow the site setting.

Pagination

Since v0.33.7, [vakanssi_jobs] paginates on a static Page: the wizard’s careers page, a hand-placed shortcode or block, or the static front page. It shows limit roles per page (20 by default) with a <nav class="vakanssi-jobs-pagination"> below the list. /page/2/ works under pretty permalinks, ?paged=2 under plain ones.

Since v0.33.10, that nav is a range readout (“Showing 21–40 of 112 roles”), a “Page 2 of 6” counter, and two round Previous/Next arrow buttons, in place of core’s numbered paginate_links() output. It says where a candidate is in the roles rather than in the pages, and the arrows mirror under right-to-left languages.

Pagination only applies on a Page. A single job’s own page and the built-in /jobs/ archive are unaffected: the archive already paginates through its own main query when archive="main" resolves (see the archive row above). orderby="rand" never paginates either, since a fresh random sample on every page would skip and repeat roles instead of continuing the list.

The count line spans every page, not just the one being viewed. With include_expired="true" and more than one page, Vakanssi runs one extra lightweight query so the closed-role tally in the count line also covers the whole listing (“8 roles, 2 closed” stays accurate on every page, not just the page that happens to hold the closed roles).

Since v0.33.8, the search box and the team and remote filters reach every page, not just the one being viewed. The first time a visitor uses one, JavaScript quietly fetches the listing’s other pages and folds their rows in; the team picker also lists every team across the whole listing, not just this page. The pagination nav hides while a cross-page filter is active, and returns once it clears.

This only applies to a paginated listing of 10 pages or fewer (200 roles at the default page size); a longer listing’s filters cover the page being viewed only, since neither the extra pages nor the extra teams would honestly be reachable.

A page number past the end of the listing (a stale bookmark, a hand-typed URL) shows “No roles on this page,” with the nav’s Previous arrow leading back to the last real page, Next disabled, and no range readout, rather than a 404 or a false “There are no open positions” message.

If the listing has since shrunk to a single page, it shows that one page instead, since there is no longer a “back” to offer.

Since v0.33.7 this also fixes a routing bug: with the built-in archive off and a Page owning the job URL base (see The job URL base), /careers/page/2/ used to match the single-job URL pattern first and 404 as a job literally named “page”. The listing’s page links now resolve to the page correctly.

Canonical tag still points at page 1

WordPress core’s <link rel="canonical"> on a Page’s /page/2/ points at page 1, not page 2, since Pages have no per-page canonical concept. This is standard WordPress behavior, not specific to Vakanssi; each job is still indexed on its own URL regardless.

Set paginate="false" for a fixed-size teaser instead, for example a homepage strip of the three newest roles, or on the second of two listings placed on the same Page (they would otherwise share one page number).

Markup and extension points

The listing renders as <div class="vakanssi-jobs-board" data-vakanssi-jobs="N"> (N is the number of matching roles; on a paginated listing that is the total across every page, not just the rows on this one), an optional <div class="vakanssi-jobs-bar"> command bar, a <div class="vakanssi-jobs-meta"> count line, then a <ul class="vakanssi-jobs"> (plus vakanssi-jobs--grid in grid layout) of <li class="vakanssi-job"> rows.

Each row is an <a class="vakanssi-job-link"> whose <span class="vakanssi-job-main"> holds the department eyebrow (vakanssi-job-dept), the title (vakanssi-job-title), and a <span class="vakanssi-job-meta"> of <span class="vakanssi-job-fact"> items.

The facts are the workplace badge, the location, the employment type, and the salary (vakanssi-job-fact--salary). Salary has no attribute of its own: it shows when the job publishes one, unless Salary is unchecked under Job details in Settings, then Jobs & Careers.

When the listing paginates (archive="main" on the built-in archive, or paginate="true" on a Page, the default, see Pagination above) and there is more than one page, a <nav class="vakanssi-jobs-pagination"> follows the list.

Since v0.33.10, that nav holds a <p class="vakanssi-jobs-pagination__range"> range readout (omitted past the last page), a <span class="vakanssi-jobs-pagination__page"> page counter, and two <a class="vakanssi-jobs-pagination__arrow vakanssi-jobs-pagination__arrow--prev"> / --next buttons. At either end, the inactive arrow renders as a <span> carrying is-disabled instead of a link.

Four filters let you customize it:

  • vakanssi/jobs/query_args ( $args, $atts ): the assembled WP_Query arguments. On a paginated listing, $args already carries paged and no_found_rows => false; leave those alone, or the count line and the page nav lose the total they’re built from.
  • vakanssi/jobs/listing_row ( $row_html, $post, $atts ): the per-row HTML. Since v0.37.5, the return value is printed through wp_kses() with post markup plus inline SVG and <bdi> allowed, so data-* attributes survive but <script>, event-handler attributes, <form>, <input>, and <select> are stripped.
  • vakanssi/jobs/empty_state ( $default, $atts ): the no-results markup (for example, to swap in a newsletter CTA). Escaped the same way as the board around it. It is wrapped in a plain <div data-vakanssi-jobs="0">, without the board class, bar, or count line.
  • vakanssi/jobs/bar_controls ( $controls, $atts ): which command-bar controls render, resolved from filters and already in render order. Add or remove any of search, department, remote, sort; return [] to drop the bar entirely. Runs last, so it can even restore a bar where filters="false", but cannot force the team picker onto a single-team listing (that check runs after it). The return value is re-sanitized against the four known names.

Since v0.37.9, every one of the markup filters above (plus vakanssi/jobs/apply_markup, details_markup, and related_markup on the job page) is escaped this way; see How returned markup is escaped for the full allowlist.

See Hooks for stability markers and full signatures.

Examples

Default listing (20 newest open jobs, list layout, all facts and the filter bar on):

[vakanssi_jobs]

Grid layout (same jobs and markup, card-style CSS):

[vakanssi_jobs layout="grid"]

One department, by Department term slug:

[vakanssi_jobs department="engineering"]

Several departments at once (a job in either shows):

[vakanssi_jobs department="design,marketing"]

One location, by Location term slug (not the address field, see above):

[vakanssi_jobs location="tel-aviv"]

Keyword search across titles and descriptions:

[vakanssi_jobs search="engineer"]

Hand-picked jobs only, by post ID:

[vakanssi_jobs include="48,50"]

Everything except one job:

[vakanssi_jobs exclude="6"]

A short, chrome-free teaser (three most recent jobs, titles and type only, no toolbar). Since v0.33.7, add paginate="false" on a Page, or this becomes a 3-per-page paginated listing instead of a fixed teaser:

[vakanssi_jobs limit="3" paginate="false" filters="false" show_location="false"]

Ten roles per page on a Page, with pagination controls once there are more than ten open roles (pagination is on by default, so only limit changes here):

[vakanssi_jobs limit="10"]

Keep just the search box and sort, drop the team picker and remote toggle:

[vakanssi_jobs filters="search,sort"]

Alphabetical ordering:

[vakanssi_jobs orderby="title" order="ASC"]

Custom empty message when nothing matches:

[vakanssi_jobs department="finance" empty_message="Nothing in this team right now, check back soon or send us your CV."]

Include expired jobs alongside open ones (their apply CTA shows the closed notice):

[vakanssi_jobs include_expired="true"]

[vakanssi_job_details]

Renders one job’s facts block, the same <dl class="vakanssi-job-details"> that appears on the job’s own page.

AttributeTypeDefaultWhat it does
idint0Which job to render. On a single job page, 0 (or omitting it) falls back to the current job; anywhere else, 0 renders nothing. Since v0.35.3, a password-protected job renders nothing until the visitor has entered its password.
[vakanssi_job_details id="48"]

The block lists these rows, each omitted when the job has no value for it (or when the matching schema/detail field is hidden in Settings):

  • Location: remote jobs show “Remote”, or “Remote (Israel, United States)” when eligible countries are set (country names, not codes); other jobs show the display label, or “locality, region, country”.
  • Workplace: On-site, Hybrid, or Remote.
  • Employment: all selected employment-type labels, comma-joined.
  • Salary: omitted when salary display is Hidden or no currency is set; otherwise “ILS 28,000 per month” (exact) or “ILS 28,000–42,000 per month” (range), the currency shown once even when the two ends differ.
  • Closing date: in the site’s date format. Labelled “Closing date” for an open job, “Closed on” for an expired one.

Every value here is the same data the Google for Jobs JobPosting schema publishes. Google requires that structured-data values also be visible on the page, and this block is how Vakanssi satisfies that. The rows and final markup are filterable via vakanssi/jobs/details_rows ( $rows, $post_id ) and vakanssi/jobs/details_markup ( $html, $post_id, $rows ).

[vakanssi_job_apply]

Renders one job’s apply call-to-action, whatever “apply” currently means for that job.

AttributeTypeDefaultWhat it does
idint0Which job to render. On a single job page, 0 falls back to the current job. Renders nothing unless the resolved post is a published job. Since v0.35.3, it also renders nothing for a password-protected job until the visitor has entered its password.
[vakanssi_job_apply id="49"]

What renders, in priority order:

  1. Job expired (closing date passed, end of day in the site timezone): the closed notice, “This position is now closed.” (plus a “View current openings” link when a careers page is configured).
  2. External apply URL set on the job: an “Apply for this position” button opening the external URL in a new tab (with a screen-reader note that it does).
  3. Otherwise: the markup returned by the vakanssi/jobs/apply_markup ( $default, $post_id ) filter. The Application Intake module (on by default) hooks this to inject the built-in application form (name, email, phone, CV upload, cover letter, consent). See Receiving applications. With Intake disabled, the unfiltered default is shown: “Online applications for this position open soon.”

The Job Listings block (vakanssi/jobs)

The Job Listings block is a build-free editor shell over [vakanssi_jobs]. Its render callback maps the block attributes onto the shortcode-atts shape and delegates to the shared renderer, so the front-end HTML is identical to the shortcode. It lives in the Vakanssi block category, supports wide and full alignment, and has html editing disabled.

Its block.json exposes a subset of the shortcode attributes (no include, exclude, search, show_workplace, or empty_message), and department / location are arrays of term slugs picked from editor dropdowns rather than comma-separated strings:

Block attributeTypeDefaultMaps to shortcode attribute
layoutstringlistlayout
limitnumber20limit
paginatebooleantruepaginate
filtersbooleantruefilters (master switch)
filterControlsarray of strings["search","department","remote","sort"]filters (which controls, once the master switch above is on)
showLocationbooleantrueshow_location
showTypebooleantrueshow_type
orderbystringdateorderby
orderstringDESCorder
includeExpiredbooleanfalseinclude_expired
departmentarray of strings[]department (joined to CSV)
locationarray of strings[]location (joined to CSV)
{
    "name": "vakanssi/jobs",
    "title": "Job Listings",
    "category": "vakanssi",
    "attributes": {
        "layout":         { "type": "string",  "default": "list" },
        "limit":          { "type": "number",  "default": 20 },
        "paginate":       { "type": "boolean", "default": true },
        "filters":        { "type": "boolean", "default": true },
        "filterControls": { "type": "array",   "default": [ "search", "department", "remote", "sort" ], "items": { "type": "string" } },
        "showLocation":   { "type": "boolean", "default": true },
        "showType":       { "type": "boolean", "default": true },
        "orderby":        { "type": "string",  "default": "date" },
        "order":          { "type": "string",  "default": "DESC" },
        "includeExpired": { "type": "boolean", "default": false },
        "department":     { "type": "array",   "default": [], "items": { "type": "string" } },
        "location":       { "type": "array",   "default": [], "items": { "type": "string" } }
    }
}

Since v0.33.7, paginate behaves exactly as it does on the shortcode: on a Page it splits the list into pages of limit roles once there is more than one page’s worth, with pagination controls below it. See Pagination above. In the inspector, a Split into pages toggle (default on) controls it, and the Maximum roles slider is labeled Roles per page while it is on.

In the block inspector, Show search & filters is the master switch; toggling it on reveals four more toggles underneath, one per control (Search box, Team picker, Remote only toggle, Sort), bound to filterControls.

Content saved before filterControls existed carries no value for it, and is unaffected: WordPress merges a block type’s block.json defaults into the attributes before the render callback runs, so that older markup arrives already carrying all four. The default is the compatibility guarantee: narrowing it would empty the bar on every page saved before this setting shipped.

Because the Location taxonomy is not exposed in the REST API, the block localizes both taxonomies’ terms into its editor script instead of fetching them from the core data store. For richer control (include, exclude, search, per-row fact toggles, a custom empty message), use the shortcode.

The block paginates itself on the archive

When the block renders on the built-in /jobs/ archive (the Jobs archive Site Editor template places it there by default) and every query-shaping attribute is left at its default, department, location, limit, orderby, order, and include_expired, the block automatically sets archive="main" internally so it shares the archive’s paginated main query instead of a fixed 20-job list.

Give the block any non-default department, location, limit, orderby, order, or include_expired value and it goes back to a plain, unpaginated query, since a curated list is no longer “the whole archive”. Presentation-only attributes like layout, filters, paginate, or the show_* toggles never affect this.

Page-builder elements: Elementor and Divi

On a site running Elementor, Vakanssi registers three widgets grouped under a Vakanssi category in the widget panel: Vakanssi Jobs, Vakanssi Application Form, and Vakanssi Job Details. Divi gets a matching Vakanssi Jobs module only, listing-only, same as before. The integration only loads inside each builder’s own registration hook (elementor/widgets/register, et_builder_ready), so nothing extra runs on a site without that builder.

Vakanssi Jobs (Elementor and Divi)

Both builders expose the same settings: Layout (List or Grid), Number of jobs (1 to 100, default 20, the page size when Paginate is on), and, since v0.33.7, Paginate (on by default: splits the list into pages of that size, off for a fixed-size teaser).

They also share Departments and Locations (comma-separated slugs, empty for all), Show filters, Show location, Show employment type, a workplace toggle (Elementor labels it Show workplace badge (Remote / Hybrid), Divi Show workplace (remote/hybrid/on-site)), and Empty-state message. Only Elementor adds Filter controls, described below.

Both are thin shells over [vakanssi_jobs]: the settings compose a shortcode string (attributes left at their defaults are omitted), so the front-end HTML, CSS classes, and extension filters are identical to the shortcode and the block. For anything beyond these settings (include, exclude, search, orderby, order, include_expired), use the raw shortcode in the builder’s Shortcode or Code element.

  • Elementor: the widget is listed under the Vakanssi category (the panel search also finds it under “jobs”, “careers”, “vacancies”, or “hiring”).
    • It renders a live preview in the editor, styled and with its search / team / remote / sort toolbar working as on the published page. The widget declares careers.css and careers.js as Elementor style and script dependencies, so both load inside the editor iframe.
    • careers.js re-runs its toolbar initializer on Elementor’s frontend/element_ready event for the widget. The widget registers on elementor/widgets/register with support for both the 3.5+ and older registration APIs.
    • Once Show filters is on, a Filter controls multi-select appears (Search box, Team picker, Remote only toggle, Sort); clearing it means none, the same as unchecking every box in the Gutenberg block. A widget saved before this control existed picks up all four as its default. Divi’s Show filters stays a plain on/off toggle; it does not expose the per-control picker.
  • Divi: the module is listed as “Vakanssi Jobs” and registers on et_builder_ready. It is a PHP-only module: the front end renders fully and every setting is editable in the settings modal and wireframe mode, but the Visual Builder canvas shows Divi’s standard placeholder instead of a live preview.

The Show workplace toggle adds the Remote/Hybrid badge described in the show_workplace attribute row above.

Vakanssi Application Form and Vakanssi Job Details (Elementor only)

Two more Elementor widgets, vakanssi-job-apply (Vakanssi Application Form) and vakanssi-job-details (Vakanssi Job Details), render a single job’s apply call-to-action and facts block with no Shortcode widget needed. Both share one control, Job: a number field, default 0.

Job Details renders through [vakanssi_job_details] itself, so its markup is identical to the shortcode. Application Form calls JobDetails::apply_html() directly, the same renderer [vakanssi_job_apply] uses, but skips the shortcode’s published-only check, so a draft job renders the “Online applications for this position open soon.” notice instead of nothing (the form itself needs a published job).

Since v0.37.5 its output passes through wp_kses() with the apply-form allowlist before it is printed; the markup is unchanged.

Job resolution (WidgetContext::resolve_job_id(), shared by both widgets) mirrors the shortcodes’ own fallback:

  1. A non-zero Job value wins, but only when it actually points at a vakanssi_job post (of any status); an id left over from a deleted or repurposed post resolves to nothing rather than rendering the wrong post type.
  2. Otherwise, on a single job’s own page (an Elementor Pro Theme Builder Single, or a job built directly with Elementor), the current queried job is used.
  3. Otherwise, the widget renders nothing.

Both widgets now preview the same way in the editor, since neither’s output has a render-time side effect (the intake form’s anti-spam token is stateless, nothing is stored just by rendering it):

  • Application Form renders the real, styled form inside the Elementor editor canvas whenever a published job resolves, for example editing directly on a Theme Builder Single with a preview job set, or a specific Job ID entered. When no job resolves, the editor shows a dashed placeholder box and the front end prints nothing.
  • Job Details is pure display and behaves the same way: it previews live in the editor whenever a job resolves, and shows a placeholder when it doesn’t.

Both widgets declare the vakanssi-careers stylesheet as an Elementor style dependency (Application Form also declares the vakanssi-careers script), so the editor preview is styled like the published page instead of only picking up assets enqueued on the front end.

No Elementor dynamic tags

Vakanssi does not register Elementor dynamic tags for individual job fields (salary, location, and so on). Binding a value to a generic widget through the “dynamic” toggle is an Elementor Pro-only feature; free Elementor has no dynamic-tag UI. Use the Job Details widget, or [vakanssi_job_details], for field-level placement instead.

Placement and job IDs

On every single job page, Vakanssi automatically appends the details block and the apply call-to-action (plus an “Other open roles” section and a “View all open positions” link) after the job description, via the_content at priority 20, on the main query only, once per page.

Settings, then Jobs & Careers can drop the details block or the “Other open roles” section from this append. Since v0.35.3, nothing is appended to a password-protected job until the visitor has entered its password.

You never need [vakanssi_job_details] or [vakanssi_job_apply] on the job’s own page. They exist for embedding a single job elsewhere: a landing page, a page-builder layout, a team page featuring one opening.

Since v0.32.15, JobDetails::apply_html() renders at most once per job per request, however many of its three producers a page asks for it: the content append above, the [vakanssi_job_apply] shortcode, and the Vakanssi Application Form Elementor widget.

A Theme Builder Single that appends post content and places the widget, or a job whose content also contains the shortcode, now shows the form, the external apply button, or the closed notice once, wherever the page puts it first, instead of duplicating it.

The one exception is the Elementor editor canvas, which keeps re-previewing the form as you compose, even after a first render elsewhere on the same request.

To find a job’s ID, go to Hiring, then Jobs, open the job, and read the number after post= in the address bar (post.php?post=48&action=edit means the ID is 48).

Asset loading

Vakanssi loads one stylesheet, careers.css, and one small script, careers.js:

  • Both are registered up front but enqueued only when Vakanssi markup actually renders, so a page with no Vakanssi shortcode, block, or job template makes no extra request.
  • [vakanssi_jobs] (and the block) enqueue both the stylesheet and careers.js. The list is fully server-rendered; careers.js only powers the optional search / team / remote / sort toolbar (the filters attribute). [vakanssi_job_details] enqueues only the stylesheet. [vakanssi_job_apply] enqueues the stylesheet, plus careers.js when it renders the built-in application form (a double-submit guard; the form works without it).
  • Enqueued assets are cache-busted by file modification time, so a source edit reaches the browser without a version bump.
  • To style everything yourself, turn off the stylesheet under Hiring, then Settings, then Jobs & Careers (a false-src handle is registered instead, so the existing enqueue calls stay valid no-ops).
  • The only inline CSS Vakanssi attaches is its own brand-accent override, printed right after careers.css. Since v0.36.0 there is no Custom CSS box and no vakanssi/frontend/custom_css or vakanssi/frontend/custom_css_boost filter: write your own rules in Appearance, Customize, Additional CSS, which WordPress prints itself. The vakanssi- class names are unchanged, so existing rules keep working.

See Performance for the wider picture of what Vakanssi loads where, and Your careers page for the beginner-friendly version of this page.