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,occupationalCategoryorindustry
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.