# JobPosting Structured Data for Job Listings

> Adds JobPosting JSON-LD (salary, employment type, location or remote, closing date and employer) to single job listings, so they can appear in Google for Jobs.

- Type: PHP
- Runs: Frontend only
- Category: [SEO](https://wpide.com/library/category/seo/)
- Tags: schema, jobs, json-ld
- Author: WPIDE Team
- Installs: 0
- Updated: 2026-10-28
- Page: https://wpide.com/library/seo-job-posting-schema/

## Code

```php
add_action( 'wp_head', function () {
    if ( ! is_singular( 'job' ) ) {
        return;
    }

    $post = get_queried_object();
    $get  = function ( $key ) use ( $post ) {
        return trim( (string) get_post_meta( $post->ID, '_job_' . $key, true ) );
    };

    $data = array(
        '@context'           => 'https://schema.org',
        '@type'              => 'JobPosting',
        'title'              => wp_strip_all_tags( get_the_title( $post ) ),
        'description'        => wp_kses_post( wpautop( $post->post_content ) ),
        'datePosted'         => get_the_date( DATE_W3C, $post ),
        'employmentType'     => $get( 'type' ),
        'directApply'        => false,
        'hiringOrganization' => array(
            '@type'  => 'Organization',
            'name'   => get_bloginfo( 'name' ),
            'sameAs' => home_url( '/' ),
            'logo'   => has_custom_logo() ? wp_get_attachment_image_url( (int) get_theme_mod( 'custom_logo' ), 'full' ) : '',
        ),
    );

    if ( $get( 'valid_through' ) ) {
        $data['validThrough'] = $get( 'valid_through' ) . 'T23:59:59' . wp_date( 'P' );
    }

    if ( $get( 'city' ) || $get( 'country' ) ) {
        $data['jobLocation'] = array(
            '@type'   => 'Place',
            'address' => array_filter( array( '@type' => 'PostalAddress', 'addressLocality' => $get( 'city' ), 'addressCountry' => $get( 'country' ) ) ),
        );
    }

    if ( $get( 'remote' ) ) {
        $data['jobLocationType'] = 'TELECOMMUTE';

        if ( $get( 'country' ) ) {
            $data['applicantLocationRequirements'] = array( '@type' => 'Country', 'name' => $get( 'country' ) );
        }
    }

    if ( is_numeric( $get( 'salary_min' ) ) && $get( 'currency' ) ) {
        $value = array( '@type' => 'QuantitativeValue', 'unitText' => $get( 'salary_unit' ) ? $get( 'salary_unit' ) : 'YEAR' );

        if ( is_numeric( $get( 'salary_max' ) ) && (float) $get( 'salary_max' ) > (float) $get( 'salary_min' ) ) {
            $value['minValue'] = (float) $get( 'salary_min' );
            $value['maxValue'] = (float) $get( 'salary_max' );
        } else {
            $value['value'] = (float) $get( 'salary_min' );
        }

        $data['baseSalary'] = array( '@type' => 'MonetaryAmount', 'currency' => strtoupper( $get( 'currency' ) ), 'value' => $value );
    }

    $data['hiringOrganization'] = array_filter( $data['hiringOrganization'] );

    echo '<script type="application/ld+json">' . wp_json_encode( array_filter( $data, function ( $v ) { return '' !== $v && null !== $v; } ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG ) . "</script>\n";
} );
```

## Notes

Reads the Job Details meta box fields. Remove or expire listings once filled; Google penalises stale job markup.

## How to use it

Install the free WPIDE plugin (https://wordpress.org/plugins/wpide/), find "JobPosting Structured Data for Job Listings" in its snippet library and add it in one click; or create a new PHP snippet in WPIDE (or any code snippets plugin) and paste the code above. More snippets: https://wpide.com/library/
