Skip to content

Implement oneOf "by" arguments to replace id and idType pattern. #3416

Description

@jasonbahl

What problem does this address?

Parent Issue: #3050 - Introduce support for the @OneOf GraphQL Directive

Summary

Replace the current id/idType argument pattern with semantic oneOf by arguments across all singular node queries. This provides better type safety, improved developer experience, and establishes oneOf patterns for future WPGraphQL features.

Current vs. Proposed

Current Pattern

# Error-prone - relies on resolver validation
post(id: "hello-world", idType: SLUG) { title }
user(id: "john@example.com", idType: EMAIL) { name }
category(id: 5, idType: DATABASE_ID) { name }

# Invalid combinations possible at schema level
post(id: "hello-world", idType: DATABASE_ID) { title } # Runtime error

Proposed Pattern

# Type-safe - schema validates input
post(by: { slug: "hello-world" }) { title }
user(by: { email: "john@example.com" }) { name }  
category(by: { databaseId: 5 }) { name }

# Invalid combinations prevented at schema level
post(by: { slug: "hello-world", databaseId: 123 }) # Schema validation error

Scope

Affected Query Fields

  • Posts: post, page, and all custom post type singular queries
  • Users: user
  • Terms: category, tag, and all custom taxonomy term queries
  • Media: mediaItem
  • Comments: comment (if applicable)

Implementation Requirements

1. Create oneOf Identifier Input Types

"""Identify a post by one of several methods"""
input PostIdentifierInput @oneOf {
  """The globally unique ID"""
  id: ID
  """The database ID"""  
  databaseId: Int
  """The URI/path"""
  uri: String
  """The slug"""
  slug: String
}

"""Identify a user by one of several methods"""
input UserIdentifierInput @oneOf {
  """The globally unique ID"""
  id: ID
  """The database ID"""
  databaseId: Int
  """The email address"""
  email: String
  """The username/login"""
  username: String
  """The user slug/nicename"""
  slug: String
}

"""Identify a term by one of several methods"""
input TermIdentifierInput @oneOf {
  """The globally unique ID"""
  id: ID
  """The database ID"""
  databaseId: Int
  """The slug"""
  slug: String
  """The name""" 
  name: String
}

"""Identify a media item by one of several methods"""
input MediaItemIdentifierInput @oneOf {
  """The globally unique ID"""
  id: ID
  """The database ID"""
  databaseId: Int
  """The source URL"""
  sourceUrl: String
  """The slug"""
  slug: String
}

2. Add by Arguments to Query Fields

type RootQuery {
  # Existing (to be deprecated)
  post(
    id: ID! @deprecated(reason: "Use the 'by' argument instead")
    idType: PostIdType @deprecated(reason: "Use the 'by' argument instead")
  ): Post
  
  # New oneOf approach
  post(by: PostIdentifierInput!): Post
  
  # Apply same pattern to all affected fields
  user(by: UserIdentifierInput!): User
  category(by: TermIdentifierInput!): Category
  mediaItem(by: MediaItemIdentifierInput!): MediaItem
  # etc.
}

3. Validation and Error Handling

  • Schema-level validation: oneOf directive prevents multiple identifier types
  • Runtime validation: Throw error if both old and new patterns used together
  • Resolver logic: Handle all identifier types in resolvers
// Example validation in resolver
if (!empty($args['id']) && !empty($args['by'])) {
    throw new UserError(
        'Cannot use deprecated "id" argument with new "by" argument. Please use only the "by" argument.'
    );
}

Benefits

Developer Experience

  • Intuitive: Clear semantic meaning vs. generic id/idType
  • Type-safe: Schema prevents invalid identifier combinations
  • Discoverable: GraphQL introspection reveals available identifier types
  • Self-documenting: Field names indicate what type of identifier is expected

Technical Benefits

  • Better error messages: Schema validation provides specific feedback
  • Reduced support burden: Fewer "how do I query by X?" questions
  • Foundation for future: Establishes oneOf patterns for connection filtering
  • Extensible: Plugin developers can add custom identifier types

Implementation Plan

Phase 1: Core Infrastructure

  • Create identifier input types
  • Add oneOf directive support validation
  • Update resolver logic to handle by arguments

Phase 2: Schema Updates

  • Add by arguments to all affected query fields
  • Deprecate existing id/idType arguments
  • Add validation to prevent mixing old/new patterns

Phase 3: Testing & Documentation

  • Comprehensive test coverage for all identifier types
  • Update documentation with new examples
  • Migration guide for existing queries
  • Schema introspection tests

Phase 4: Extension Support

  • Guidelines for plugin developers adding custom identifier types
  • Examples for extending identifier inputs

Backward Compatibility

Deprecation Strategy

  • 12-18 month transition period with both patterns supported
  • Deprecation warnings in schema introspection
  • Clear migration guidance in documentation
  • Runtime errors only when patterns are mixed

Migration Support

  • Before/after examples for all common patterns
  • Automated migration suggestions where possible
  • Community education through blog posts and tutorials

Testing Requirements

Unit Tests

  • oneOf input type validation for all identifier types
  • Resolver logic for all identifier methods across all node types
  • Error handling for mixed old/new argument patterns
  • Validation that id and idType must be used together (if using deprecated pattern)
  • Deprecation warning functionality

Integration Tests

  • All identifier types work correctly for all 14+ affected query fields
  • Backward compatibility maintained during transition period
  • Error messages are helpful and guide users to new pattern
  • Performance impact assessment across all node types
  • Custom post types and taxonomies work with new pattern

Schema Tests

  • Introspection shows new by fields for all affected queries
  • Deprecated id/idType fields marked correctly
  • oneOf validation works as expected for all identifier input types
  • Schema documentation reflects new patterns

Success Metrics

  • Schema complexity reduction (fewer enum types needed)
  • Developer feedback positive on new patterns
  • Reduced support questions about node identification
  • Smooth migration path demonstrated
  • Foundation established for connection filtering oneOf types

Related Issues

Dependencies

  • WPGraphQL 2.0+ (oneOf directive support)
  • Updated graphql-php with oneOf support
  • Documentation updates

Metadata

Metadata

Assignees

No one assigned

    Labels

    compat: possible breakThere is a possibility that this might lead to breaking changes, but not confirmed yetcomponent: inputRelating to GraphQL Input Typescomponent: queryRelating to GraphQL Querieseffort: medLess than a weekimpact: highUnblocks new use cases, substantial improvement to existing feature, fixes a major bugscope: apiIssues related to access functions, actions, and filtersstatus: actionableReady for work to begintype: featureNew functionality being added

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    🆕 New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions