<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: sandy S.</title>
    <description>The latest articles on DEV Community by sandy S. (@sandystone).</description>
    <link>https://dev.to/sandystone</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4013201%2Fc8d9fafe-70de-4aeb-b263-e35f64957166.png</url>
      <title>DEV Community: sandy S.</title>
      <link>https://dev.to/sandystone</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sandystone"/>
    <language>en</language>
    <item>
      <title>Implementing JobPosting Schema for a Job Board: What I Learned</title>
      <dc:creator>sandy S.</dc:creator>
      <pubDate>Fri, 03 Jul 2026 08:09:12 +0000</pubDate>
      <link>https://dev.to/sandystone/implementing-jobposting-schema-for-a-job-board-what-i-learned-1bcc</link>
      <guid>https://dev.to/sandystone/implementing-jobposting-schema-for-a-job-board-what-i-learned-1bcc</guid>
      <description>&lt;p&gt;If you've ever tried to get "&lt;strong&gt;rich results&lt;/strong&gt;" for job listings on Google Search — the ones with the salary, location, and posted-date pills right in the SERP — you've probably run into Google's &lt;strong&gt;JobPosting&lt;/strong&gt; schema docs and immediately wanted to close the tab. It's one of the more verbose schema types out there, with a long list of "&lt;strong&gt;recommended&lt;/strong&gt;" properties that all seem mandatory until you actually read the fine print.&lt;/p&gt;

&lt;p&gt;Here's what I learned implementing it across listing pages on a live job board, and why I ended up going with a minimal &lt;strong&gt;JobPosting&lt;/strong&gt; + &lt;strong&gt;ItemList&lt;/strong&gt; combination instead of the full schema Google's examples suggest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with going "&lt;strong&gt;full schema&lt;/strong&gt;"
&lt;/h2&gt;

&lt;p&gt;The naive approach is to take Google's sample JobPosting JSON-LD and fill in every property: &lt;strong&gt;baseSalary&lt;/strong&gt;, &lt;strong&gt;employmentType&lt;/strong&gt;, &lt;strong&gt;jobLocation&lt;/strong&gt;, &lt;strong&gt;hiringOrganization&lt;/strong&gt;, &lt;strong&gt;validThrough&lt;/strong&gt;, &lt;strong&gt;applicantLocationRequirements&lt;/strong&gt;, &lt;strong&gt;jobLocationType&lt;/strong&gt;... the list goes on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two problems show up fast when you try this on real listing data:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Data completeness. Not every job posting has a clean, structured salary. A lot of listings say things like "Salary: Best in Industry" or "Negotiable" — which isn't a valid baseSalary value, and stuffing a fake number in there to satisfy the schema is a fast way to get flagged for spam or misleading markup.&lt;br&gt;
Maintenance overhead. Every optional property is a property you now have to keep accurate. validThrough in particular is unforgiving — if a listing expires and you don't update or remove the markup, Google will eventually stop trusting your JobPosting data altogether across the whole domain.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I went with instead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For each listing page, I output:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One ItemList at the page level, listing all jobs shown on that page (this maps naturally to how Google wants paginated listing pages structured)&lt;br&gt;
A minimal JobPosting block per job — only the properties I could guarantee were accurate at time of publish and would stay accurate without manual upkeep&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;json&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ItemList"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"itemListElement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/jobs/senior-backend-developer-ahmedabad"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com/jobs/hr-executive-rajkot"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on each individual job page, something closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;json&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://schema.org"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JobPosting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Senior Backend Developer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"datePosted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hiringOrganization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Organization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Example Pvt Ltd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sameAs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Place"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PostalAddress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"addressLocality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ahmedabad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"addressRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gujarat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"addressCountry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IN"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"employmentType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FULL_TIME"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's missing: no baseSalary, no validThrough. I only added those two properties on listings where the source data was actually structured (a real numeric salary range, a real expiry date) rather than backfilling them everywhere for "completeness."&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What broke, and what Google actually indexed&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A few things I didn't expect going in:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rich results didn't show up immediately, even on valid pages. Google Search Console's "Enhancement" reports lag behind actual crawling by days, sometimes longer. Don't panic-debug valid markup just because GSC hasn't caught up yet.&lt;br&gt;
Listings without validThrough still got indexed, but Google's docs are explicit that missing it may shorten how long a posting is eligible for the job-specific rich result, worth knowing before you decide to omit it broadly like I did.&lt;br&gt;
Duplicate or thin description fields caused more validation warnings than any structural schema error. The schema was syntactically fine; Google just didn't think the underlying content was worth surfacing. Rich snippets can't fix thin content.&lt;br&gt;
The ItemList at the page level had zero impact on individual job rich results — it's a separate signal, mainly useful for how Google understands the listing/collection page itself, not the jobs within it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The takeaway&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're adding &lt;strong&gt;JobPosting&lt;/strong&gt; schema to a board with real, imperfect data (which is most of them), resist the urge to fill in every optional property Google's documentation shows in the example. A smaller set of properties you can actually keep accurate will outperform a "complete" schema block full of soft-filled or stale data over time, both for trust with Google and for your own sanity maintaining it.&lt;/p&gt;

&lt;p&gt;I implemented this on &lt;a href="https://jobgrin.co.in" rel="noopener noreferrer"&gt;JobGrin&lt;/a&gt;, an Indian job portal, happy to answer questions if you're working through the same thing.&lt;/p&gt;

</description>
      <category>jobposting</category>
      <category>schema</category>
      <category>jobportal</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
