Google for Jobs Schema Markup: The Developer's Complete Guide
Everything developers need to implement Google for Jobs schema markup — JSON-LD JobPosting schema, required fields, enriched data, and validation strategies.
What Google for Jobs Is and Why It Matters
Google for Jobs is a structured data feature that surfaces job listings in a dedicated carousel at the top of Google Search results for job-related queries. When implemented correctly, your job listings can appear in this carousel with salary ranges, company logos, apply buttons, and other rich data — directly in search results, before a user even clicks through to your site.
The traffic impact is significant. Sites that properly implement JobPosting schema typically see 30–50% increases in organic traffic to their job listing pages within months of implementation. Google's index bot specifically looks for JobPosting structured data when crawling job boards.
This guide covers everything a developer needs to implement it correctly.
The JSON-LD Approach
Google supports three formats for structured data (JSON-LD, Microdata, and RDFa), but JSON-LD is strongly recommended. It's the cleanest to implement, doesn't require modifying HTML structure, and is easiest to generate programmatically.
The basic structure looks like this:
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "JobPosting",
"title": "...",
"description": "...",
"datePosted": "...",
"hiringOrganization": { ... },
"jobLocation": { ... }
}
</script>
Place this script tag in the <head> of every individual job listing page. Do not use it on list/search pages — it belongs on the individual job's canonical URL.
Required Fields
Google considers the following fields required. Omitting any of them will prevent your listing from appearing in the Jobs carousel:
title
The job title. Use the actual job title — don't append location, salary, or other metadata here.
"title": "Senior Backend Engineer"
description
The full job description in HTML or plain text. This must be the actual job description, not a truncated preview. Google uses this content for indexing and matching.
"description": "<p>We're looking for a senior backend engineer to join our platform team...</p><ul><li>5+ years of experience with distributed systems</li></ul>"
datePosted
ISO 8601 format date when the job was posted. This must be a real date, not a far-future date to make the listing appear perpetually fresh.
"datePosted": "2026-04-15"
hiringOrganization
Information about the company. The name property is required; sameAs and logo are optional but improve rich result eligibility.
"hiringOrganization": {
"@type": "Organization",
"name": "Acme Corporation",
"sameAs": "https://www.acmecorp.com",
"logo": "https://www.acmecorp.com/logo-200x200.png"
}
jobLocation or applicantLocationRequirements
For in-person or hybrid roles, use jobLocation:
"jobLocation": {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"streetAddress": "100 Main Street",
"addressLocality": "San Francisco",
"addressRegion": "CA",
"postalCode": "94105",
"addressCountry": "US"
}
}
For fully remote roles, use jobLocationType with applicantLocationRequirements:
"jobLocationType": "TELECOMMUTE",
"applicantLocationRequirements": {
"@type": "Country",
"name": "US"
}
Recommended Fields
These fields are not required but significantly improve your listing's appearance in search results:
validThrough
The date the listing expires. Including this helps Google manage listing freshness. Format: ISO 8601 datetime.
"validThrough": "2026-05-15T23:59:59Z"
employmentType
Accepted values: FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN, VOLUNTEER, PER_DIEM, OTHER. Can be an array.
"employmentType": "FULL_TIME"
baseSalary
This is one of the highest-impact fields — salary information prominently appears in the Jobs carousel and significantly increases click-through rates. Note that salary must be in full dollars for schema markup, not in thousands.
If you're using a job data API that stores salary in thousands (salary_min: 150 = $150,000), multiply by 1000 before generating the schema:
"baseSalary": {
"@type": "MonetaryAmount",
"currency": "USD",
"value": {
"@type": "QuantitativeValue",
"minValue": 150000,
"maxValue": 200000,
"unitText": "YEAR"
}
}
For hourly roles, use "unitText": "HOUR". For monthly, use "MONTH".
identifier
A unique identifier for the posting within your system. Useful for Google to track listings across recrawls.
"identifier": {
"@type": "PropertyValue",
"name": "YourBoard",
"value": "job-12345"
}
Complete Implementation Example
Here's a complete, production-ready JSON-LD implementation in TypeScript/Next.js:
function generateJobSchema(job: Job): string {
const schema: Record<string, unknown> = {
'@context': 'https://schema.org/',
'@type': 'JobPosting',
'title': job.title,
'description': job.description,
'datePosted': job.posted_at.split('T')[0], // YYYY-MM-DD
'validThrough': job.expires_at ? job.expires_at : undefined,
'employmentType': 'FULL_TIME',
'hiringOrganization': {
'@type': 'Organization',
'name': job.company.name,
'sameAs': job.company.website ?? undefined,
'logo': job.company.logo_url ?? undefined,
},
'identifier': {
'@type': 'PropertyValue',
'name': 'YourBoard',
'value': job.id,
},
};
// Handle location vs remote
if (job.remote) {
schema.jobLocationType = 'TELECOMMUTE';
if (job.location?.country) {
schema.applicantLocationRequirements = {
'@type': 'Country',
'name': job.location.country,
};
}
} else if (job.location) {
schema.jobLocation = {
'@type': 'Place',
'address': {
'@type': 'PostalAddress',
'addressLocality': job.location.city ?? undefined,
'addressRegion': job.location.state ?? undefined,
'addressCountry': job.location.country ?? 'US',
},
};
}
// Handle salary — job.salary_min is stored in thousands
if (job.salary_min || job.salary_max) {
schema.baseSalary = {
'@type': 'MonetaryAmount',
'currency': 'USD',
'value': {
'@type': 'QuantitativeValue',
'minValue': job.salary_min ? job.salary_min * 1000 : undefined,
'maxValue': job.salary_max ? job.salary_max * 1000 : undefined,
'unitText': 'YEAR',
},
};
}
// Remove undefined values
const cleaned = JSON.parse(JSON.stringify(schema));
return JSON.stringify(cleaned);
}
// In your Next.js page component:
export default function JobPage({ job }: { job: Job }) {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: generateJobSchema(job) }}
/>
{/* ... rest of page ... */}
</>
);
}
Common Mistakes That Prevent Indexing
- Schema on list pages: Only place JobPosting schema on individual job pages, not on search result pages
- Stale datePosted: Don't re-use old dates or set future dates. Google may penalize listings that appear to be faking freshness.
- Missing description content: The description must contain meaningful content — Google checks that the schema description matches actual visible page content
- Wrong salary unit: Schema uses full dollar values, not thousands.
minValue: 150000notminValue: 150. - Remote + in-person conflict: Don't specify both
jobLocationType: "TELECOMMUTE"and a physicaljobLocation— pick the one that accurately describes the role - Non-canonical URL: The page with the schema must be the canonical URL for the listing. Duplicate pages with the same schema cause indexing problems.
Validation and Testing
Always validate your schema before deploying at scale:
- Google Rich Results Test:
https://search.google.com/test/rich-results— paste your URL or code and get immediate validation feedback - Schema.org Validator:
https://validator.schema.org— validates against the schema.org spec - Google Search Console: After deployment, check the "Rich Results" report under "Search Appearance" — it will show any errors across your full site
Test your schema template with multiple job types (remote/onsite, with/without salary, different employment types) before rolling out to production. A single template bug can affect thousands of pages.
Monitoring Performance
After implementation, track Google for Jobs performance in Search Console:
- Navigate to Search Console → Performance → Search type: Google for Jobs
- Monitor impressions, clicks, and CTR specifically for job listing pages
- Watch the Rich Results Status report for any new errors introduced by content changes
Expect 4–8 weeks for Google to fully recrawl and index your schema markup after initial implementation. For large sites, submit your sitemap after deployment to accelerate crawling.
Frequently Asked Questions
What JSON-LD schema does Google for Jobs require?
Google requires JobPosting schema with: title, description, datePosted, hiringOrganization (name), and jobLocation (addressLocality, addressCountry). Recommended: baseSalary, employmentType, validThrough.
How long does it take for Google to index job listings?
Google typically indexes new JobPosting schema within 1-3 days. Use Google Search Console to submit your sitemap and monitor indexing. Ensure your pages are crawlable and the JSON-LD validates correctly.
Does salary in Google for Jobs need to be annual?
Google supports multiple salary units (HOUR, DAY, WEEK, MONTH, YEAR). Always specify the unitText. If your data stores salary in thousands (like JobDataLake where 150 = $150k), multiply by 1000 for the schema value.
Try JobDataLake
1M+ enriched job listings from 20,000+ companies. Free API key with 1,000 credits — no credit card required.