Claude Skill

je-query-builder-custom-type

Registers or audits a custom JetEngine Query Builder type with paired runtime and editor classes. Covers all six Base_Query abstract methods including set_filtered_prop, setup_query dynamic/macro merging, pagination, automatic item caching and explicit count caching, filtering, R

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-jet-engine_je-query-builder-custom-type-52f6020.zip · 5 KB
Part of lonsdale201/wp-agent-skills — 226 skills

Install

skills CLI npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/jet-engine/je-query-builder-custom-type
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

JetEngine Query Builder custom type

Build a saved-query type as two coordinated components: a runtime query and an admin editor. Keep the runtime authoritative; editor controls, REST inputs, filters, and MCP-created settings are all untrusted input to that runtime.

When to use this skill

  • Expose a custom table, service, or repository to Query Builder/Listings.
  • Add query-type-specific editor controls.
  • Diagnose abstract-class fatals after a JetEngine upgrade.
  • Fix dynamic arguments, filters, cache, count, or pagination behavior.
  • Make a custom type intentionally usable from saved-query REST or MCP tooling.

Architecture and registration

Use the same vendor-prefixed slug in both registrations.

add_action(
    'jet-engine/query-builder/queries/register',
    static function($factory): void {
        require_once __DIR__ . '/src/class-my-plugin-query.php';
        $factory::register_query('my-plugin-records', My_Plugin_Query::class);
    }
);

add_action(
    'jet-engine/query-builder/query-editor/register',
    static function($editor): void {
        require_once __DIR__ . '/src/class-my-plugin-query-editor.php';
        $editor->register_type(new My_Plugin_Query_Editor());
    }
);

The runtime base has six required methods in 3.8.14:

_get_items()
get_items_total_count()
get_items_page_count()
get_items_pages_count()
get_current_items_page()
set_filtered_prop($prop = '', $value = null)

Omitting set_filtered_prop() leaves the subclass abstract and causes a fatal when JetEngine instantiates it.

Runtime skeleton

use Jet_Engine\Query_Builder\Queries\Base_Query;

final class My_Plugin_Query extends Base_Query {
    private function args(): array {
        $this->setup_query();
        $args = $this->get_query_args();

        return array(
            'status'   => sanitize_key($args['status'] ?? 'active'),
            'page'     => max(1, absint($args['page'] ?? 1)),
            'per_page' => min(100, max(1, absint($args['per_page'] ?? 20))),
        );
    }

    public function _get_items() {
        return my_plugin_repository()->find($this->args());
    }

    public function get_items_total_count() {
        $cached = $this->get_cached_data('count');
        if (false !== $cached) {
            return (int) $cached;
        }

        $count = (int) my_plugin_repository()->count($this->args());
        $this->update_query_cache($count, 'count');
        return $count;
    }

    public function get_items_per_page() {
        return $this->args()['per_page'];
    }

    public function get_current_items_page() {
        return $this->args()['page'];
    }

    public function get_items_pages_count() {
        return max(1, (int) ceil(
            $this->get_items_total_count() / $this->get_items_per_page()
        ));
    }

    public function get_items_page_count() {
        return count($this->get_items());
    }

    public function set_filtered_prop($prop = '', $value = null) {
        if ('_page' === $prop) {
            $this->final_query['page'] = max(1, absint($value));
            return;
        }

        $this->merge_default_props($prop, $value);
    }
}

Base_Query::get_items() automatically reads and writes the item cache. Do not duplicate item caching in _get_items(). Counts and auxiliary requests need their own keys. Always test cache lookup with false !== $cached, because zero and an empty array are valid cached values.

Query setup and filtering

Call setup_query() or get_query_args() before reading final arguments. It:

  • merges saved and dynamic values;
  • resolves JetEngine macros;
  • merges _id-addressed nested groups;
  • explodes properties declared by get_args_to_explode();
  • adds _query_type and queried_object_id.

Do not read $this->query as the executable query, and do not call merge_dynamic_nested_args() on the entire final query. That helper accepts a single nested group with an args member; setup_query() invokes it correctly.

In set_filtered_prop(), validate each property and preserve restrictions. Intersect allowlists/IDs when a filter must narrow the base query. Blindly replacing a tenant, owner, status, or visibility restriction can expose data.

Pagination and cache invariants

  • Apply the same normalized filters to item and count queries.
  • Include page/offset, site, locale, user/tenant, permissions, and all dynamic inputs in the effective cache hash when they affect results.
  • Set a finite cache_expires for external or frequently changing data.
  • Invalidate domain caches after writes; JetEngine cannot infer external data changes.
  • Return objects with stable IDs and fields that Listings can consume.
  • Implement reset_query()/query_was_changed() if the class holds an inner query object or mutable state beyond final_query.

Editor, REST, and MCP boundaries

The editor subclass must at least implement get_id() and get_name(). Return a component name/template/file only if custom controls are required. JetEngine enqueues editor component files with an empty dependency array; ensure required globals are already provided by the Query Builder page, or enqueue a separate dependency-aware bundle.

Saved Query Builder queries can opt into a REST endpoint. The type must still sanitize every runtime argument and preserve access constraints; endpoint permission comes from the saved query's access settings, not the custom class.

JetEngine's MCP add-query tool lists registered custom type slugs, but it does not understand their arguments automatically. Without a converter, converted_args is empty and the saved type-specific settings may be empty. For intentional MCP support:

  1. provide static mcp_description() for schema guidance; and
  2. return a converter through jet-engine/query-builder/mcp/get-converter/my-plugin-records.

This MCP feature creates saved queries; it does not turn every saved query into an independently callable MCP tool.

Read runtime-editor-mcp.md when building the editor UI, filter semantics, REST exposure, or MCP conversion.

Verification

Test empty results, cached empty results, zero count, two pages, final partial page, out-of-range page, filter narrowing, attempted restriction widening, macro changes, two identical cached calls, mutation invalidation, editor save / reload, and REST permissions if enabled. For MCP support, create a query and inspect the persisted type-specific settings rather than only the returned ID.

References

  • Official documentation: https://crocoblock.com/knowledge-base/plugins/jetengine/
  • Crocoblock developer documentation: https://github.com/Crocoblock/developer-documentation/tree/main/01-jet-engine
  • Verified source paths:
    • wp-content/plugins/jet-engine/includes/components/query-builder/queries/base.php
    • wp-content/plugins/jet-engine/includes/components/query-builder/query-factory.php
    • wp-content/plugins/jet-engine/includes/components/query-builder/query-editor.php
    • wp-content/plugins/jet-engine/includes/components/query-builder/editor/base.php
    • wp-content/plugins/jet-engine/includes/components/query-builder/rest-api/query-endpoint.php
    • wp-content/plugins/jet-engine/includes/components/query-builder/mcp/controller.php
    • wp-content/plugins/jet-engine/includes/components/query-builder/mcp/tool-add-query.php
Files (wp-agent-skills)
  • agents
    • openai.yaml 253 B
      interface:
        display_name: "JetEngine Query Builder Type"
        short_description: "Build custom JetEngine query types"
        default_prompt: "Use $je-query-builder-custom-type to implement or audit this custom JetEngine Query Builder runtime and editor type."
      
  • references
    • runtime-editor-mcp.md 4.6 KB
      # Query Builder runtime, editor, REST, and MCP reference
      
      Load this reference when implementing the editor component, nested controls,
      filter merging, REST exposure, or MCP query creation.
      
      ## Editor base contract
      
      ```php
      use Jet_Engine\Query_Builder\Query_Editor\Base_Query;
      
      final class My_Plugin_Query_Editor extends Base_Query {
          public function get_id() {
              return 'my-plugin-records';
          }
      
          public function get_name() {
              return __('My Plugin records', 'my-plugin');
          }
      
          public function editor_component_name() {
              return 'my-plugin-records-query';
          }
      
          public function editor_component_file() {
              return plugins_url('assets/query-editor.js', MY_PLUGIN_FILE);
          }
      
          public function editor_component_template() {
              ob_start();
              require __DIR__ . '/../templates/query-editor.php';
              return ob_get_clean();
          }
      
          public function editor_component_data() {
              return array('statuses' => my_plugin_allowed_statuses());
          }
      }
      ```
      
      Keep `get_id()` identical to the runtime slug. Escape translated labels for the
      actual Vue/HTML context. Do not interpolate untrusted values into a template or
      inline script.
      
      JetEngine localizes component data as an object named from the handle
      `jet-query-component-{type}`, with hyphens converted to underscores.
      
      ## Dynamic and nested settings
      
      Give repeatable/nested rows stable `_id` values. JetEngine saves dynamic
      overrides by row ID and `setup_query()` merges them. Renumbering or regenerating
      IDs on each editor load disconnects saved dynamic values.
      
      For a nested group, the shape is conceptually:
      
      ```php
      array(
          'is_group' => true,
          '_id'      => 'stable-group-id',
          'relation' => 'AND',
          'args'     => array(
              array(
                  '_id'     => 'stable-row-id',
                  'field'   => 'status',
                  'operator'=> '=',
                  'value'   => 'active',
              ),
          ),
      )
      ```
      
      The runtime must allowlist fields, operators, relation values, order fields,
      and cast values. Editor restrictions are usability, not server validation.
      
      ## Filter merging
      
      `set_filtered_prop()` is a security boundary when a frontend filter changes a
      saved query. Use one of these policies per property:
      
      - replace: safe presentation property such as page;
      - merge: independent additive constraints;
      - intersect: IDs/statuses/tenants where filters may only narrow;
      - reject: internal credentials, table names, raw SQL, ownership scope.
      
      If an intersection becomes empty, use an explicit no-results sentinel rather
      than removing the restriction.
      
      ## REST saved-query endpoint
      
      `Base_Query::maybe_register_rest_api_endpoint()` registers only when the saved
      query enables it and provides a namespace and path. `Query_Endpoint` applies
      the saved access mode/capability/roles. Audit:
      
      - public versus role/capability settings;
      - schema arguments and runtime sanitization;
      - object-level visibility of returned records;
      - pagination bounds and denial-of-service costs;
      - cache variation for authenticated users;
      - output fields and sensitive repository columns.
      
      ## MCP conversion
      
      The MCP add-query tool obtains a converter through:
      
      ```php
      add_filter(
          'jet-engine/query-builder/mcp/get-converter/my-plugin-records',
          static function($converter) {
              if ($converter) {
                  return $converter;
              }
              return new My_Plugin_Query_MCP_Converter();
          }
      );
      ```
      
      Match the converter interface used in
      `includes/components/query-builder/mcp/converters/converter-interface.php`.
      Allowlist input keys, cast values, cap page sizes, reject raw SQL/identifiers,
      and return the exact settings shape the editor/runtime expects.
      
      Without this filter, the custom slug can appear in the MCP schema while the
      tool persists an empty custom argument array. Treat that as unsupported, not
      automatic compatibility.
      
      ## Personalized cache hashes
      
      If results depend on state not already present in `final_query`, extend the
      hash explicitly:
      
      ```php
      public function get_query_hash_args() {
          $args = parent::get_query_hash_args();
          $args['my_plugin_user_id'] = get_current_user_id();
          $args['my_plugin_blog_id'] = get_current_blog_id();
          return $args;
      }
      ```
      
      Add only deterministic scalar/array dimensions. Do not put credentials or
      non-serializable service objects into the hash.
      
      ## Regression checklist
      
      ```text
      runtime class is concrete
      runtime/editor IDs match
      saved -> reload preserves every setting
      dynamic nested values merge by stable _id
      item cache distinguishes effective queries
      count cache accepts 0
      filters cannot widen protected scope
      empty intersection yields no results
      REST public access is intentional
      MCP-created settings equal editor-created settings
      ```
      
  • SKILL.md 8.1 KB
    ---
    name: je-query-builder-custom-type
    description: >-
      Registers or audits a custom JetEngine Query Builder type with paired runtime
      and editor classes. Covers all six Base_Query abstract methods including
      set_filtered_prop, setup_query dynamic/macro merging, pagination, automatic
      item caching and explicit count caching, filtering, REST endpoint exposure,
      editor assets, and optional MCP argument conversion. Use for custom tables,
      HPOS-like repositories, external APIs, or bugs involving stale cache, broken
      filters, missing editor controls, empty MCP-created queries, or pagination.
    metadata:
      wp-skills-author: "Soczó Kristóf"
      wp-skills-contact: "mailto:lonsdale201@hotmail.com"
      wp-skills-plugin: "jet-engine"
      wp-skills-plugin-version-tested: "3.8.14"
      wp-skills-wp-version-tested: "7.0.4"
      wp-skills-php-min: "7.4"
      wp-skills-last-updated: "2026-08-17"
    ---
    
    # JetEngine Query Builder custom type
    
    Build a saved-query type as two coordinated components: a runtime query and an
    admin editor. Keep the runtime authoritative; editor controls, REST inputs,
    filters, and MCP-created settings are all untrusted input to that runtime.
    
    ## When to use this skill
    
    - Expose a custom table, service, or repository to Query Builder/Listings.
    - Add query-type-specific editor controls.
    - Diagnose abstract-class fatals after a JetEngine upgrade.
    - Fix dynamic arguments, filters, cache, count, or pagination behavior.
    - Make a custom type intentionally usable from saved-query REST or MCP tooling.
    
    ## Architecture and registration
    
    Use the same vendor-prefixed slug in both registrations.
    
    ```php
    add_action(
        'jet-engine/query-builder/queries/register',
        static function($factory): void {
            require_once __DIR__ . '/src/class-my-plugin-query.php';
            $factory::register_query('my-plugin-records', My_Plugin_Query::class);
        }
    );
    
    add_action(
        'jet-engine/query-builder/query-editor/register',
        static function($editor): void {
            require_once __DIR__ . '/src/class-my-plugin-query-editor.php';
            $editor->register_type(new My_Plugin_Query_Editor());
        }
    );
    ```
    
    The runtime base has six required methods in 3.8.14:
    
    ```text
    _get_items()
    get_items_total_count()
    get_items_page_count()
    get_items_pages_count()
    get_current_items_page()
    set_filtered_prop($prop = '', $value = null)
    ```
    
    Omitting `set_filtered_prop()` leaves the subclass abstract and causes a fatal
    when JetEngine instantiates it.
    
    ## Runtime skeleton
    
    ```php
    use Jet_Engine\Query_Builder\Queries\Base_Query;
    
    final class My_Plugin_Query extends Base_Query {
        private function args(): array {
            $this->setup_query();
            $args = $this->get_query_args();
    
            return array(
                'status'   => sanitize_key($args['status'] ?? 'active'),
                'page'     => max(1, absint($args['page'] ?? 1)),
                'per_page' => min(100, max(1, absint($args['per_page'] ?? 20))),
            );
        }
    
        public function _get_items() {
            return my_plugin_repository()->find($this->args());
        }
    
        public function get_items_total_count() {
            $cached = $this->get_cached_data('count');
            if (false !== $cached) {
                return (int) $cached;
            }
    
            $count = (int) my_plugin_repository()->count($this->args());
            $this->update_query_cache($count, 'count');
            return $count;
        }
    
        public function get_items_per_page() {
            return $this->args()['per_page'];
        }
    
        public function get_current_items_page() {
            return $this->args()['page'];
        }
    
        public function get_items_pages_count() {
            return max(1, (int) ceil(
                $this->get_items_total_count() / $this->get_items_per_page()
            ));
        }
    
        public function get_items_page_count() {
            return count($this->get_items());
        }
    
        public function set_filtered_prop($prop = '', $value = null) {
            if ('_page' === $prop) {
                $this->final_query['page'] = max(1, absint($value));
                return;
            }
    
            $this->merge_default_props($prop, $value);
        }
    }
    ```
    
    `Base_Query::get_items()` automatically reads and writes the item cache. Do not
    duplicate item caching in `_get_items()`. Counts and auxiliary requests need
    their own keys. Always test cache lookup with `false !== $cached`, because zero
    and an empty array are valid cached values.
    
    ## Query setup and filtering
    
    Call `setup_query()` or `get_query_args()` before reading final arguments. It:
    
    - merges saved and dynamic values;
    - resolves JetEngine macros;
    - merges `_id`-addressed nested groups;
    - explodes properties declared by `get_args_to_explode()`;
    - adds `_query_type` and `queried_object_id`.
    
    Do not read `$this->query` as the executable query, and do not call
    `merge_dynamic_nested_args()` on the entire final query. That helper accepts a
    single nested group with an `args` member; `setup_query()` invokes it correctly.
    
    In `set_filtered_prop()`, validate each property and preserve restrictions.
    Intersect allowlists/IDs when a filter must narrow the base query. Blindly
    replacing a tenant, owner, status, or visibility restriction can expose data.
    
    ## Pagination and cache invariants
    
    - Apply the same normalized filters to item and count queries.
    - Include page/offset, site, locale, user/tenant, permissions, and all dynamic
      inputs in the effective cache hash when they affect results.
    - Set a finite `cache_expires` for external or frequently changing data.
    - Invalidate domain caches after writes; JetEngine cannot infer external data
      changes.
    - Return objects with stable IDs and fields that Listings can consume.
    - Implement `reset_query()`/`query_was_changed()` if the class holds an inner
      query object or mutable state beyond `final_query`.
    
    ## Editor, REST, and MCP boundaries
    
    The editor subclass must at least implement `get_id()` and `get_name()`. Return
    a component name/template/file only if custom controls are required. JetEngine
    enqueues editor component files with an empty dependency array; ensure required
    globals are already provided by the Query Builder page, or enqueue a separate
    dependency-aware bundle.
    
    Saved Query Builder queries can opt into a REST endpoint. The type must still
    sanitize every runtime argument and preserve access constraints; endpoint
    permission comes from the saved query's access settings, not the custom class.
    
    JetEngine's MCP add-query tool lists registered custom type slugs, but it does
    not understand their arguments automatically. Without a converter,
    `converted_args` is empty and the saved type-specific settings may be empty.
    For intentional MCP support:
    
    1. provide static `mcp_description()` for schema guidance; and
    2. return a converter through
       `jet-engine/query-builder/mcp/get-converter/my-plugin-records`.
    
    This MCP feature creates saved queries; it does not turn every saved query into
    an independently callable MCP tool.
    
    Read [runtime-editor-mcp.md](references/runtime-editor-mcp.md) when building the
    editor UI, filter semantics, REST exposure, or MCP conversion.
    
    ## Verification
    
    Test empty results, cached empty results, zero count, two pages, final partial
    page, out-of-range page, filter narrowing, attempted restriction widening,
    macro changes, two identical cached calls, mutation invalidation, editor save /
    reload, and REST permissions if enabled. For MCP support, create a query and
    inspect the persisted type-specific settings rather than only the returned ID.
    
    ## References
    
    - Official documentation: <https://crocoblock.com/knowledge-base/plugins/jetengine/>
    - Crocoblock developer documentation: <https://github.com/Crocoblock/developer-documentation/tree/main/01-jet-engine>
    - Verified source paths:
      - `wp-content/plugins/jet-engine/includes/components/query-builder/queries/base.php`
      - `wp-content/plugins/jet-engine/includes/components/query-builder/query-factory.php`
      - `wp-content/plugins/jet-engine/includes/components/query-builder/query-editor.php`
      - `wp-content/plugins/jet-engine/includes/components/query-builder/editor/base.php`
      - `wp-content/plugins/jet-engine/includes/components/query-builder/rest-api/query-endpoint.php`
      - `wp-content/plugins/jet-engine/includes/components/query-builder/mcp/controller.php`
      - `wp-content/plugins/jet-engine/includes/components/query-builder/mcp/tool-add-query.php`
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related