Hire for Craft CMS

Templating

The listing

{{ craft.hire.listing() }}

That is a working careers page — search box, filters, results, pagination — rendered on the server, so it works with JavaScript off, is indexable, and every filter is a link you can paste into an email. A small script enhances it; it never creates content the server didn't render.

Options:

{{ craft.hire.listing({
    heading: 'Open positions',
    limit: 10,
    search: false,
    orderBy: 'expiryDate asc',
    workplaceType: 'remote',
}) }}
Option
headingA heading above the listing
limitJobs per page (default from Settings → General)
searchShow the search box
orderBypostDate, expiryDate, applications, title, dateCreated, dateUpdated or a custom field handle, then asc or desc
filtersFixed filters, as { groupHandle: ['term-slug'] }
workplaceType, formId, specId, siteIdPassed straight to the job query

Closed jobs are left out unless Keep closed jobs in the listing is on.

The data without the markup

{% set result = craft.hire.results({ limit: 10 }) %}

<p>{{ result.total }} jobs</p>
{% for job in result.jobs %}
    <a href="{{ job.url }}">{{ job.title }}</a> — {{ job.locationLabel }}
{% endfor %}

results() is the same call the built-in listing and the JSON endpoint make, so the three can never disagree.

Querying jobs

craft.hire.jobs is an ordinary element query:

{% set jobs = craft.hire.jobs
    .status('open')
    .spec({ department: ['engineering'], jobType: ['full-time'] })
    .orderBy('hire_jobs.postDate desc')
    .all() %}

Within a group, filters are OR; across groups, AND — see Specifications and filtering.

A job page

Set the template for each site under Settings → Job URLs. The job is available as job:

{% block head %}{{ craft.hire.schema(job) }}{% endblock %}

<h1>{{ job.title }}</h1>
<p>{{ job.locationLabel }} · {{ job.salary }} · closes {{ job.expiryDate|date('j F Y') }}</p>

{{ job.description }}  {# a field of your own #}

{{ craft.hire.applyForm(job) }}

applyForm() renders nothing when the job takes applications by URL, email or not at all — use job.applyUrl for the first two.

Structured data

craft.hire.schema(job) outputs the JobPosting JSON-LD that Google for Jobs reads. Hire builds it rather than leaving it to a template, because an invalid posting fails silently — nothing is flagged anywhere a site owner looks; the job simply never appears. It includes datePosted, validThrough, identifier, directApply, baseSalary, employmentType, occupationalCategory, and both halves of a remote posting: jobLocationType and applicantLocationRequirements.

Fill in the organisation name, URL, logo, default country and currency under Settings → General. craft.hire.listSchema(jobs) outputs an ItemList for a listing page.

Changing the markup

Every template Hire renders is an ordinary Twig file. Copy it into your own templates/hire/ and edit it — Hire looks for the site's copy first. No overrides file, no theme layer.

Copy thisTo change
_listing/listing.twigthe whole careers page
_listing/_filters.twigthe filter panel
_listing/_job.twigone row of the listing
_form/form.twigthe application form
_form/_field.twigone question
_portal/status.twigthe applicant's status page

Writing your own form

If you hand-write the form instead, it must post to hire/apply/submit with the jobId, and carry the signed timestamp — a form posted without one is refused by the timing check:

<form method="post" enctype="multipart/form-data">
    {{ csrfInput() }}
    {{ actionInput('hire/apply/submit') }}
    {{ hiddenInput('jobId', job.id) }}
    {{ hiddenInput('hireStamp', craft.hire.formTimestamp()) }}
    …
</form>

Starting from a copy of _form/form.twig is easier, and keeps the honeypot and any CAPTCHA.

Everything else on craft.hire

craft.hire.job(id)One job, by ID, reference (JOB-104) or slug
craft.hire.applicationsAn application query
craft.hire.specGroups, specGroup(handle), specs(handle)Specification groups and terms
craft.hire.specCounts()Job counts per term
craft.hire.stagesPipeline stages
craft.hire.form(handle)An application form
craft.hire.settingsPlugin settings

More in Site Features

Pairs well with Hire

The bigger pieces a client asks for by name. Events, bookings, memberships, courses, reviews, a support desk, job listings and the rest.