JobPosting Schema

No Comments
Jobposting schema
TL;DR: Job listing pages without valid JobPosting structured data are invisible to the Google job search experience, the enriched jobs interface that sits above regular organic results. Add complete JobPosting JSON-LD to each individual job page, keep validThrough honest, and pair it with the Indexing API so listings appear while they are still open.
Element code
RI-016
Category
Structured data
Applies to
Job listing pages
Payoff
Google jobs visibility
Detection
Rich Results Test, GSC

What JobPosting schema is and where it shows up

JobPosting is the schema.org type that describes a single open role: what the job is, who is hiring, where it is, what it pays, and when the listing expires. Google reads it to populate its job search experience, the prominent jobs module that appears when someone searches "marketing manager jobs near me" or similar. That interface aggregates listings from career sites, ATS platforms, and job boards, and the entry ticket is valid JobPosting markup on an indexable page.

Here is the part that stings if you run a careers section without it: the jobs module sits above classic organic results for hiring queries. Your beautifully written job page can rank number one organically and still sit below a stack of Indeed and LinkedIn entries pulled into that module, because they have the markup and you do not. I have seen mid-size companies get more qualified applicants from fixing schema on 40 job pages than from a quarter of paid job board spend. Not a promise, but a pattern.

One structural rule people miss: the markup belongs on the individual job detail page, one JobPosting per canonical URL. Google explicitly does not want the schema on list pages that show multiple postings, and marking up a category page with an array of jobs gets you nothing.

The properties that actually matter

Google's documentation splits properties into required and recommended, and the difference has teeth. Miss a required property and the page is ineligible. Skimp on recommended ones, especially salary and remote attributes, and you lose filters and placement inside the jobs interface.

PropertyStatusPractitioner notes
titleRequiredThe job title only. "Senior Data Engineer", not "Senior Data Engineer!! Remote!! Apply NOW". Stuffed titles are a documented reason for removal.
descriptionRequiredFull HTML description of the role. Must match what is on the page, not a teaser sentence.
datePostedRequiredISO 8601 date. Do not fake-refresh it to look recent; reposting games get noticed.
hiringOrganizationRequiredThe employer, with name and ideally logo and sameAs URL. Staffing agencies list themselves, not the end client, unless disclosed.
jobLocationRequired*A Place with a real postal address. For remote roles, swap in jobLocationType: "TELECOMMUTE" plus applicantLocationRequirements.
validThroughStrongly recommendedExpiry datetime. Required in practice for expiration handling; leaving filled jobs live past this is the fastest route to a manual action.
baseSalaryRecommendedMonetaryAmount with currency and unitText. Feeds the salary filter; several US states and EU pay transparency rules push you here anyway.
employmentTypeRecommendedFULL_TIME, PART_TIME, CONTRACTOR and friends. Cheap to add, powers filters.
directApplyRecommendedBoolean signaling the user can complete an application on your page rather than bouncing through redirects.

How a job page gets into the jobs experience

Job detail page JobPosting JSON-LD Crawl and index sitemap + Indexing API for fast pickup Validation required props present, content matches page Google jobs experience logo, salary, location filters, apply link Missing required property? Ineligible: organic listing only From markup to the jobs module

How to detect the gap

  • Rich Results Test: paste a live job URL. It reports whether JobPosting was detected and lists every missing or invalid property with severity.
  • Search Console: the Job postings enhancement report under Enhancements shows valid, warning, and error counts across the site, plus which URLs share each problem. If the report does not exist, Google has not detected any markup at all, which is your answer.
  • Screaming Frog: enable structured data extraction and validation in the crawl config, crawl the careers section, and filter the Structured Data tab for missing JobPosting on URLs matching your job page pattern. This catches template-level gaps at scale.
  • Server logs: if Googlebot rarely hits new job URLs before they expire, markup alone will not save you. Crawl frequency is the other half of this problem.

How to implement it properly

  1. Generate JSON-LD server-side per job page from the same database record that renders the visible content. Hand-written schema drifts out of sync with reality within a month; templated schema cannot.
  2. Cover the required set first: title, description, datePosted, hiringOrganization, and jobLocation (or TELECOMMUTE plus applicantLocationRequirements for remote roles).
  3. Add validThrough and baseSalary. Salary ranges are fine using minValue and maxValue. If your ATS does not expose salary, escalate it; the listing competes against ones that show pay.
  4. Wire up expiration handling. The moment a role is filled or expired: either return 404 or 410, or keep the page and ensure validThrough is in the past. Advertising dead jobs violates the guidelines and Google has issued manual actions for exactly this.
  5. Use the Indexing API for job URLs. This is one of the two official use cases (job postings and livestream events). Ping URL_UPDATED on publish and URL_DELETED on close, so short-lived postings actually spend their lifespan indexed.
  6. Validate, then monitor. Rich Results Test on the template, then watch the Search Console enhancement report weekly. ATS template updates have a talent for silently breaking one property.

DO and DON'T

DO

  • Put one JobPosting per indexable job detail page, generated from live data
  • Include salary, employmentType, and honest remote attributes
  • Expire markup and page together the day a role closes
  • Use the Indexing API for publish and delete events
  • Keep the schema description in sync with the visible posting
DON'T

  • Mark up list or category pages with multiple JobPosting objects
  • Stuff titles with locations, salaries, or ALL CAPS bait
  • Leave filled jobs live with a future validThrough
  • Mark up roles you are not actually hiring for to farm traffic
  • Block job pages behind login or noindex and expect the module to find them

FAQ

Do I still need this if our jobs are already on Indeed and LinkedIn?
Those platforms send Google their own marked-up versions, so the aggregator's page gets the visibility and owns the applicant relationship. Markup on your careers site puts your URL in the module, sends candidates into your ATS directly, and skips the middleman fees for at least part of your pipeline.
How do I mark up a fully remote position?
Set jobLocationType to TELECOMMUTE and add applicantLocationRequirements naming the countries or states you can hire in. If the role is remote but restricted, say remote US only, the location requirements property is what communicates that. Only include jobLocation as well if there is a genuine office option.
Why does Search Console show valid items but my jobs still do not appear?
Valid markup makes you eligible, not guaranteed. Common blockers: the page is not indexed yet (short-lived jobs often expire first, use the Indexing API), the content looks duplicated across dozens of near-identical postings, quality guideline issues like missing apply flow, or the page is thin apart from boilerplate. Check indexing status with URL Inspection before blaming the schema.
Can staffing agencies and job boards use JobPosting markup?
Yes, and much of the module is exactly that. The rules: hiringOrganization should reflect reality (the agency if it is the employer of record, disclosed appropriately), postings must be real and current, and you cannot mark up scraped listings you have no right to represent. Google's job posting guidelines call these out explicitly.
JSON-LD or microdata for this?
JSON-LD. It is Google's recommended format, it is what every ATS integration emits, and it keeps the markup in one block you can generate and test independently of the page HTML. Microdata works but you will regret maintaining it inside a templating system.
Schema is one check among hundreds

I validate structured data eligibility, indexing coverage, and crawl behavior across your whole site in an Advanced SEO Audit, with a fix list your developers can execute in order.

Book an Advanced SEO Audit

Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.

About SEO ProCheck

Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.

Work With Me

Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.

Subscribe to our newsletter!

More from our blog