SEO & Technical2026-04-189 min read

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: 150000 not minValue: 150.
  • Remote + in-person conflict: Don't specify both jobLocationType: "TELECOMMUTE" and a physical jobLocation — 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.