Skip to main content

How to Get Featured on Google for Jobs: Schema Optimization & API Indexing Guide

Why isn't your job board ranking in Google for Jobs? Learn how to fix JobPosting schema bugs, handle remote jobs correctly, and use the Google Indexing API to get indexed in minutes.

June 8, 20268 min readBy Deepak Sharma
ShareXLinkedIn
How to Get Featured on Google for Jobs: Schema Optimization & API Indexing Guide

If you run a job board, standard SEO rules do not apply.

Normally, you submit a sitemap, wait a few days or weeks, and eventually, Google indexers find your page. But for job boards, this standard cycle is a death sentence. By the time Google crawls your new posting, the role is often closed, filled, or stale.

To show up in the premium Google for Jobs search widget (which drives the majority of organic job search traffic), you need two things: strictly validated schema data and real-time indexing.

In this guide, we'll walk through the exact schema pitfalls that keep job postings out of Google Search, how to format remote jobs correctly, and how to integrate the Google Indexing API to get new postings live in minutes.


1. Schema Pitfalls That Get Your Jobs Rejected

Google reads job postings through the JobPosting JSON-LD schema. If your schema has even one error or missing recommended property, Google Search Console (GSC) will flag it, and Google for Jobs might ignore your listing entirely.

Here are the four biggest schema mistakes and how to fix them.

Pitfall #1: Setting jobLocation on 100% Remote Jobs

Google has strict guidelines for remote jobs. If a job is fully remote (work from home), Google's guidelines state you must omit the jobLocation property entirely.

If you specify both jobLocation (e.g., pointing to a physical address) and jobLocationType: "TELECOMMUTE", Google's parser gets confused. As a result, your listing won't show up when users apply the "Remote" filter.

The Correct Way to Define Remote Jobs:

{
  "@context": "https://schema.org",
  "@type": "JobPosting",
  "title": "Senior React Developer",
  "jobLocationType": "TELECOMMUTE",
  "applicantLocationRequirements": {
    "@type": "Country",
    "name": "IN"
  }
}

Pitfall #2: Using Country Names instead of ISO 2-Letter Codes

For both addressCountry (inside jobLocation) and applicantLocationRequirements, Google requires country codes to follow the ISO 3166-1 alpha-2 standard.

Passing "India" or "United States" is one of the most common validation warnings. You must map country names to their respective two-letter codes:

const COUNTRY_TO_ISO: Record<string, string> = {
  india: 'IN',
  'united states': 'US',
  usa: 'US',
  'united kingdom': 'GB',
  uk: 'GB',
  canada: 'CA',
  germany: 'DE'
}

function toIsoCountry(countryName: string): string {
  return COUNTRY_TO_ISO[countryName.trim().toLowerCase()] ?? countryName.trim();
}

Pitfall #3: Sending Plain Text Instead of HTML Descriptions

Google's job widget displays the description field exactly as it's provided in your schema. If you strip all formatting to a plain-text string, your job post will look like a massive, unreadable wall of text.

Google for Jobs explicitly supports basic HTML tags in the schema description: <p>, <ul>, <li>, <strong>, <em>, and <br/>.

Instead of stripping markdown, convert it to basic HTML before injecting it into the JSON-LD script:

function markdownToBasicHtml(text: string): string {
  if (!text) return '';
  let html = text.replace(/\r\n/g, '\n').trim();
  
  // Convert Markdown syntax to basic HTML tags
  html = html
    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
    .replace(/\*(.*?)\*/g, '<em>$1</em>')
    .replace(/^#{1,6}\s+(.*?)$/gm, '<strong>$1</strong>');

  // Map bullet points to <ul> and <li> tags
  const lines = html.split('\n');
  const result: string[] = [];
  let inList = false;

  for (const line of lines) {
    const trimmed = line.trim();
    const listMatch = trimmed.match(/^[-*+]\s+(.*)$/);

    if (listMatch) {
      if (!inList) {
        result.push('<ul>');
        inList = true;
      }
      result.push(`<li>${listMatch[1]}</li>`);
    } else {
      if (inList) {
        result.push('</ul>');
        inList = false;
      }
      if (trimmed === '') {
        result.push('<br/>');
      } else {
        result.push(`<p>${trimmed}</p>`);
      }
    }
  }
  if (inList) result.push('</ul>');

  return result.join('\n');
}

Pitfall #4: Missing the validThrough Field

Google requires or highly recommends a validThrough date (expiration date). If you leave this out, Google Search Console will flag your listings with warnings.

If your database doesn't store an explicit expiration date, derive one dynamically (e.g., datePosted + 60 days) so Google knows the listing is fresh:

const validThrough = job.valid_through 
  ?? new Date(new Date(job.date_posted).getTime() + 60 * 24 * 60 * 60 * 1000)
       .toISOString().split('T')[0];

2. Pushing Updates in Real-Time: Google Indexing API

Even with perfect schema, waiting for Google's bot to find your sitemaps is too slow. To get jobs listed instantly, you must use the Google Indexing API.

The Indexing API allows you to notify Google immediately when a page is added, updated, or removed, forcing Googlebot to schedule a crawl within minutes.

Setting Up the Indexing API in 4 Steps:

  1. Enable the API: Go to the Google Cloud Console, select your project, and search for the Web Search Indexing API. Click Enable.
  2. Create a Service Account: Go to IAM & Admin > Service Accounts, create a service account, and download its credentials as a JSON private key file.
  3. Add as Property Owner (Crucial): Open Google Search Console. Go to Settings > Users and permissions. Click Add User, enter the Service Account email address, and select Owner permissions. (Note: The Indexing API will reject requests with a 403 error if the service account has only 'Full' or 'Viewer' access. It must be an Owner).
  4. Deploy Credentials: Save the JSON key securely in your environment variables (e.g., GOOGLE_INDEXING_KEY_JSON).

Node.js Script to Notify Google:

Below is a complete, dependency-free Node.js script to authorize your service account using JWT and notify Google of new job URLs:

import { createSign } from 'crypto';

function base64url(str) {
  return Buffer.from(str).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}

// Generate JWT token to authenticate with Google APIs
async function getAccessToken(serviceAccount) {
  const now = Math.floor(Date.now() / 1000);
  const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));
  const claim = base64url(JSON.stringify({
    iss: serviceAccount.client_email,
    scope: 'https://www.googleapis.com/auth/indexing',
    aud: 'https://oauth2.googleapis.com/token',
    exp: now + 3600,
    iat: now,
  }));
  const signingInput = `${header}.${claim}`;
  const sign = createSign('RSA-SHA256');
  sign.update(signingInput);
  const signature = sign.sign(serviceAccount.private_key, 'base64')
    .replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
  
  const jwt = `${signingInput}.${signature}`;
  const res = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({ 
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', 
      assertion: jwt 
    }),
  });
  
  const data = await res.json();
  return data.access_token;
}

// Notify Google Search index of new or updated URL
export async function notifyGoogle(url, keyJson) {
  const serviceAccount = JSON.parse(keyJson);
  const token = await getAccessToken(serviceAccount);
  
  const res = await fetch('https://indexing.googleapis.com/v3/urlNotifications:publish', {
    method: 'POST',
    headers: { 
      'Content-Type': 'application/json', 
      Authorization: `Bearer ${token}` 
    },
    body: JSON.stringify({ url, type: 'URL_UPDATED' }),
  });
  
  if (!res.ok) {
    const errorData = await res.json();
    throw new Error(`Google API error: ${errorData.error?.message}`);
  }
  return true;
}

3. The "Email Not Found" Search Console Workaround

When adding your Service Account email (e.g., bot@my-project.iam.gserviceaccount.com) in Search Console, Google often returns a frustrating "Email not found" error. This is a known, long-standing bug in the modern Search Console interface.

The Legacy Workaround:

If the modern interface fails, use Google's legacy Webmaster Central portal:

  1. Visit the Legacy Webmaster Verification Portal.
  2. Click on your website property.
  3. Scroll to the bottom of the page and click Add an Owner.
  4. Paste the Service Account email address and click Continue.

This legacy endpoint bypasses the modern email validation check and immediately registers your service account as a verified owner.


Google for Jobs SEO Checklist

Before launching your job board, run a quick checklist:

  • Fully remote jobs have jobLocation omitted.
  • Country codes are standard 2-letter ISO codes (IN, US, GB).
  • Job descriptions are formatted using basic HTML tags in schema.
  • validThrough date is set (explicitly or derived).
  • Google Indexing API service account is set up with Owner access.
  • New URLs trigger an Indexing API call immediately upon publish.

By implementing these fixes, we aligned OnlyFrontendJobs' SEO structure to match and exceed the top players in the ecosystem. Your jobs will get indexed in minutes, maintain clean layout structures in Google's widgets, and correctly capture remote-work traffic.


Found this useful? Share it.

ShareXLinkedIn

Ready to land your next frontend role?

Browse curated frontend jobs from top companies — updated daily.

Browse Frontend Jobs →