JobPosting schema markup done right: a practical guide for UK agencies and job boards

JobPosting schema markup done right: a practical guide for UK agencies and job boards

JobPosting schema is the structured data that tells Google a page is a job advert, and it's the price of entry for Google for Jobs, the boxed job listings that sit at the top of search results. Without valid markup your vacancies don't appear there, however good the advert is. With it, your listings can show salary, location, contract type and posting date directly in search, and candidates reach you before they reach an aggregator. This guide covers how to mark up job listings properly, the mistakes we see most often on UK agency and recruitment websites, and how to validate and monitor structured data for job listings so it keeps working after launch.

Why JobPosting schema matters more for agencies than most

Job search is one of the few areas where Google shows its own interface on top of the normal results. A candidate searching 'care assistant jobs Chester' sees a list of roles pulled from structured data across many sites, filterable by date, contract type and distance, before any ordinary blue links.

Big boards and aggregators have this nailed. Many agency sites don't, which means their own vacancies appear in Google for Jobs only via Indeed, Reed or a multiposter, with the 'apply' route sending candidates to someone else's site. Getting your own markup right puts your site in that list directly, with your brand on the listing and the application landing on your page. It's one of the few SEO jobs where a technical fix produces visible results quickly.

The required and recommended properties

Google requires five properties on every JobPosting. Miss any of them and the listing isn't eligible.

  • title: the job title and nothing else. No reference codes, salaries, locations or 'URGENT'.
  • description: the full advert as HTML, with paragraphs and lists. It must cover responsibilities, requirements and hours, and it must match what's on the page.
  • datePosted: the date the job was first posted, in ISO 8601 format.
  • hiringOrganization: the organisation offering the job, with its name and ideally its website and logo.
  • jobLocation: where the work happens, as a postal address. addressCountry is required, and for UK jobs that's GB.

Then the recommended properties. Google treats these as optional, but in practice they decide how good your listing looks and which filters it shows up in:

  • validThrough: when the advert closes. Required in practice if the job has a closing date.
  • employmentType: FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN, VOLUNTEER, PER_DIEM or OTHER. You can give more than one.
  • baseSalary: the actual pay, as a figure or a range, with a unit (HOUR, DAY, WEEK, MONTH or YEAR).
  • identifier: your internal job reference.
  • directApply: true only if a candidate can apply on your page without extra hoops.
  • jobLocationType and applicantLocationRequirements: for fully remote roles.

A complete UK example

Here's a temp healthcare role marked up in full. The pattern holds for permanent roles; change the employment type and salary unit.

{
  '@context': 'https://schema.org/',
  '@type': 'JobPosting',
  'title': 'Registered Nurse',
  'description': 'We're looking for a Registered Nurse to join a 40-bed nursing home in Chester on a temporary basis.\n\nHours: 3 x 12-hour day shifts per week.\n\nRequirements: active NMC registration, at least 6 months' UK experience, and an Enhanced DBS (or willingness to apply).',
  'identifier': {
    '@type': 'PropertyValue',
    'name': 'Example Healthcare Recruitment',
    'value': 'RN-4821'
  },
  'datePosted': '2026-10-06',
  'validThrough': '2026-11-06T23:59:00+00:00',
  'employmentType': ['TEMPORARY', 'PART_TIME'],
  'hiringOrganization': {
    '@type': 'Organization',
    'name': 'Example Healthcare Recruitment',
    'sameAs': 'https://www.example-healthcare.co.uk',
    'logo': 'https://www.example-healthcare.co.uk/logo.png'
  },
  'jobLocation': {
    '@type': 'Place',
    'address': {
      '@type': 'PostalAddress',
      'addressLocality': 'Chester',
      'addressRegion': 'Cheshire',
      'postalCode': 'CH1',
      'addressCountry': 'GB'
    }
  },
  'baseSalary': {
    '@type': 'MonetaryAmount',
    'currency': 'GBP',
    'value': {
      '@type': 'QuantitativeValue',
      'minValue': 24.50,
      'maxValue': 27.00,
      'unitText': 'HOUR'
    }
  },
  'directApply': true
}

A few UK-specific details worth getting right: use GBP for currency, GB (not UK) for country, and give hourly rates for temp work as HOUR rather than converting to an annual figure that doesn't reflect what the candidate will be paid.

The agency question: who is the hiring organisation?

Google's definition is simple: hiringOrganization is the organisation offering the job. For agencies it's the property that causes the most head-scratching, because the client often doesn't want to be named.

The rule that keeps you safe is that the markup must match the page. If the advert names the client, use the client. If the role is confidential, use your agency, say in the description that you're recruiting on behalf of a client, and never put a client's name in the schema that isn't on the visible page. Structured data that says something the page doesn't is exactly what Google's guidelines prohibit.

Common JobPosting schema mistakes

Most broken job markup we audit fails in the same handful of ways.

  1. Markup on search results and listing pages. JobPosting belongs on the individual job page only. A category page listing 20 roles should carry no JobPosting markup at all.
  2. Expired jobs left live. When a role closes, Google expects one of three things: validThrough set to a past date, the page returning 404 or 410, or the markup removed (a noindex also works). Leaving filled jobs marked up as open is the fastest route to a manual action against your job listings.
  3. Stuffed titles. 'URGENT!! Registered Nurse £27ph Chester – Apply Today' is a title, a salary, a location and a call to action. Put each in its own property.
  4. Salary text in a number field. 'Competitive', 'DOE' and '£negotiable' aren't salaries. If you don't have a figure, leave baseSalary out rather than filling it with text or a zero.
  5. Hybrid roles marked as remote. TELECOMMUTE is for jobs that are 100% remote, and the description must say so. One day a week in the office means it isn't. Fully remote roles also need applicantLocationRequirements (for example, GB) so Google knows where applicants can be based.
  6. Refreshing datePosted to look new. Google wants the original posting date. Bumping it on every re-post to climb the 'last 24 hours' filter counts as misrepresentation.
  7. Schema and page drifting apart. The salary gets updated in the CMS, the markup comes from a separate field or plugin that doesn't. Now the listing in Google shows one rate and the page another.
  8. Thin or plain-text descriptions. A two-line summary, or one block of text with no paragraphs, won't meet the completeness requirement.

Build it so it can't drift

The mistakes above have a common root: the markup is treated as a separate thing bolted onto the page. A plugin generates it from one set of fields while the page renders from another, and closing a job means remembering to update both.

The reliable fix is to generate the JSON-LD from the same job record that renders the page, in the same template, server-side. Change the salary and both update. Close the job and the same flag sets validThrough, drops the markup and switches the page to a closed state. Validation rules sit in the code (no text in salary fields, TELECOMMUTE only when the remote flag is set) so bad data is stopped at the point of entry.

Rendering server-side matters too. Google can process JSON-LD injected by JavaScript, but it's slower and less predictable, and on a job board with thousands of short-lived pages, slow discovery means listings appear in search after the role has already been filled.

Validate, then keep checking

Validation is a launch step and an ongoing one.

  • Rich Results Test. Run one live URL from every job template you have (permanent, temp, contract, remote). It shows errors that block eligibility and warnings for missing recommended properties.
  • Search Console Job postings report. Once Google has crawled your listings, this report groups valid items, warnings and errors across the whole site. It's where template-level problems show up, because one bug appears on every job at once.
  • URL Inspection. Use it on a single job to confirm Google sees the rendered markup and that the page is indexed.
  • A monthly check of closed jobs. Pick a few roles filled last month and make sure their pages have stopped claiming to be open.

Tell Google when jobs change

Jobs are short-lived, so discovery speed matters. Google recommends two things together: keep an XML sitemap of live job pages with accurate lastmod dates, and use the Indexing API to notify it when a job is posted or removed. The Indexing API now needs approval from Google before you can use it, so apply early if you're launching a board. A clean sitemap on its own still works; it's slower.

We go into indexing, sitemaps and the reasons listings don't appear in more depth in [Building a job board Google for Jobs actually indexes].

Get it right once, at the template

JobPosting markup is fiddly, but it's a template problem. Fix it properly in the code that renders your jobs and every current and future vacancy benefits, with no ongoing editing. Leave it to a plugin or a copy-and-paste snippet and it will drift the first time someone changes a field.

We build this into every Nodex recruitment website and into our job board platform, with markup generated from the job record, closed jobs handled automatically, and sitemaps kept current. If your vacancies aren't appearing in Google for Jobs, or Search Console is full of job posting errors, talk to us about a technical SEO fix.

If your website is not helping your agency, it is time to replace it with one that does

Book a 20-minute demo. We'll walk the platform live and show exactly how Nodex would work for your agency.

  • Tell us about your agency A few details so the demo is relevant, not generic.
  • We reply within one business day A real person from the team, not an automated drip.
  • Walk the platform together Live demo, honest pricing, clear next steps. No obligation.

Prefer to talk now? Call 01260 734 107.

From £189/month. No long-term lock-in. Your data stays yours.