Recruiting Ops

How an Indeed XML Feed and Google for Jobs JSON-LD Actually Work

ClarityHire Team(Editorial)5 min read

The two main distribution channels

When you post a job on Indeed or Google for Jobs, you have two options:

  1. Post directly — log in to Indeed.com, fill out a form, click publish
  2. Syndicate via feed — provide your own job listing in a machine-readable format (XML or JSON-LD); the job board pulls from your feed and indexes it

Direct posting is easy but limited. Syndication is a bit more technical but gives you precise control: you can update a job's status, salary, or requirements in your system, and it reflects on Indeed within hours without re-entering data.

Most serious hiring teams use feeds.

The Indeed XML format

Indeed accepts a proprietary XML feed. Here's the structure:

<?xml version="1.0" encoding="UTF-8"?>
<source>
  <publisher>Your Company Name</publisher>
  <publisherurl>https://yourcompany.com</publisherurl>
  <lastBuildDate>2026-05-21T10:00:00Z</lastBuildDate>
  <job>
    <title>Senior Backend Engineer</title>
    <date>2026-05-21T08:00:00Z</date>
    <repourl>https://yourcompany.com/jobs/senior-backend-engineer</repourl>
    <description><![CDATA[
      <p>We are hiring a senior backend engineer...</p>
      <ul>
        <li>5+ years Python/Go experience</li>
      </ul>
    ]]></description>
    <salary>120000</salary>
    <hiringOrganization>Your Company</hiringOrganization>
    <jobLocation>
      <countryName>US</countryName>
      <stateName>California</stateName>
      <cityName>San Francisco</cityName>
    </jobLocation>
    <employmentType>FULL_TIME</employmentType>
    <jobCategory>ENGINEER</jobCategory>
  </job>
</source>

Key elements:

  • <title>: Job title (required)
  • <description>: Full job description. Wrap in <![CDATA[...]]> if it contains HTML or special characters. Don't put HTML inside the CDATA — keep it clean text or add the HTML markup.
  • <salary>: Base salary in USD. Some regions accept salary ranges, but Indeed primarily indexes single values.
  • <jobLocation>: City, state, country. If remote, either omit or set city to "Remote."
  • <date>: When the job was posted (ISO 8601 format). Indeed uses this to prioritize newer postings.
  • <repourl>: Link back to your careers page for that specific job. When someone clicks "Apply," they land here.
  • <employmentType>: FULL_TIME, PART_TIME, CONTRACT, TEMPORARY

Google for Jobs: JSON-LD

Google has a different format called JobPosting structured data in JSON-LD. This is embedded in your HTML as a <script type="application/ld+json"> tag.

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "JobPosting",
  "title": "Senior Backend Engineer",
  "description": "We are hiring a senior backend engineer...",
  "identifier": {
    "@type": "PropertyValue",
    "name": "Your Company",
    "value": "job_123"
  },
  "datePosted": "2026-05-21",
  "hiringOrganization": {
    "@type": "Organization",
    "name": "Your Company",
    "sameAs": "https://yourcompany.com",
    "logo": "https://yourcompany.com/logo.png"
  },
  "jobLocation": {
    "@type": "Place",
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "123 Main St",
      "addressLocality": "San Francisco",
      "addressRegion": "CA",
      "postalCode": "94105",
      "addressCountry": "US"
    }
  },
  "baseSalary": {
    "@type": "PriceSpecification",
    "currency": "USD",
    "minValue": 120000,
    "maxValue": 160000,
    "unitText": "YEAR"
  },
  "employmentType": "FULL_TIME",
  "validThrough": "2026-06-21"
}
</script>

Critical fields for Google to index:

  • datePosted: Must be present and recent. Google de-ranks old postings.
  • hiringOrganization: Company name and (optionally) logo. Used for the employer brand snippet.
  • baseSalary: Salary range (min/max). Optional but improves CTR.
  • validThrough: Job closing date. Google removes postings after this date.
  • jobLocation: Full postal address or remote.

CDATA pitfalls and special characters

In XML, special characters like <, >, and & break the feed. Wrap your description in <![CDATA[...]]>:

<description><![CDATA[
We are looking for a developer with 5+ years experience.
Experience with: Java, Spring Boot, Docker.
]]></description>

Inside CDATA, you can include plain HTML:

<description><![CDATA[
<p>We are looking for a developer with 5+ years experience.</p>
<ul>
  <li>Java 8+</li>
  <li>Spring Boot</li>
  <li>Docker</li>
</ul>
]]></description>

Without CDATA, the <p> and <li> tags break the XML parser. Indeed's feed validator will reject the entire feed.

Salary encoding

Indeed XML: Single salary value only. If you have a range, use the midpoint or the low end.

<salary>140000</salary>

Google for Jobs: Supports ranges:

"baseSalary": {
  "@type": "PriceSpecification",
  "currency": "USD",
  "minValue": 120000,
  "maxValue": 160000,
  "unitText": "YEAR"
}

If your job is hourly, use:

"baseSalary": {
  "@type": "PriceSpecification",
  "currency": "USD",
  "minValue": 45,
  "maxValue": 65,
  "unitText": "HOUR"
}

Update frequency and refreshes

Once you've set up a feed URL, Indeed crawls it periodically (usually daily, sometimes more frequently if jobs change often). When a job closes, remove it from the feed or set a closing date. Indeed will automatically de-index it.

Google for Jobs crawls the HTML pages where you embed JobPosting JSON-LD. Every time a candidate visits your job page, Google's crawler indexes it. If you remove the JSON-LD markup, the job disappears from Google for Jobs within days.

Validation and debugging

For Indeed XML:

  • Use Indeed's Feed Validator to test your feed before connecting it
  • Common errors: malformed CDATA, missing required fields, invalid date format

For Google for Jobs:

How ClarityHire generates feeds

ClarityHire auto-generates both an Indeed XML feed at /api/jobs/feed.xml and a Google for Jobs JSON-LD template that you embed on your job pages at /careers/jobs/[job-id].

When you update a job (title, salary, description), the feeds refresh automatically. Closed jobs are removed from feeds within 2 hours. The system respects your job's visibility flag: draft jobs don't appear in feeds.

TL;DR

Indeed expects XML with CDATA-wrapped descriptions, salary, and location. Google for Jobs expects JSON-LD markup on your HTML with datePosted, validThrough, and salary ranges. Both use ISO date formats (YYYY-MM-DD or ISO 8601 timestamps). CDATA is your friend when wrapping HTML descriptions in XML. Validate before deploying. Auto-remove closed jobs from feeds. Test with Google's Rich Results Test and Indeed's validator.

Once set up, your jobs distribute to Indeed and Google without re-entry. The setup takes a day; the ongoing leverage is immense.

job board integrationxml feedgoogle for jobsjson-ldjob posting

Related Articles