Claude Skill

polylang-rest-headless

Build or audit REST and headless integrations with Polylang 3.8.5, Polylang Pro 3.8.5, and Polylang for WooCommerce 2.2.2. Covers REST lang parameter behavior, pll/v1 languages, filterable REST routes, Pro lang/translations REST fields, pll_rest_api_post_types and pll_rest_api_ta

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download lonsdale201-wp-agent-skills-polylang_polylang-rest-headless-52f6020.zip · 4 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/polylang/polylang-rest-headless
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
Git git clone https://github.com/Lonsdale201/wp-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Polylang REST and Headless

Use this skill when a client, plugin, or importer talks to WordPress REST and expects language-aware content.

Core Polylang and Polylang Pro behave differently:

Stack REST behavior
Polylang core Defines current language from lang; exposes pll/v1/languages and settings controllers.
Polylang Pro Adds lang and translations REST fields to translated posts/terms; filters collections by lang; adds translation endpoints.
Polylang for WooCommerce Replaces generic Pro handling for products/orders/product taxonomies with Woo-aware handlers.

Current language in REST

Polylang core reads a lang request parameter during REST dispatch:

GET /wp-json/wp/v2/posts?lang=fr

If lang is valid, pll_current_language() returns that language during the request. If an invalid lang is sent and a default language exists, Polylang falls back to the default language.

Do not rely on URL prefixes alone in custom REST routes. Read the language through Polylang:

register_rest_route( 'myplugin/v1', '/cards', array(
    'methods'             => WP_REST_Server::READABLE,
    'permission_callback' => '__return_true',
    'callback'            => static function ( WP_REST_Request $request ) {
        $lang = function_exists( 'pll_current_language' ) ? pll_current_language() : false;

        if ( ! $lang && function_exists( 'pll_default_language' ) ) {
            $lang = pll_default_language();
        }

        return rest_ensure_response( array(
            'lang' => $lang ?: get_locale(),
        ) );
    },
) );

For state-changing custom routes, still use normal REST permissions and nonces/auth. Language is not authorization.

Languages endpoint

Polylang core registers:

  • GET /wp-json/pll/v1/languages
  • POST /wp-json/pll/v1/languages
  • GET|PUT|PATCH|DELETE /wp-json/pll/v1/languages/<term_id>
  • GET /wp-json/pll/v1/languages/<slug>

The response schema includes fields such as term_id, name, slug, locale, w3c, is_rtl, flag_url, is_default, active, home_url, search_url, page_on_front, page_for_posts, fallbacks, and term_props.

Use this endpoint for language selector bootstrapping in headless clients. Do not hardcode language lists in JS.

Filterable REST routes

Core Polylang detects REST routes for translated show_in_rest post types and taxonomies, plus wp/v2/search. It exposes the filter list to editor scripts and can add query parameters to preload paths.

The filter is:

add_filter( 'pll_filtered_rest_routes', static function ( array $routes ): array {
    $routes['myplugin_item'] = 'myplugin/v1/items';
    return $routes;
} );

Use this only for collection routes that accept a lang parameter and return language-filterable objects. Do not mark single-object routes ending with an ID as collection filters.

Polylang Pro fields

Polylang Pro 3.8 registers REST fields on translated REST-enabled post types and taxonomies:

{
  "lang": "en",
  "translations": {
    "en": 123,
    "fr": 456
  }
}

The lang field is a string enum of language slugs. The translations field is an object whose keys are slugs and values are IDs.

Write pattern:

PUT /wp-json/wp/v2/book/456
Content-Type: application/json
X-WP-Nonce: ...

{
  "lang": "fr",
  "translations": {
    "en": 123
  }
}

The Pro update callback checks language validity and translation capabilities. Invalid language returns rest_invalid_language_code; insufficient translation permission returns rest_cannot_set_language.

REST type filters in 3.8

Polylang Pro filters the REST-enabled object types it manages:

add_filter( 'pll_rest_api_post_types', static function ( array $post_types ): array {
    $post_types[] = 'book';
    return array_values( array_unique( $post_types ) );
} );

add_filter( 'pll_rest_api_taxonomies', static function ( array $taxonomies ): array {
    $taxonomies[] = 'genre';
    return array_values( array_unique( $taxonomies ) );
} );

In 3.8 the legacy format with options arrays is deprecated. Return a simple indexed array of post type or taxonomy names. The Pro source sanitizes legacy formats but calls _deprecated_argument().

Custom REST controllers

Polylang's request helper can infer object type from WP_REST_Posts_Controller or WP_REST_Terms_Controller. For a custom controller that does not extend those, identify the object type:

add_filter(
    'pll_rest_request_object_type',
    static function ( $type, array $handler, WP_REST_Request $request ) {
        if ( str_starts_with( $request->get_route(), '/myplugin/v1/books' ) ) {
            return 'post';
        }

        return $type;
    },
    10,
    3
);

Accepted return values are post, term, or null.

Pro translation endpoints

Polylang Pro 3.8 adds:

POST /wp-json/pll/v1/translation

Required body:

{
  "from_post": 123,
  "lang": "fr",
  "action": "duplicate"
}

It duplicates a translatable post to the target language through Pro's sync model. Permissions require edit_posts, read_post on the source, and the ability to translate into the requested language.

Untranslated posts endpoint:

GET /wp-json/pll/v1/untranslated-posts?type=book&lang=en&untranslated_in=fr&search=foo

Required query params are type, lang, and untranslated_in. This endpoint is useful for editor UIs and headless translation management.

Its permission check allows view-context reads and only enforces the post type edit capability for context=edit. Do not proxy it as a private management endpoint without your own permission layer.

WooCommerce REST

Polylang for WooCommerce 2.2.2 requires Polylang Pro 3.8+ for its REST module.

It removes generic Pro handling for:

  • product
  • product_variation
  • shop_order
  • product_cat
  • product_tag
  • product_brand
  • product_attribute_term

Then it registers Woo-aware handlers. For products:

  • lang and translations are exposed on Woo REST product objects.
  • SKU and global unique ID checks are language-aware during REST writes.
  • Batch product create reads lang from each create item through an internal FIFO queue.

Example:

POST /wp-json/wc/v3/products
Content-Type: application/json
Authorization: Basic ...

{
  "name": "Blue shirt",
  "type": "simple",
  "lang": "en",
  "translations": {
    "fr": 456
  }
}

For orders, Polylang WC adds lang to Woo order REST object queries:

GET /wp-json/wc/v3/orders?lang=fr

When HPOS is enabled, its HPOS query filter adds language JOIN/WHERE clauses for translated order types.

Headless rules

  • Always pass lang explicitly in REST collection reads.
  • Treat empty lang differently from missing lang: Polylang WC restores explicit lang => '' in Woo queries to mean all languages.
  • Use language endpoint data for switchers and route generation.
  • For writes, send both lang and translations only when the user has translation capability.
  • Do not use lang to bypass object permissions. Keep normal REST permission checks.
  • Do not return cached REST HTML/JSON without varying by language, user, and auth state where applicable.

Common mistakes

  • Expecting core Polylang alone to add lang and translations fields to posts. That is Pro behavior.
  • Returning legacy option arrays from pll_rest_api_post_types or pll_rest_api_taxonomies on 3.8+.
  • Filtering single-object routes by language as if they were collections.
  • Creating Woo products through REST without lang, then trying to repair language from SKU later.
  • Assuming Woo batch update/delete need language queue. Polylang WC's queue is for batch create items because those objects do not exist yet.

Cross-references

  • Use wp-rest-api for generic route security, nonce, schema, and permission rules.
  • Use polylang-object-translations for PHP-side linking after imports.
  • Use polylang-wc-compatibility for product/order language data store behavior.

Verification

Local source checked against:

  • REST language definition: wp-content/plugins/polylang/src/rest-request.php
  • REST request helper and pll_rest_request_object_type: src/modules/REST/Request.php
  • Languages endpoint schema/routes: src/modules/REST/V1/Languages.php
  • Pro REST fields and filters: polylang-pro/src/modules/rest/*
  • Woo REST module and batch queue: polylang-wc/src/modules/REST/*

References

  • Official documentation: https://polylang.pro/doc/rest-api/
  • Official documentation: https://polylang.pro/doc/function-reference/
  • Verified source paths:
    • wp-content/plugins/polylang/src/rest-request.php
    • wp-content/plugins/polylang/src/modules/REST/Request.php
    • wp-content/plugins/polylang/src/modules/REST/API.php
    • wp-content/plugins/polylang/src/modules/REST/V1/Languages.php
    • wp-content/plugins/polylang/src/filter-rest-routes.php
    • wp-content/plugins/polylang-pro/src/modules/rest/rest-api.php
    • wp-content/plugins/polylang-pro/src/modules/rest/Translatable/Abstract_object.php
    • wp-content/plugins/polylang-pro/src/modules/rest/Translated/Abstract_Object.php
    • wp-content/plugins/polylang-pro/src/modules/rest/V1/Translation.php
    • wp-content/plugins/polylang-pro/src/modules/rest/V1/Untranslated_Posts.php
    • wp-content/plugins/polylang-wc/src/modules/REST/Module.php
    • wp-content/plugins/polylang-wc/src/modules/REST/Translated/Product.php
    • wp-content/plugins/polylang-wc/src/modules/REST/Translated/Batch.php
    • wp-content/plugins/polylang-wc/src/modules/REST/Filtered/Order.php
Files (wp-agent-skills)
  • agents
    • openai.yaml 246 B
      interface:
        display_name: "Polylang REST and Headless"
        short_description: "Polylang REST and headless language APIs"
        default_prompt: "Use $polylang-rest-headless to build or audit REST, headless, or Woo REST language behavior for Polylang."
      
  • SKILL.md 10.4 KB
    ---
    name: polylang-rest-headless
    description: "Build or audit REST and headless integrations with Polylang 3.8.5, Polylang Pro 3.8.5, and Polylang for WooCommerce 2.2.2. Covers REST lang parameter behavior, pll/v1 languages, filterable REST routes, Pro lang/translations REST fields, pll_rest_api_post_types and pll_rest_api_taxonomies 3.8 format, pll/v1/translation and pll/v1/untranslated-posts, custom REST object-type detection, collection filtering, write permissions, and Woo REST product/order language behavior. Use when creating headless frontends, mobile clients, custom WP REST routes, or REST imports/updates for translated content."
    metadata:
      wp-skills-author: "Soczo Kristof"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "polylang + polylang-pro + polylang-wc"
      wp-skills-plugin-version-tested: "Polylang 3.8.5 + Polylang Pro 3.8.5 + Polylang for WooCommerce 2.2.2"
      wp-skills-wp-version-tested: "7.0"
      wp-skills-php-min: "7.4"
      wp-skills-last-updated: "2026-07-01"
    ---
    
    # Polylang REST and Headless
    
    Use this skill when a client, plugin, or importer talks to WordPress REST and expects language-aware content.
    
    Core Polylang and Polylang Pro behave differently:
    
    | Stack | REST behavior |
    |---|---|
    | Polylang core | Defines current language from `lang`; exposes `pll/v1/languages` and settings controllers. |
    | Polylang Pro | Adds `lang` and `translations` REST fields to translated posts/terms; filters collections by `lang`; adds translation endpoints. |
    | Polylang for WooCommerce | Replaces generic Pro handling for products/orders/product taxonomies with Woo-aware handlers. |
    
    ## Current language in REST
    
    Polylang core reads a `lang` request parameter during REST dispatch:
    
    ```http
    GET /wp-json/wp/v2/posts?lang=fr
    ```
    
    If `lang` is valid, `pll_current_language()` returns that language during the request. If an invalid `lang` is sent and a default language exists, Polylang falls back to the default language.
    
    Do not rely on URL prefixes alone in custom REST routes. Read the language through Polylang:
    
    ```php
    register_rest_route( 'myplugin/v1', '/cards', array(
        'methods'             => WP_REST_Server::READABLE,
        'permission_callback' => '__return_true',
        'callback'            => static function ( WP_REST_Request $request ) {
            $lang = function_exists( 'pll_current_language' ) ? pll_current_language() : false;
    
            if ( ! $lang && function_exists( 'pll_default_language' ) ) {
                $lang = pll_default_language();
            }
    
            return rest_ensure_response( array(
                'lang' => $lang ?: get_locale(),
            ) );
        },
    ) );
    ```
    
    For state-changing custom routes, still use normal REST permissions and nonces/auth. Language is not authorization.
    
    ## Languages endpoint
    
    Polylang core registers:
    
    - `GET /wp-json/pll/v1/languages`
    - `POST /wp-json/pll/v1/languages`
    - `GET|PUT|PATCH|DELETE /wp-json/pll/v1/languages/<term_id>`
    - `GET /wp-json/pll/v1/languages/<slug>`
    
    The response schema includes fields such as `term_id`, `name`, `slug`, `locale`, `w3c`, `is_rtl`, `flag_url`, `is_default`, `active`, `home_url`, `search_url`, `page_on_front`, `page_for_posts`, `fallbacks`, and `term_props`.
    
    Use this endpoint for language selector bootstrapping in headless clients. Do not hardcode language lists in JS.
    
    ## Filterable REST routes
    
    Core Polylang detects REST routes for translated `show_in_rest` post types and taxonomies, plus `wp/v2/search`. It exposes the filter list to editor scripts and can add query parameters to preload paths.
    
    The filter is:
    
    ```php
    add_filter( 'pll_filtered_rest_routes', static function ( array $routes ): array {
        $routes['myplugin_item'] = 'myplugin/v1/items';
        return $routes;
    } );
    ```
    
    Use this only for collection routes that accept a `lang` parameter and return language-filterable objects. Do not mark single-object routes ending with an ID as collection filters.
    
    ## Polylang Pro fields
    
    Polylang Pro 3.8 registers REST fields on translated REST-enabled post types and taxonomies:
    
    ```json
    {
      "lang": "en",
      "translations": {
        "en": 123,
        "fr": 456
      }
    }
    ```
    
    The `lang` field is a string enum of language slugs. The `translations` field is an object whose keys are slugs and values are IDs.
    
    Write pattern:
    
    ```http
    PUT /wp-json/wp/v2/book/456
    Content-Type: application/json
    X-WP-Nonce: ...
    
    {
      "lang": "fr",
      "translations": {
        "en": 123
      }
    }
    ```
    
    The Pro update callback checks language validity and translation capabilities. Invalid language returns `rest_invalid_language_code`; insufficient translation permission returns `rest_cannot_set_language`.
    
    ## REST type filters in 3.8
    
    Polylang Pro filters the REST-enabled object types it manages:
    
    ```php
    add_filter( 'pll_rest_api_post_types', static function ( array $post_types ): array {
        $post_types[] = 'book';
        return array_values( array_unique( $post_types ) );
    } );
    
    add_filter( 'pll_rest_api_taxonomies', static function ( array $taxonomies ): array {
        $taxonomies[] = 'genre';
        return array_values( array_unique( $taxonomies ) );
    } );
    ```
    
    In 3.8 the legacy format with options arrays is deprecated. Return a simple indexed array of post type or taxonomy names. The Pro source sanitizes legacy formats but calls `_deprecated_argument()`.
    
    ## Custom REST controllers
    
    Polylang's request helper can infer object type from `WP_REST_Posts_Controller` or `WP_REST_Terms_Controller`. For a custom controller that does not extend those, identify the object type:
    
    ```php
    add_filter(
        'pll_rest_request_object_type',
        static function ( $type, array $handler, WP_REST_Request $request ) {
            if ( str_starts_with( $request->get_route(), '/myplugin/v1/books' ) ) {
                return 'post';
            }
    
            return $type;
        },
        10,
        3
    );
    ```
    
    Accepted return values are `post`, `term`, or `null`.
    
    ## Pro translation endpoints
    
    Polylang Pro 3.8 adds:
    
    ```http
    POST /wp-json/pll/v1/translation
    ```
    
    Required body:
    
    ```json
    {
      "from_post": 123,
      "lang": "fr",
      "action": "duplicate"
    }
    ```
    
    It duplicates a translatable post to the target language through Pro's sync model. Permissions require `edit_posts`, `read_post` on the source, and the ability to translate into the requested language.
    
    Untranslated posts endpoint:
    
    ```http
    GET /wp-json/pll/v1/untranslated-posts?type=book&lang=en&untranslated_in=fr&search=foo
    ```
    
    Required query params are `type`, `lang`, and `untranslated_in`. This endpoint is useful for editor UIs and headless translation management.
    
    Its permission check allows view-context reads and only enforces the post type edit capability for `context=edit`. Do not proxy it as a private management endpoint without your own permission layer.
    
    ## WooCommerce REST
    
    Polylang for WooCommerce 2.2.2 requires Polylang Pro 3.8+ for its REST module.
    
    It removes generic Pro handling for:
    
    - `product`
    - `product_variation`
    - `shop_order`
    - `product_cat`
    - `product_tag`
    - `product_brand`
    - `product_attribute_term`
    
    Then it registers Woo-aware handlers. For products:
    
    - `lang` and `translations` are exposed on Woo REST product objects.
    - SKU and global unique ID checks are language-aware during REST writes.
    - Batch product create reads `lang` from each `create` item through an internal FIFO queue.
    
    Example:
    
    ```http
    POST /wp-json/wc/v3/products
    Content-Type: application/json
    Authorization: Basic ...
    
    {
      "name": "Blue shirt",
      "type": "simple",
      "lang": "en",
      "translations": {
        "fr": 456
      }
    }
    ```
    
    For orders, Polylang WC adds `lang` to Woo order REST object queries:
    
    ```http
    GET /wp-json/wc/v3/orders?lang=fr
    ```
    
    When HPOS is enabled, its HPOS query filter adds language JOIN/WHERE clauses for translated order types.
    
    ## Headless rules
    
    - Always pass `lang` explicitly in REST collection reads.
    - Treat empty `lang` differently from missing `lang`: Polylang WC restores explicit `lang => ''` in Woo queries to mean all languages.
    - Use language endpoint data for switchers and route generation.
    - For writes, send both `lang` and `translations` only when the user has translation capability.
    - Do not use `lang` to bypass object permissions. Keep normal REST permission checks.
    - Do not return cached REST HTML/JSON without varying by language, user, and auth state where applicable.
    
    ## Common mistakes
    
    - Expecting core Polylang alone to add `lang` and `translations` fields to posts. That is Pro behavior.
    - Returning legacy option arrays from `pll_rest_api_post_types` or `pll_rest_api_taxonomies` on 3.8+.
    - Filtering single-object routes by language as if they were collections.
    - Creating Woo products through REST without `lang`, then trying to repair language from SKU later.
    - Assuming Woo batch update/delete need language queue. Polylang WC's queue is for batch create items because those objects do not exist yet.
    
    ## Cross-references
    
    - Use `wp-rest-api` for generic route security, nonce, schema, and permission rules.
    - Use `polylang-object-translations` for PHP-side linking after imports.
    - Use `polylang-wc-compatibility` for product/order language data store behavior.
    
    ## Verification
    
    Local source checked against:
    
    - REST language definition: `wp-content/plugins/polylang/src/rest-request.php`
    - REST request helper and `pll_rest_request_object_type`: `src/modules/REST/Request.php`
    - Languages endpoint schema/routes: `src/modules/REST/V1/Languages.php`
    - Pro REST fields and filters: `polylang-pro/src/modules/rest/*`
    - Woo REST module and batch queue: `polylang-wc/src/modules/REST/*`
    
    ## References
    
    - Official documentation: <https://polylang.pro/doc/rest-api/>
    - Official documentation: <https://polylang.pro/doc/function-reference/>
    - Verified source paths:
      - `wp-content/plugins/polylang/src/rest-request.php`
      - `wp-content/plugins/polylang/src/modules/REST/Request.php`
      - `wp-content/plugins/polylang/src/modules/REST/API.php`
      - `wp-content/plugins/polylang/src/modules/REST/V1/Languages.php`
      - `wp-content/plugins/polylang/src/filter-rest-routes.php`
      - `wp-content/plugins/polylang-pro/src/modules/rest/rest-api.php`
      - `wp-content/plugins/polylang-pro/src/modules/rest/Translatable/Abstract_object.php`
      - `wp-content/plugins/polylang-pro/src/modules/rest/Translated/Abstract_Object.php`
      - `wp-content/plugins/polylang-pro/src/modules/rest/V1/Translation.php`
      - `wp-content/plugins/polylang-pro/src/modules/rest/V1/Untranslated_Posts.php`
      - `wp-content/plugins/polylang-wc/src/modules/REST/Module.php`
      - `wp-content/plugins/polylang-wc/src/modules/REST/Translated/Product.php`
      - `wp-content/plugins/polylang-wc/src/modules/REST/Translated/Batch.php`
      - `wp-content/plugins/polylang-wc/src/modules/REST/Filtered/Order.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related