Job Posting Schema: Eligibility and Expiry Rules

Job Posting Schema: Eligibility and Expiry Rules

A job listing shows up as a rich result in Google Search only when the page's structured data proves three things at once: that the role is real, that it is current, and that a few required fields exist in a form Google can parse. Most publishers clear the first two hurdles without noticing. Where JobPosting markup actually trips people up is the third: it is one of the few common schema types where a missing property doesn't just mean a warning in a testing tool, it can mean the listing quietly stops surfacing without any error you would notice.

There are two separate questions to get right, and conflating them is the usual mistake. The first is eligibility: what has to be present on the page for Google to consider the listing for a rich result at all. The second is the expiry lifecycle: what keeps that listing accurate over time, and what you're expected to do once the role is filled or pulled. Get the first wrong and a role never shows up. Get the second wrong and it shows up, then vanishes, or worse, keeps showing after the position is gone.

The properties Google won't render a listing without

Google's structured data guidelines list a small set of required properties for JobPosting, and all of them have to resolve to real, visible content on the page, not just exist in the JSON-LD block. Miss one and the whole listing is ineligible, not degraded.

Remote listings fail in a specific way that looks inexplicable in a testing tool: a posting can have a clean title, a full description, a correct date, and a properly formed hiringOrganization, and still be rejected because jobLocationType was set to telecommute without applicantLocationRequirements alongside it. Google needs to know where a remote hire is allowed to be based, not just that the role has no office. The type alone describes the job, not the eligible geography, and both properties have to be present together.

  • title: the job title as a plain string, matching what's on the page. A boilerplate string like "Careers at the company" repeated across every req isn't a title Google will treat as distinct.
  • description: the full job description, which can include basic HTML formatting such as lists and paragraph breaks, but it has to match what a visitor actually sees on the page, not a trimmed or templated summary.
  • datePosted: an ISO 8601 date for when the listing first went live. This is not the date you last touched the page; re-saving a draft doesn't move it forward, and setting it that way risks looking like manipulation.
  • hiringOrganization: an Organization object with at minimum a name. logo and sameAs are recommended, not required, but they're what let the rich result show your brand instead of a generic building icon.
  • jobLocation, or jobLocationType plus applicantLocationRequirements: a full address for on-site and hybrid roles, or, for fully remote roles, the telecommute type together with the countries or regions applicants may be based in.

What separates eligible from competitive

Clearing the required list gets a posting into consideration. employmentType, baseSalary, and validThrough are recommended rather than required, and none of them will get a listing rejected on their own, but they're what determine whether it shows up in a filtered search such as full-time only, salary range, or remote only, and how clearly it signals that it is still current.

employmentType takes one of a fixed set of values, including full time, part time, contractor, temporary, intern, volunteer, and per diem, and accepts more than one where a role genuinely fits several, like a contract-to-hire req marked both contractor and full time. baseSalary is a nested structure: a MonetaryAmount with a currency code, wrapping a QuantitativeValue that carries either a flat value or a minValue and maxValue range along with a unitText such as hour or year. It's more setup than a flat number, and it's easy to get the nesting wrong on a first pass, since the currency belongs on the outer MonetaryAmount and the range belongs on the inner QuantitativeValue.

Salary data has also stopped being optional in practice even where it's optional in the schema. A growing number of U.S. states and municipalities now require a pay range to be disclosed in job advertising, which means the baseSalary block on your page isn't just an SEO nicety in those jurisdictions. It's the same range compliance already requires you to publish, structured in a form Google can actually read and filter on.

Why this schema type expires and most don't

Product and Event markup describe things that stay true until you change them: a price, a venue, a date. JobPosting is built on a different assumption, that a listing left alone gradually stops being trustworthy, because roles get filled. Google encodes that assumption directly into a property its own documentation singles out even though it's technically marked recommended rather than required: validThrough.

Google's documentation says to leave validThrough out only when a posting genuinely never expires or the end date is unknown. When a role does have an end date, omitting it removes the clearest freshness signal the listing can send. A req with a known close date but no validThrough leaves Google to guess whether it is still open.

This is the part that catches evergreen or high-volume reqs: retail seasonal hiring, standing engineering pipelines, roles a company deliberately keeps open on rolling admission. If the role genuinely stays open for three months, validThrough has to say so. Without it, nothing in the markup tells Google how long the opening is meant to last.

Keeping a long-running requisition visible without gaming it

The honest way to extend a listing's life is to set validThrough to a date that actually reflects how long the role will stay open, and to revisit it on a real cadence, when you review the requisition itself, not on an arbitrary schedule designed purely to look fresh to Google.

What doesn't work, and what Google's guidelines specifically warn against, is bumping datePosted forward on an unchanged listing purely to reset its apparent freshness. datePosted is supposed to represent when the posting first went live; moving it without the content actually changing is the same category of problem as backdating a blog post's publish date. It's detectable, because the rest of the page's signals, its URL, its cached history, its unchanged description, don't move along with the date.

If a role is reposted after a genuine pause, reopened after a hiring freeze, or refreshed with updated requirements, that's a legitimate reason to treat it as a new posting: new datePosted, new validThrough, and ideally a description that reflects what actually changed rather than a copy-paste of the original text.

The other half of the lifecycle: removing what's filled

validThrough handles listings you forget to manage. It doesn't cover the listing you remember perfectly well is closed. Once a role is filled, withdrawn, or put on hold, the obligation to remove it promptly sits with the publisher, not with validThrough catching up on its own schedule.

There are three defensible ways to handle a closed role: return a 404 or 410 status for the page so it drops out of the index entirely; remove the JobPosting structured data from the page while leaving informational content behind, such as a notice that the role has been filled; or redirect the URL to a live listings or careers page if the URL itself is being retired along with the role.

What isn't defensible is leaving the markup and the page exactly as they were for a role that's actually closed. Google's stated position is that sites which routinely leave expired or filled postings live risk having their future submissions treated with more skepticism, meaning fewer of that domain's new listings get surfaced going forward. The cost of a stale listing isn't confined to the one posting you forgot to take down.

Multi-location, syndicated, and hybrid postings

A single requisition open in several cities doesn't need several pages. JobPosting supports multiple jobLocation entries under one posting, and that's the correct structure for one role available in several offices: one JobPosting, one description, an array of Place objects for each valid location. Duplicating the page per city instead creates near-identical content competing against itself, and it multiplies the maintenance burden of the expiry rules above by however many copies exist.

Hybrid roles still need a real jobLocation. A small number of required office days a week doesn't make a role telecommute, and marking it that way anyway will surface it to applicants who can't actually take it. Reserve the telecommute value for roles with no office requirement at all, and use applicantLocationRequirements to state the eligible geography either way, hybrid or fully remote.

For listings syndicated across multiple job boards, keep the title, the hiringOrganization name, and the datePosted identical across every instance where the underlying role is identical. Google deduplicates job postings that describe the same role using exactly these signals; inconsistent values across syndicated copies are what cause a single opening to show up as several distinct listings instead of one, which reads as a spam pattern even when the intent was simply to post everywhere at once.

A working checklist for publish and offboarding

Two short lists cover most of what goes wrong in practice: one for the day a listing goes live, and one for the day the role closes.

  • Before publishing: title, description, datePosted, hiringOrganization name, and either jobLocation or jobLocationType plus applicantLocationRequirements are all present and match the visible page.
  • Before publishing: validThrough is set to a realistic close date, not left blank and not defaulted to whenever it happens to expire.
  • Before publishing: employmentType and baseSalary are filled in wherever the underlying facts are known; both increase how filterable and specific the listing is, and baseSalary may already be legally required in your jurisdiction.
  • On close: the page is either removed with a 404 or 410, stripped of JobPosting markup, or redirected, rather than left live with expired data.
  • On close: the cleanup isn't left to validThrough alone; an end date in the markup is a backstop, not a substitute for actually taking the listing down.

Frequently asked questions

Do I need to set validThrough if the role never really expires?

Google's documentation says to omit validThrough when a posting genuinely never expires or you don't know when it will. If the role does have a realistic end date, set validThrough to it and revisit it on your normal review cadence, and remove the listing promptly once the role closes.

Does adding JobPosting schema help the page rank higher generally?

No, it governs eligibility for the Google Jobs rich result, which is a separate surface from standard organic ranking. The underlying page still has to earn its ranking the normal way, through content quality, relevance, and the usual on-page and off-page signals; the structured data only decides whether that same page can also appear in the jobs-specific presentation.

What actually happens if I leave a filled position's listing live?

It stays eligible to show until validThrough passes, presenting applicants with a role that no longer exists. Beyond the immediate applicant-trust problem, sites with a pattern of stale or expired listings risk having future postings from that domain treated with more skepticism, which can mean fewer of a site's new listings get surfaced going forward.

Can one JobPosting entry cover a role open in multiple cities?

Yes. Use a single JobPosting with multiple entries in the jobLocation array, one Place per valid office. That's the structure Google expects for one role available in several locations, and it avoids the duplicate-content and duplicate-maintenance problems of building a separate page per city.

Is baseSalary worth the extra setup if it's only recommended?

Usually yes. Beyond making a listing eligible for salary-filtered searches, a growing number of jurisdictions already require a disclosed pay range in job advertising, so the same range you're legally posting can be structured into baseSalary rather than left as plain text the schema can't use.

Updated: October 2, 2026

All articles