Skip to content

Google for Jobs on WordPress

The Recruitly WordPress plugin syncs your published jobs into WordPress as a current-vacancies custom post type, with every job attribute stored as post meta (full field reference). The plugin does not output JobPosting markup itself — you add it once in your theme, using the metadata the plugin has already synced.

Prerequisites

  • A WordPress site with the Recruitly plugin installed and jobs syncing
  • Access to your theme's functions.php (WordPress Admin → Appearance → Theme Editor, or FTP/cPanel at /wp-content/themes/your-theme/functions.php)
  • A Google Search Console account for monitoring (recommended)

TIP

Use a child theme (or a small custom plugin / Code Snippets plugin) so the code survives theme updates.

The snippet

php
/**
 * Add Google for Jobs (JobPosting) structured data to Recruitly job posts.
 * Customize the values marked CUSTOMIZE below.
 */
function custom_recruitly_google_jobs_structured_data() {
    if (is_singular('current-vacancies')) { // Recruitly post type
        global $post;

        // CUSTOMIZE THESE VALUES
        $agency_name     = 'Your Agency Name'; // Fallback hiring organisation
        $currency        = 'GBP';              // GBP, USD, EUR, AUD, CAD, INR, ...
        $default_country = 'GB';               // 2-letter code: GB, US, AU, CA, IN, ...

        // Map your Recruitly job types to Google's employment types
        $employment_type_map = array(
            'Full Time'  => 'FULL_TIME',
            'Part Time'  => 'PART_TIME',
            'Contract'   => 'CONTRACTOR',
            'Temporary'  => 'TEMPORARY',
            'Intern'     => 'INTERN',
            'Freelance'  => 'CONTRACTOR',
            'Permanent'  => 'FULL_TIME',
            'Casual'     => 'PART_TIME',
            'Fixed Term' => 'TEMPORARY',
            // Add more mappings as needed
        );

        // Job meta synced by the Recruitly plugin
        $job_type     = get_post_meta($post->ID, 'jobType', true);
        $posted_on    = get_post_meta($post->ID, 'postedOn', true);
        $company_name = get_post_meta($post->ID, 'companyName', true);
        $min_salary   = get_post_meta($post->ID, 'minSalaryRange', true);
        $max_salary   = get_post_meta($post->ID, 'maxSalaryRange', true);

        // Location meta
        $country        = get_post_meta($post->ID, 'countryCode', true) ?: $default_country;
        $city           = get_post_meta($post->ID, 'town', true);
        $region         = get_post_meta($post->ID, 'county', true);
        $postal_code    = get_post_meta($post->ID, 'postCode', true);
        $remote_working = get_post_meta($post->ID, 'remoteWorking', true);

        $employment_type = isset($employment_type_map[$job_type]) ? $employment_type_map[$job_type] : 'FULL_TIME';

        $structured_data = array(
            '@context'       => 'https://schema.org/',
            '@type'          => 'JobPosting',
            'title'          => get_the_title(),
            'description'    => strip_tags(get_the_content()),
            'datePosted'     => date('Y-m-d', strtotime($posted_on)),
            'employmentType' => $employment_type,
            'validThrough'   => date('Y-m-d', strtotime($posted_on . ' + 60 days')) . 'T23:59:59'
        );

        // Hiring organisation — client company if present, otherwise your agency
        $structured_data['hiringOrganization'] = array(
            '@type' => 'Organization',
            'name'  => $company_name ?: $agency_name
        );

        if ($remote_working == 'true' || $remote_working == 1) {
            $structured_data['jobLocationType'] = 'TELECOMMUTE';
        }

        $location_data = array(
            '@type'   => 'Place',
            'address' => array(
                '@type'          => 'PostalAddress',
                'addressCountry' => $country
            )
        );
        if ($city)        $location_data['address']['addressLocality'] = $city;
        if ($region)      $location_data['address']['addressRegion']   = $region;
        if ($postal_code) $location_data['address']['postalCode']      = $postal_code;
        $structured_data['jobLocation'] = $location_data;

        // Salary — optional, but jobs with salary get significantly more clicks
        if ($min_salary || $max_salary) {
            $salary_data = array(
                '@type'    => 'MonetaryAmount',
                'currency' => $currency
            );
            if ($min_salary && $max_salary) {
                $salary_data['value'] = array(
                    '@type'    => 'QuantitativeValue',
                    'minValue' => floatval($min_salary),
                    'maxValue' => floatval($max_salary),
                    'unitText' => 'YEAR'
                );
            } else {
                $salary_data['value'] = array(
                    '@type'    => 'QuantitativeValue',
                    'value'    => floatval($min_salary ?: $max_salary),
                    'unitText' => 'YEAR'
                );
            }
            $structured_data['baseSalary'] = $salary_data;
        }

        ?>
        <script type="application/ld+json">
        <?php echo wp_json_encode($structured_data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); ?>
        </script>
        <?php
    }
}
add_action('wp_head', 'custom_recruitly_google_jobs_structured_data');

Customise your values

VariableWhat it doesExample
$agency_nameHiring organisation whenever a job has no client company (or you run confidential roles)'Acme Recruitment'
$currencyISO currency code for salaries'GBP', 'USD', 'EUR', 'AUD'
$default_countryFallback 2-letter country code when a job has none'GB', 'US', 'AU'
$employment_type_mapMaps every job-type label your agency uses to a Google valuesee snippet

Google accepts only these employment types: FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN, VOLUNTEER, PER_DIEM, OTHER. Any label of yours that isn't mapped falls back to FULL_TIME — add every label you use in Recruitly to the map.

Meta fields used

All synced by the plugin onto each current-vacancies post:

Meta keyContents
jobTypeEmployment type label as set in Recruitly (e.g. Permanent, Contract)
postedOnPosting date
companyNameHiring company (empty for confidential clients)
minSalaryRange / maxSalaryRangeSalary range
countryCode / country2-letter country code / country name
town / county / postCodeCity, region, postcode
remoteWorkingSet when the job is remote

The full list of synced fields (recruiter, web advert sections, images, taxonomies) is in the Custom Post Type reference.

Next: validate and submit

Head to Testing & Search Console — check a job page with the Rich Results Test, submit your sitemap, and monitor the Job postings report.

Recruitly — Recruitment CRM for Agencies