- JobPosting powers the dedicated Google Jobs search experience - the rich result that takes over most of the SERP for job-related queries, with filters for location, employment type, salary range, and remote.
- Required:
title,description,datePosted,hiringOrganization,jobLocation(orjobLocationTypefor remote),validThrough. - baseSalary is recommended and high-impact. Listings with salary surface in salary-filtered queries and lift application CTR substantially.
- Remote jobs use
jobLocationType: "TELECOMMUTE"plusapplicantLocationRequirementsfor country/region restrictions. - Use the Indexing API for new and removed jobs. This is one of the few documented production use cases of the API.
Chapter 1. Before you start
JobPosting is the gateway to Google Jobs - a dedicated job search vertical that takes over much of the SERP for job-related queries. Listings without JobPosting schema are effectively invisible in this vertical; everything inside it comes from schema-tagged pages.
- Confirm each job has its own URL. One URL per opening. Don't ship JobPosting on a careers landing page that lists multiple openings.
- Confirm the page content matches the schema. Job title, description, location, and salary in the schema must match what users see on the page. Mismatches trigger manual actions.
- Pull the canonical hiring organization data - name, URL, logo - so Google can associate the listing with the Organization entity.
- Decide on
validThrough. Every JobPosting must have an expiration date. After it passes, the listing leaves Google Jobs. - Set up Indexing API access for the site (service account, GSC ownership). Without it, new jobs take days to appear in Google Jobs instead of hours.
validThrough date and showed jobs that closed months earlier still active in Google Jobs. 9 published remote roles without jobLocationType: TELECOMMUTE, missing the "remote" filter. 7 omitted baseSalary entirely on listings where salary was disclosed in the page body. 5 hosted multiple jobs on one URL with a single JobPosting record.
Chapter 2. What does JobPosting schema actually do for SEO + AI search?
- Google Jobs experience inclusion. The dedicated job-search UI inside Google Search pulls exclusively from JobPosting schema. Without it, your jobs never appear there.
- Salary range and remote filter eligibility. Users can filter by salary band and "remote only"; both filters require explicit schema fields.
- Application CTR uplift. The rich result shows logo, salary, employment type, and posting age in the SERP - much taller and higher-CTR than a plain organic listing.
- AI engine "jobs at X" answers. ChatGPT and Perplexity cite JobPosting-schema'd pages directly when answering employer-name queries.
Chapter 3. Required and recommended properties
{
"@context": "https://schema.org",
"@type": "JobPosting",
"@id": "https://www.example.com/careers/senior-seo-strategist#job",
"title": "Senior SEO Strategist",
"description": "<p>Capconvert is hiring a Senior SEO Strategist to lead organic strategy across 12 ecommerce clients...</p>",
"identifier": {
"@type": "PropertyValue",
"name": "Capconvert",
"value": "JOB-2026-0142"
},
"datePosted": "2026-05-20",
"validThrough": "2026-07-31T23:59:00-05:00",
"employmentType": "FULL_TIME",
"hiringOrganization": {
"@type": "Organization",
"name": "Capconvert",
"sameAs": "https://www.capconvert.com",
"logo": "https://www.capconvert.com/brand/capconvert-logo.png"
},
"jobLocation": {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"streetAddress": "1234 W Madison St",
"addressLocality": "Chicago",
"addressRegion": "IL",
"postalCode": "60607",
"addressCountry": "US"
}
},
"baseSalary": {
"@type": "MonetaryAmount",
"currency": "USD",
"value": {
"@type": "QuantitativeValue",
"minValue": 95000,
"maxValue": 130000,
"unitText": "YEAR"
}
},
"industry": "Marketing and Advertising",
"qualifications": "5+ years of SEO experience, demonstrated ecommerce results",
"responsibilities": "Lead strategy, manage 3-5 client accounts, mentor junior strategists",
"skills": "Technical SEO, content strategy, GA4, Ahrefs",
"workHours": "40 hours per week"
}
employmentType values: FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN, VOLUNTEER, PER_DIEM, OTHER. Multi-type roles use an array.
baseSalary.value.unitText options: HOUR, DAY, WEEK, MONTH, YEAR. Always include even for hourly roles - Google's salary filter needs the period to normalize.
Chapter 4. Remote and multi-location modeling
Fully remote (anywhere in country)
"jobLocationType": "TELECOMMUTE",
"applicantLocationRequirements": {
"@type": "Country",
"name": "USA"
}
Remote with state restrictions
"jobLocationType": "TELECOMMUTE",
"applicantLocationRequirements": [
{ "@type": "State", "name": "Illinois" },
{ "@type": "State", "name": "California" },
{ "@type": "State", "name": "New York" }
]
Hybrid (in-office + remote)
Include both jobLocation (the office) AND jobLocationType: "TELECOMMUTE". Google interprets the combination as hybrid.
Multiple physical locations
One JobPosting per location, even if the role is identical. Each location gets its own URL. Listing one JobPosting with an array of locations is allowed but produces a weaker rich result.
Chapter 5. validThrough and Indexing API integration
validThrough discipline
Every JobPosting must include validThrough. After this date, Google removes the listing from Google Jobs. Leaving expired jobs in the index is one of the highest-frequency policy violations - Google has issued manual actions against sites with stale listings.
When you close a job before validThrough, update the page (mark expired in body copy, return HTTP 410 or 404, or remove from sitemap) AND notify the Indexing API.
Indexing API
Google's Indexing API is officially limited to JobPosting and BroadcastEvent. For JobPosting it's near-mandatory: new jobs take hours instead of days to appear in Google Jobs, and closed jobs are removed within minutes.
POST https://indexing.googleapis.com/v3/urlNotifications:publish
{
"url": "https://www.example.com/careers/senior-seo-strategist",
"type": "URL_UPDATED" // or "URL_DELETED" when the job closes
}
We cover the full Indexing API setup (service account, GSC ownership, daily quota of 200 URLs) in our Indexing API guide.
Chapter 6. Where do you place JobPosting schema on the site?
One JobPosting record per individual job URL. Convention: /careers/{job-slug} or /jobs/{job-id}. Each URL contains exactly one JobPosting schema record.
The careers landing page (/careers) should not carry JobPosting schema. It can use ItemList referencing the individual job URLs by @id.
Chapter 7. The breakages we see most often
Ranked by frequency across 18 job-publishing site audits:
- No
validThrough, leaving closed jobs visible in Google Jobs months after they end. 12 of 18. - Remote roles without
jobLocationType: TELECOMMUTE, missing the "remote only" filter. 9 of 18. - No
baseSalarydespite the salary being in the body copy. Loses the salary band filter eligibility. 7 of 18. - Multiple jobs on one URL with a single JobPosting that tries to cover all of them. 5 of 18.
- No Indexing API integration, so jobs lag days behind in Google Jobs and closed jobs linger. 4 of 18.
employmentTypeas free text ("Full Time" instead ofFULL_TIME). 3 of 18.
We track these on running sites through our Sentry structured-data rule set.
FAQ
What happens if I forget to set validThrough?
The listing may still be eligible for the rich result short-term, but Google increasingly treats missing validThrough as a quality signal against the listing. Sites with many stale undated jobs can receive a manual action removing all their jobs from Google Jobs.
Can I post the same job to multiple job boards with JobPosting on each?
Yes, but only one URL should be the canonical source. The third-party boards (Indeed, LinkedIn, etc.) ship their own JobPosting for the listing on their side. You ship yours on your own careers page. Google deduplicates by employer + title + location.
Should I include salary if my company doesn't publicly disclose it?
If salary isn't disclosed on the page, don't fabricate it in the schema. You lose the salary-band filter visibility, but inventing salary triggers a manual action. Increasingly, US states (NY, CA, WA, CO) require salary disclosure; if you're hiring in those states, disclose anyway and include the band in baseSalary.
What's identifier for?
An internal job requisition ID. Optional but useful when the same role exists at multiple locations (so you can track applications back to the right req). It also helps Google deduplicate listings.
Can I have JobPosting + Article schema on the same page?
Yes. A careers landing page can have an Article record (the marketing copy about why to work here) and JobPosting records for individual openings. Use @id to keep them as distinct entities.
How do I handle a contract-to-hire role?
Use employmentType: ["CONTRACTOR", "FULL_TIME"] (array). Indicate the contract-to-hire arrangement in the description. Google handles array employmentType correctly.
References
- Schema.org. "JobPosting." schema.org/JobPosting
- Google Search Central. "Job posting (JobPosting) structured data." developers.google.com/search/docs/appearance/structured-data/job-posting
- Google Search Central. "Indexing API." developers.google.com/search/apis/indexing-api/v3/quickstart
- Schema.org. "MonetaryAmount." schema.org/MonetaryAmount
- Schema.org. "applicantLocationRequirements." schema.org/applicantLocationRequirements
- Schema.org. "Schema Markup Validator." validator.schema.org