agentsclimarketplace

Br resource cpt

Skill Lonsdale201/wp-agent-skills/better-route/br-resource-cpt

Build better-route 1.1 CRUD endpoints over a WordPress custom post type with Resource::make, restNamespace, sourceCpt, allow, fields, filters, sort, filterSchema, writeSchema, policy, fieldPolicy, cptVisibleStatuses, cptVisibilityPolicy, pagination, deleteMode, uniformEnvelope, or a custom CPT repository. Use when exposing CPT records safely, reviewing visibility and pagination, or generating Resource OpenAPI contracts.From its SKILL.md

Install
npx -y skills add Lonsdale201/wp-agent-skills --skill br-resource-cpt

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

6.5 KB, ~1.4k tokens by cl100k_base, as published. Nobody here has run it

better-route: CPT Resource CRUD

Register the Resource itself during rest_api_init; it creates and registers its own internal Router from restNamespace.

use BetterRoute\Resource\Resource;
use BetterRoute\Resource\ResourcePolicy;

add_action('rest_api_init', static function (): void {
    Resource::make('books')
        ->restNamespace('myapp/v1')
        ->sourceCpt('book')
        ->allow(['list', 'get', 'create', 'update', 'delete'])
        ->fields(['id', 'title', 'slug', 'content', 'status', 'author'])
        ->filters(['status', 'author'])
        ->filterSchema([
            'status' => ['type' => 'enum', 'values' => ['publish', 'draft']],
            'author' => 'int',
        ])
        ->sort(['date', 'title', 'id'])
        ->policy(ResourcePolicy::publicReadPrivateWrite('edit_posts'))
        ->writeSchema([
            'title' => ['type' => 'string', 'required' => true, 'sanitize' => 'text'],
            'status' => ['type' => 'enum', 'values' => ['draft', 'publish']],
        ])
        ->deleteMode('trash')
        ->register();
});

Do not pass a Router to Resource::register(). Its optional argument is a DispatcherInterface, intended mainly for tests/custom dispatch.

Actions

Omitting allow() registers full CRUD. In 1.1:

  • allow(['list', 'get']) creates a read-only Resource;
  • allow([]) deliberately registers no routes;
  • unsupported names throw InvalidArgumentException;
  • update covers both PUT and PATCH.

Never configure both sourceCpt() and sourceTable(); the second call now throws.

CPT visibility

Default visible statuses are ['publish']. Configure the source-verified method:

->cptVisibleStatuses(['publish', 'draft'])

There is no allowedStatuses() method.

1.1 fails closed on reads:

  • default list/get permission allows an unset policy only when the post type is publicly viewable;
  • every item must have a visible status;
  • the post type/item must be publicly queryable;
  • password-protected items require can_read === true;
  • the Resource always asks the repository for id, status, password_protected, publicly_queryable, and can_read, even if the client did not request them;
  • missing security fields from a custom repository deny rather than expose the item.

Prefer an explicit Resource policy even though public CPT reads have a safe default.

For additional item logic:

->cptVisibilityPolicy(static function (array $item, string $action): bool {
    return ($item['tenant_id'] ?? null) === current_tenant_id();
})

The callback receives the projected repository item plus action (list or get), not just a status and not the WP request.

An arbitrary PHP callback cannot be pushed into WP_Query. To keep total, pages, and page contents truthful, 1.1 scans every matched repository page, applies the callback, then slices the visible set. This can be expensive. On large datasets, implement visibility in a custom query-level repository/filter so the database produces the correct total.

Query contract

List queries use:

  • fields=a,b,c
  • sort=field or sort=-field
  • page and per_page
  • explicitly listed filters.

Configure sort field names without a - prefix:

->sort(['date', 'title', 'id'])
// Client may request ?sort=-date

An empty sort configuration uses the defaults date and id; it is not an open/no-sort state. The repository adds ID as a stable tie-breaker.

Strict unknown-parameter checks allow WordPress globals _locale, _fields, _embed, _envelope, and _jsonp. This keeps wp.apiFetch's _locale=user compatible while rejecting other unknown input.

Configure pagination in any fluent order, but the final state must satisfy:

  • defaultPerPage >= 1;
  • maxPerPage >= 1;
  • defaultPerPage <= maxPerPage;
  • maxOffset >= 0.

Exceeding maxPerPage or maxOffset returns 400 validation_failed; values are not silently clamped.

Writes and policies

Use flat enum rules:

['type' => 'enum', 'values' => ['draft', 'publish']]

Use br-write-schema for validation and br-resource-policy for action/field authorization. In 1.1 a denied fieldPolicy does not silently discard the field: it returns a validation error or 403 forbidden according to the rule type.

The default WordPressCptRepository also checks mapped CPT capabilities for create/update/delete and publishing/author changes. Resource route permission does not replace object-level WordPress capability checks.

Valid delete modes are force and trash.

Response and OpenAPI

Lists return {data, meta}. Create/update return {data}. A single get returns the raw item unless uniformEnvelope(true) is set; then it returns {data}.

After register(), call contracts() to export generated contracts. Provide <Resource>, <Resource>Input, <Resource>Response, and <Resource>ListResponse schemas in strict OpenAPI mode as applicable. Explicitly public Resource actions emit security: [].

Review checklist

  • Set restNamespace and exactly one source.
  • Choose allow() deliberately; distinguish omitted from [].
  • Declare an explicit policy for the intended audience.
  • Keep visibility security fields available in custom repositories.
  • Avoid per-item visibility callbacks for large result sets.
  • Use flat enum values and field names without - in sort().
  • Test private status, non-public CPT, password protection, denied field write, pagination totals, and _locale=user.

Related skills

  • Use br-resource-policy for action and field authorization.
  • Use br-write-schema for payload validation.
  • Use br-openapi for generated schemas/contracts.

References

  • Verified source paths:
    • src/Resource/Resource.php
    • src/Resource/Cpt/WordPressCptRepository.php
    • src/Resource/Cpt/CptListQueryParser.php
    • src/Resource/ResourcePolicy.php

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.