If you publish job listings and want them surfaced in job search results, you need JobPosting structured data. The specification is short. Most implementations still get the same handful of things wrong.
The minimum that actually works
{
"@context": "https://schema.org",
"@type": "JobPosting",
"title": "Data Engineer",
"description": "<p>Full HTML description…</p>",
"datePosted": "2026-08-16",
"hiringOrganization": { "@type": "Organization", "name": "Acme" },
"jobLocation": {
"@type": "Place",
"address": {
"@type": "PostalAddress",
"addressLocality": "Berlin",
"addressCountry": "DE"
}
}
}
The three fields people get wrong
- datePosted. Must be when the employer published the role, not when you ingested it. Boards that stamp ingestion time make every listing look fresh, which is exactly the behaviour the field exists to expose.
- Remote roles. A remote job needs
jobLocationType: "TELECOMMUTE", and if there is a geographic restriction,applicantLocationRequirements. Putting the company headquarters injobLocationfor a remote role is both wrong and common. - baseSalary. If you do not have a salary, omit the field. Emitting a zero, a placeholder, or a made-up range is worse than silence.
validThrough is not optional in practice
Listings without an expiry stay eligible until something removes them. Set validThrough, and remove the markup when the role closes. A listing that outlives the requisition is the single most common complaint about job search results, and it is a self-inflicted one.
Things that look helpful and are not
- Marking up a search results page as a JobPosting. It is a list, not a posting.
- Duplicating markup for the same role on several of your own URLs. Pick a canonical.
- Inflating
descriptionwith keywords. It is rendered to users.
How to check
Validate the JSON, then look at the rendered page and ask whether the markup describes what a person sees. Markup that disagrees with the visible page is the category of error that gets a site removed from job results rather than merely ranked lower.