agentsclimarketplace

Graphql

Skill api-platform/skillset/skills/graphql

Exposes API Platform resources over GraphQL — enabling GraphQL, Query/QueryCollection/Mutation/DeleteMutation operations, security expressions, custom resolvers, Relay cursor pagination, and nested relations. Use when the user mentions GraphQL, a GraphQL schema, queries/mutations, Relay connections, GraphQL playground, resolvers, or asks to expose existing REST resources via GraphQL.From its SKILL.md

Install
npx -y skills add api-platform/skillset --skill graphql

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

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

SKILL.md

7.2 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it

GraphQL

Default to REST. GraphQL trades away things API Platform gives you for free over REST: HTTP cache semantics (ETag, Cache-Control, invalidation), one URL per resource, simple CDN/proxy caching, and predictable per-operation cost. A single GraphQL query can fan out into arbitrarily deep/expensive resolution. Reach for GraphQL when it is a hard client requirement (e.g. a Relay/Apollo frontend, or clients that genuinely need to select fields and avoid round-trips) — not as a default. The same resource class can serve both; you don't have to choose globally.

Enabling GraphQL

Install api-platform/graphql (composer require api-platform/graphql), then it's on. A /graphql endpoint and the GraphiQL playground (/graphql/graphiql) appear. Disable globally or per resource as needed:

# config/packages/api_platform.yaml
api_platform:
    graphql:
        enabled: true
        graphiql:
            enabled: true

Declaring GraphQL operations

GraphQL operations live in graphQlOperations and are separate classes from the REST ones, under ApiPlatform\Metadata\GraphQl\. A resource with no graphQlOperations still gets a default set (item query, collection query, create / update / delete mutations) once GraphQL is enabled.

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GraphQl\DeleteMutation;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;

#[ApiResource(graphQlOperations: [
    new Query(),
    new QueryCollection(),
    new Mutation(name: 'create'),
    new Mutation(name: 'update'),
    new DeleteMutation(name: 'delete'),
])]
class Book {}

Mutation and DeleteMutation require a name — it becomes the GraphQL field name (createBook, updateBook, deleteBook). Query and QueryCollection only need a name when you declare more than one of the same kind (e.g. a custom query alongside the default).

Security on GraphQL operations

Same ExpressionLanguage as REST (see operations), set per GraphQl operation:

new Query(security: "is_granted('ROLE_USER')")
new Mutation(name: 'update', security: "object.getOwner() == user")
new Mutation(name: 'update', securityPostDenormalize: "object.getOwner() == user")

Because one GraphQL query can traverse relations, securing only the top-level operation is not enough — guard the related resources' operations too, or a nested field becomes an unguarded read path.

Custom resolvers

Use a resolver when a query/mutation needs logic beyond fetch-by-id. Resolvers are services implementing one of:

  • QueryItemResolverInterface__invoke(?object $item, array $context): object
  • QueryCollectionResolverInterface__invoke(iterable $collection, array $context): iterable
  • MutationResolverInterface__invoke(?object $item, array $context): ?object

Query arguments arrive in $context['args'].

use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;

final class BookResolver implements QueryItemResolverInterface
{
    public function __invoke(?object $item, array $context): object
    {
        // $item is the fetched Book (or null if read: false); enrich or replace it
        return $item;
    }
}

With Symfony autoconfiguration the resolver is wired automatically. Without it, tag the service api_platform.graphql.query_resolver (or ..._mutation_resolver). Then reference it by class name on the operation, and set read: false when the resolver should fetch the data itself rather than receiving a hydrated item:

new Query(name: 'recommended', resolver: BookResolver::class, read: false)
new Query(
    name: 'search',
    resolver: BookResolver::class,
    args: [
        'query' => ['type' => 'String!', 'description' => 'Full-text search'],
        'limit' => ['type' => 'Int'],
    ],
)

args overrides the auto-generated argument set — define it when the query takes parameters that aren't resource fields.

Relations and the N+1 trap

GraphQL embeds related resources by selecting nested fields:

{
  book(id: "/books/1") {
    title
    author { name }
  }
}

Relations resolve through the same providers as REST. A deeply nested query can trigger many small queries (the classic N+1). API Platform mitigates common cases, but verify against real queries and add Doctrine joins / a custom collection provider where a hot path fans out. This open-ended cost is the main reason REST is the safer default for cache-sensitive APIs.

Pagination: Relay cursor connections

Collection queries return Relay-style cursor connections by default (edges { node { ... } cursor }, pageInfo, totalCount), driven by first/ after/last/before arguments:

{
  books(first: 10, after: "endCursor") {
    totalCount
    edges { node { title } cursor }
    pageInfo { endCursor hasNextPage }
  }
}

To use simple page-based pagination instead, set paginationType: 'page' on the resource or the QueryCollection:

use ApiPlatform\Metadata\GraphQl\QueryCollection;

#[ApiResource(graphQlOperations: [
    new QueryCollection(paginationType: 'page'),
])]
class Book {}

All the pagination* controls from pagination (items per page, max, partial) apply to GraphQL collections too.

Real-time subscriptions

When a resource has mercure: true, GraphQL subscription operations push updates through the Mercure hub — see mercure.

Laravel

GraphQL is supported on Laravel. The operation classes (Query, QueryCollection, Mutation, DeleteMutation), security expressions, Relay connections, paginationType, custom resolvers and the N+1 caveats are all the same. Differences:

  • Install composer require api-platform/graphql, then enable it in config/api-platform.php under graphql ('enabled' => true) — not YAML. Depth/ complexity limits and graphiql live in the same config block.
  • Resolvers implement the same QueryItemResolverInterface / QueryCollectionResolverInterface / MutationResolverInterface and are referenced by class-string on the operation; Laravel's container resolves them — there are no api_platform.graphql.*_resolver tags to apply.
  • Real-time subscription operations depend on Mercure, which has no Laravel integration (see mercure), so GraphQL subscriptions are effectively Symfony-only.

Checklist

  • GraphQL chosen for a real client requirement, not as a REST default
  • Every Mutation/DeleteMutation has a name
  • security set on nested resources, not just the entry-point operation
  • Custom resolvers tagged (or autoconfigured) and referenced by class name
  • read: false set when the resolver fetches its own data
  • Deep/nested queries checked for N+1; joins added on hot paths
  • paginationType: 'page' set only if the client doesn't want Relay connections

What ships with it

Read from the repository

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

Gives 0 of the 12 instructions most apis services skills give in ~1.6k tokens

Counted across 448 of the 471 authors here whose files we hold, read 2026-09-06

  • Use HTTP status codes semanticallyin 25 of 448, across 11 files
  • Return 201 with a Location header on createin 24 of 448, across 9 files
  • Name resources plural, lowercase, kebab-casein 23 of 448, across 9 files
  • Configure rate limiting with limit headersin 22 of 448, across 8 files
  • Paginate list endpoints with cursor or offsetin 21 of 448, across 10 files
  • Version APIs in the URL pathin 21 of 448, across 11 files
  • Validate request input with a schemain 21 of 448, across 7 files
  • Add pagination to all list endpointsin 18 of 448, across 15 files
  • Match HTTP method to the operationin 12 of 448, across 6 files
  • Return 400 or 422 with field-level detailsin 12 of 448, across 2 files
  • Check ownership before returning resourcesin 12 of 448, across 2 files
  • Limit query depth and complexityin 12 of 448, across 7 files

Said here and by no other author read

  • Default to REST; use GraphQL only when a client requires it
  • Install api-platform/graphql with composer
  • Give every Mutation and DeleteMutation a name
  • Secure nested resource operations, not just the top-level operation
  • Use custom resolvers for logic beyond fetch-by-id
  • Reference resolvers by class name on the operation

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

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.