JobPosting schema is the Schema.org markup type that structures a single job opening into machine-readable fields: the role title, the hiring organisation, the location, the salary range, and the dates the posting is genuinely open. Where a human reads a careers page and infers all of this from layout and phrasing, an AI system reads only what the JSON-LD explicitly states, and it treats an undated, unstructured job listing very differently from one that states, in structured form, exactly when it was posted and when it closes.
JobPosting schema has existed since Google for Jobs launched it in 2017, but its stakes have changed. A search engine misreading a stale listing wastes a click. An AI job search agent that recommends a closed role, or invents a salary figure because none was stated, fails in the middle of a conversation the user is actively relying on, which is a much sharper penalty than a lost click, and it is exactly the kind of failure that makes an answer engine stop trusting a source at all.
Why AI job search agents depend on structure more than prose
Job listings are one of the few content types where the underlying facts change on a fixed schedule: a role opens, stays live for weeks, then closes, whether or not the page itself is ever edited or removed. Prose alone cannot carry that lifecycle reliably. A page that still says "we are hiring" six months after the role was filled is not lying deliberately, it is simply a static document describing a fact that has since expired, and nothing in the visible text tells a retrieval system the difference between a listing from yesterday and one from last spring.
ChatGPT, Google AI Mode, Perplexity, and the newer agentic job search tools built into LinkedIn and Indeed all answer questions like "is [company] hiring for [role] right now" by trying to establish current state, not just topical relevance. JobPosting schema is the only reliable way to give them that state directly, through the datePosted and validThrough properties, rather than forcing the model to guess from page-edit timestamps or infer freshness from unrelated cues.
- ChatGPT and Google AI Mode job search flows preferentially surface roles with explicit validThrough dates still in the future, and treat postings with no expiry date as inherently less trustworthy for a "currently hiring" query
- Perplexity and other citation-heavy engines quote baseSalary, employmentType, and jobLocation directly when they are present as structured fields, rather than paraphrasing an approximate range mentioned somewhere in the body copy
- Agentic job search tools that can apply on a user's behalf rely on directApply and applicantLocationRequirements to decide whether a role is even eligible to act on, independent of anything the visible page says
- Every major engine treats an expired or removed JobPosting with no corresponding schema update as a negative freshness signal for the entire careers section, not just the one stale listing
The required and recommended JobPosting properties
Google's job posting guidelines define a strict required set, and AI citation raises the bar further with a recommended set that meaningfully improves how much of a listing an engine can quote directly instead of paraphrasing.
Required properties
- title: the exact job title as it appears on the page, not an internal requisition code
- description: the full job description in HTML or plain text, matching what a human visitor sees
- datePosted: the ISO 8601 date the listing first went live
- hiringOrganization: an Organization object with at minimum a name and url
- jobLocation: a Place object with a full PostalAddress, or applicantLocationRequirements for remote-only roles
- employmentType: FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN, VOLUNTEER, PER_DIEM, or OTHER
Recommended properties for AI citation depth
- validThrough: the closing date; omitting this is the single most common reason a JobPosting reads as stale to an AI engine, covered in detail below
- baseSalary: a MonetaryAmount with a currency and either a fixed value or a minValue and maxValue range, which is what lets an engine quote a figure at all instead of saying "salary not specified"
- directApply: a boolean indicating whether a candidate can apply on the page itself without being redirected through several intermediate steps
- identifier: a PropertyValue linking back to the internal requisition ID, useful for deduplicating the same role posted across a careers page and third-party job boards
- workHours and jobLocationType (TELECOMMUTE for remote roles): clarifies scheduling and remote eligibility as discrete facts rather than something buried in a paragraph
The correct JSON-LD structure
A minimal but complete JobPosting block includes the full required set plus validThrough and baseSalary, since those two properties carry most of the AI citation value beyond what Google for Jobs alone requires.
Implementation in Next.js (App Router)
Generate the schema from the same record that renders the visible listing, the same principle that applies to every other schema type. A careers page backed by an applicant tracking system should read validThrough directly from the ATS record and let the listing disappear from both the page and the schema the moment the role closes, rather than maintaining two separate sources of truth that inevitably drift apart.
The expiry problem: why stale JobPosting schema actively hurts citation
Most careers pages implement JobPosting schema once, at launch, and never revisit it. The result is a slow accumulation of listings whose validThrough date has quietly passed while the page, and the schema, keep insisting the role is open. This is worse for AI citation than having no schema at all, because a fabricated or hallucinated recommendation that traces back to your domain is exactly the failure mode that makes an AI engine downgrade a source across every future query, not just the one bad listing.
The fix has two parts, and both need to happen automatically rather than as a manual task someone eventually gets to. First, remove or noindex a listing's page the moment the role closes, rather than leaving it live with stale copy. Second, drop the JobPosting schema block entirely, or update validThrough retroactively, so the structured data cannot outlive the page content it describes. Google explicitly recommends returning a 404 or 410 status for expired job pages rather than a soft "this role has closed" banner, and the same logic protects AI citation trust.
Never let validThrough silently pass without the corresponding page and schema being removed or updated. An AI agent that recommends a role based on a validThrough date that already elapsed is not making a minor error, it is presenting false information to a user who may act on it, and that is the single fastest way to get a domain's job listings deprioritised or excluded from an engine's job search feature entirely.
Where JobPosting schema matters most
The return on implementing this schema correctly scales with how much hiring activity a site actually has, and with how directly AI-driven job search traffic already competes for the same candidates.
- Company careers pages with more than a handful of open roles, where an ATS-driven feed makes automated schema generation and expiry handling far more valuable than a one-off manual implementation
- Recruiting agencies and staffing firms, where dozens of client roles rotate constantly and stale listings compound quickly across a large catalogue
- Job board and aggregator sites, where JobPosting schema is close to a genuine requirement, since Google and most AI job search integrations will not surface listings without it
- Remote-first companies, where applicantLocationRequirements and jobLocationType are the only reliable way to signal eligibility to an AI agent filtering by geography on a candidate's behalf
Validating your JobPosting schema
Structural validity and factual accuracy need separate checks, since a listing can pass every automated validator while still describing a role that closed weeks ago.
- Google Rich Results Test (search.google.com/test/rich-results): confirms JobPosting eligibility and flags missing required fields immediately
- Search Console's Job Postings report: surfaces indexing errors and, critically, listings Google has already detected as expired based on validThrough
- Schema.org validator (validator.schema.org): checks specification compliance for optional properties like baseSalary and identifier that Google's tool does not always flag
- A scheduled audit against the ATS: confirm every JobPosting still present in the schema corresponds to a role still genuinely open in the source system, not just a page nobody has taken down
- CiteRank audit: checks JobPosting schema presence, required fields, and validThrough freshness alongside every other AEO signal in a single pass
If your careers pages are hosted entirely on a third-party ATS such as Greenhouse, Lever, or Workday, check whether that platform emits JobPosting schema by default before building your own. Several major ATS providers already generate compliant markup automatically, and duplicating it on a wrapper page you control usually creates conflicting schema rather than adding coverage.