Hire for Craft CMS

Specifications and filtering

Specifications are how jobs are classified: Department, Job type, Location, and any axis you add.

Groups and terms

The split is the one Craft makes between a category group and the categories in it.

  • A group — "Location" — is part of the content model. It lives in project config, so it is reviewable in a pull request and deploys with the site. Manage groups under Hire → Settings → Specifications.
  • A term — "Bristol" — is content. It lives in the database, so adding one because the company opened an office does not need a deploy. Each group has its own screen in the Hire sidebar.

Each group can be:

  • Filterable — shown as a filter on the listing
  • Multiple — a job may carry more than one term (Location is, by default)
  • Required — a job can't be saved without one
  • Mapped to structured data — as employmentType, occupationalCategory or industry

The filter rule

Somebody ticking Engineering and Design wants either. Somebody ticking Engineering and Part time wants both. So:

  • OR within a group
  • AND across groups

A flat AND gives an empty page the moment two boxes in one group are ticked. A flat OR makes every filter widen the results. Hire emits one indexed EXISTS per group, so there is no row multiplication and no DISTINCT fighting Craft's own query.

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

Live counts

Every filter on the built-in listing shows how many jobs carry it, from one query.

{% set counts = craft.hire.specCounts() %}

Employment types and Google

Google's employmentType has a fixed vocabulary — FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN and a few more — and a wrong value invalidates the whole posting, not one property. A group mapped to employmentType asks each term for its schema value and checks it at save time. The default Job type group arrives mapped.