From harness-claude
Designs expressive GraphQL schemas with non-null defaults, interfaces, unions, action-based mutations, UserError payloads, and Relay pagination. Use for new APIs, extensions, or evolution without breaking clients.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Design expressive, evolvable GraphQL schemas with clear type hierarchies and strong nullability contracts
Guides GraphQL schema design with best practices for types, nullability, pagination, error patterns, and security. Use when designing new schemas, reviewing existing ones, or deciding on type structures.
Designs GraphQL schemas with type system, SDL patterns, field design, pagination, directives, and versioning for scalable APIs.
Designs and reviews GraphQL schemas, resolvers, mutations, pagination, and data-loading patterns for building or refactoring GraphQL APIs, fixing resolvers, or improving performance and safety.
Share bugs, ideas, or general feedback.
Design expressive, evolvable GraphQL schemas with clear type hierarchies and strong nullability contracts
Start with the domain, not the UI. Model your schema around business entities (Order, Product, User), not around specific screens or components. A well-modeled domain schema serves multiple clients without per-client hacks.
Use non-null by default. Mark fields as String! (non-null) unless the field genuinely can be absent. Non-null fields simplify client code by eliminating null checks. Reserve nullable fields for truly optional data (e.g., middleName: String).
Prefer specific types over generic ones. Use custom scalars (DateTime, URL, EmailAddress) instead of plain String for fields with validation semantics. Use enums for closed sets of values.
scalar DateTime
scalar URL
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
type Order {
id: ID!
status: OrderStatus!
createdAt: DateTime!
trackingUrl: URL
}
interface Node {
id: ID!
}
interface Timestamped {
createdAt: DateTime!
updatedAt: DateTime!
}
type User implements Node & Timestamped {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
name: String!
}
union SearchResult = User | Product | Order
cancelOrder, approveRefund, inviteTeamMember — not updateOrder(status: CANCELLED). Each mutation should have a dedicated input type and a dedicated payload type.input CancelOrderInput {
orderId: ID!
reason: String!
}
type CancelOrderPayload {
order: Order!
refundAmount: Money
errors: [UserError!]!
}
type Mutation {
cancelOrder(input: CancelOrderInput!): CancelOrderPayload!
}
UserError type in mutation payloads. This separates expected domain errors (validation failures, business rule violations) from unexpected system errors (which use GraphQL's top-level errors array).type UserError {
field: [String!]
message: String!
code: ErrorCode!
}
Use the Relay connection spec for paginated lists. Even if you do not use Relay on the client, the Connection/Edge/PageInfo pattern is well-understood, cursor-based, and forward-compatible.
Version through evolution, not URL paths. Add new fields freely. Deprecate old fields with @deprecated(reason: "Use newField instead"). Never remove fields without a deprecation period and client migration.
Keep the schema file as the source of truth. Whether you use schema-first or code-first, ensure there is one canonical .graphql file (or set of files) that documents every type. Generate code from the schema, not the other way around.
Document with descriptions. Add descriptions above types and fields — they appear in GraphiQL/Apollo Studio and serve as living API docs.
"""
A customer order containing one or more line items.
"""
type Order {
"""
Unique identifier for the order.
"""
id: ID!
}
Naming conventions: Types are PascalCase, fields are camelCase, enums are SCREAMING_SNAKE_CASE. Input types end with Input, payload types end with Payload.
Nullability trade-offs: Non-null fields are safer for clients but less forgiving for servers — if a non-null resolver throws, the error bubbles up to the nearest nullable parent, potentially nullifying an entire object. Place nullable "firewalls" at strategic points (e.g., nullable list items) to limit blast radius.
Schema stitching vs. federation: For monolithic APIs, a single schema file works. For microservices, prefer Apollo Federation where each service owns its slice of the graph and extends shared types with @key.
Anti-patterns to avoid:
update mutations with a giant optional input type — they are unvalidatable and untraceableJSON scalar as a catch-all — it defeats the purpose of a typed schemahttps://graphql.org/learn/schema/