-
Notifications
You must be signed in to change notification settings - Fork 466
Description
Experiment Proposal: Email Address Scalar
📖 See also: PR #3423 - Original implementation with additional context
Experiment Title
Email Address Scalar
Executive Summary
This experiment introduces an EmailAddress scalar type to WPGraphQL, serving as a foundational piece for establishing WPGraphQL's competitive advantage through custom scalar types. While the immediate benefit is validated email fields, the strategic value is far greater:
Why This Matters:
- Differentiation: Custom scalars provide semantic APIs that most REST APIs don't typically support—this is a unique competitive advantage
- Reduced Tech Debt: Validation logic lives in the schema, not duplicated across every client and extension
- Enhanced Tooling: IDEs, code generators, and API explorers can provide type-specific features
- Foundation: Establishes patterns for future scalars (Date, URL, JSON, etc.) that make WPGraphQL the clear choice for type-safe WordPress APIs
Strategic Positioning:
By building a library of custom scalars, WPGraphQL becomes more than just "GraphQL for WordPress"—it becomes the developer-friendly, type-safe API that provides capabilities most REST APIs don't typically support. This EmailAddress scalar is the first step in demonstrating that value proposition to the WordPress developer community.
Problem Statement
The Problem:
WPGraphQL lacks a validated scalar type for email addresses. Extension authors and developers building custom types must use generic String types for email fields, which provides no validation, no semantic meaning, and no type safety at the GraphQL layer.
Email addresses are structured data conforming to international standards (RFC 5322, HTML Email Specification), but without a dedicated scalar type, the GraphQL schema can't communicate this constraint to tooling, clients, or developers. This creates a validation gap: WordPress validates emails internally using is_email(), but this validation is invisible to the GraphQL API layer.
Strategic Context: The Value of Custom Scalars
This experiment represents more than just adding an email scalar—it's about establishing WPGraphQL's competitive advantage through custom scalar types that provide semantic meaning and enhanced developer experiences that most REST APIs (including the WP REST API) don't typically provide.
As detailed in the WPGraphQL vs WP REST API comparison, GraphQL's strongly typed schema and introspection are core differentiators. However, even GraphQL's built-in types (String, Int, Boolean) are generic—they don't convey semantic meaning. Custom scalars bridge this gap by adding semantic types that communicate data intent.
Unlike REST APIs that return generic JSON with no schema validation (or with optional schemas that plugins often don't adhere to), GraphQL's enforced type system combined with custom scalars enables:
- Self-documenting APIs that communicate intent through types—the schema itself tells you "this field expects an email"
- Enhanced tooling that understands data semantics—GraphiQL, code generators, and IDEs can provide type-specific features
- Reduced technical debt by eliminating repetitive validation code—validation lives in the schema, not duplicated across clients
- Better developer experiences through IDE support, code generation, and automatic validation
This EmailAddress scalar serves as a foundational piece for establishing WPGraphQL's ecosystem of custom scalars (dates, URLs, JSON, etc.), demonstrating why developers should choose GraphQL over REST for WordPress applications. Just as GraphQL's standardized specification enables universal tooling, custom scalars enable semantic tooling that most REST APIs don't typically provide.
Who This Affects:
- Extension Authors: Building WPGraphQL extensions (WooCommerce, ACF, etc.) that need email fields in custom types
- Plugin Developers: Creating WordPress plugins that extend the GraphQL schema with email-related functionality
- API Consumers: Applications querying or mutating email data through WPGraphQL
- Frontend Developers: Building interfaces that need to validate email input before submission
Where This Manifests:
- Registering custom GraphQL types with email fields
- Creating mutations that accept email input
- Building extensions that need type-safe email handling
- Generating TypeScript/Flow types from the schema
- Providing email-specific validation and UI components
Why Generic Strings Are Inadequate:
Validation Gap:
- GraphQL layer accepts any string value (no validation at the API boundary)
- Validation only happens deeper in WordPress (if at all)
- Invalid emails can pass through GraphQL validation and cause errors downstream
- Each implementation must duplicate validation logic
Lost Semantic Meaning:
- Schema doesn't indicate fields expect email format
- Tools can't provide email-specific features (validation, keyboard types, etc.)
- Code generators produce generic
stringtypes instead of email types - No standardization across custom types and extensions
Developer Burden:
- Must implement custom validation for every email field
- Inconsistent validation logic across different extensions
- Can't leverage WordPress's battle-tested
is_email()function at GraphQL layer - Each developer solving the same problem independently
Ecosystem Fragmentation:
- Extensions and projects resort to registering their own
EmailAddressscalar viaregister_graphql_scalar() - Different implementations with varying validation rules create inconsistency
- Potential naming conflicts when multiple plugins register the same scalar type
- No standardization across the WPGraphQL ecosystem
- Wasted effort as everyone reimplements the same functionality
- Breaking changes if an extension's custom scalar conflicts with a future core implementation
Proposed Solution
Add an EmailAddress scalar type to WPGraphQL that:
- Validates email format using WordPress's built-in
is_email()function - Sanitizes email values using
sanitize_email() - Provides proper type information in the GraphQL schema
- Makes email validation consistent across all queries and mutations
- Includes
specifiedByURLlinking to the HTML email specification
Beyond Functionality: Establishing Competitive Differentiation
This scalar demonstrates WPGraphQL's unique value proposition: custom scalars enable semantic APIs that most REST APIs don't typically provide. By providing validated, self-documenting types, WPGraphQL enables:
- Enhanced Tooling Ecosystem: IDEs, code generators, and API explorers can provide email-specific features
- Reduced Technical Debt: Developers no longer need to implement validation logic—it's built into the schema
- Type Safety: Generated TypeScript/Flow types reflect actual data semantics, not just generic strings
- Self-Documenting APIs: The schema communicates email requirements without external documentation
This EmailAddress scalar is a stepping stone toward a comprehensive custom scalar library that makes WPGraphQL the clear choice for type-safe, developer-friendly WordPress APIs.
Schema Changes:
"""
The EmailAddress scalar type represents a valid email address, conforming to the HTML specification and RFC 5322.
"""
scalar EmailAddress @specifiedBy(url: "https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address")What Users Will Do:
# Register fields using the scalar
register_graphql_field( 'MyType', 'email', [
'type' => 'EmailAddress',
'description' => 'Email address',
] );
# Use in queries/mutations - automatic validation
mutation {
updateUser(input: {
email: "user@example.com" # Validated automatically
}) {
user { email }
}
}Hypothesis
We believe that:
Custom scalars like
EmailAddressprovide strategic competitive value for WPGraphQL by enabling semantic type information, enhanced tooling, reduced technical debt, and better developer experiences that most REST APIs don't typically provide. Extension authors and developers building custom types want validated scalars that use WordPress's native validation functions and provide semantic type information through GraphQL introspection, establishing WPGraphQL as the superior choice for type-safe WordPress APIs.
We will know we're right when:
- 5+ production implementations provide positive feedback over 2+ releases
- No critical bugs or validation issues are reported
- Extension authors adopt it for their custom email fields
- Community agrees WordPress's
is_email()validation is appropriate - Performance overhead is negligible (< 1ms per field resolution)
- Developers cite custom scalars as a reason to choose WPGraphQL over REST
- Tooling ecosystem leverages scalar types for enhanced features
- Pattern established for future custom scalars (Date, URL, JSON, etc.)
We will know we're wrong when:
- WordPress's
is_email()validation is too strict or too lenient for real-world use cases - Performance impact is unacceptable
- Community prefers alternative validation approaches
- The scalar creates more problems than it solves
- Developers don't see value over generic strings
- Tooling doesn't leverage scalar type information
Competitive Differentiation: Why Custom Scalars Matter
WPGraphQL vs. WP REST API: The Scalar Advantage
As detailed in the WPGraphQL vs WP REST API comparison, GraphQL's strongly typed schema and introspection are fundamental differentiators. Custom scalars extend this advantage by adding semantic meaning that most REST APIs don't typically provide.
WP REST API Approach:
{
"email": "user@example.com" // Just a string—no semantic meaning
}- Generic JSON responses with no type information
- No schema enforcement—plugins can register fields without proper schema adherence (as seen with ACF fields returning generic "object" types)
- No schema validation at the API boundary
- Developers must implement validation on every client
- No IDE support or code generation benefits
- Documentation must be manually maintained and often falls out of sync
WPGraphQL with Custom Scalars:
{
user {
email # Type: EmailAddress - validated, self-documenting, tooling-aware
}
}- Enforced schema—every field must declare its type, and custom scalars provide semantic meaning
- Self-documenting schema with semantic types available via introspection
- Validation built into the API layer
- Tooling automatically understands data types through schema introspection
- Code generators produce type-safe interfaces (
email: EmailAddressvsemail: string) - Schema introspection enables rich developer experiences in GraphiQL and other tools
The Schema Enforcement Advantage:
The comparison document illustrates how the WP REST API allows plugins like ACF to register fields as generic "object" types with no further schema information—making it impossible for tooling or clients to understand the data structure.
GraphQL's enforced schema prevents this problem, and custom scalars take it further by expressing semantic meaning (like "this field expects an email address") directly in the schema. This enables tooling that most REST APIs don't typically support.
The Strategic Value
Custom scalars are a differentiating feature that most REST APIs don't typically provide. By establishing a library of custom scalars (EmailAddress, Date, URL, JSON, etc.), WPGraphQL offers:
1. Reduced Technical Debt
- ✅ Before: Every developer implements email validation independently
- ✅ After: Validation is centralized in the schema—write once, use everywhere
- ✅ Impact: Eliminates duplicate code, reduces bugs, standardizes behavior
2. Enhanced Tooling & Developer Experience
- ✅ IDEs: Autocomplete and type hints based on schema introspection (can distinguish
EmailAddressfromString) - ✅ Code Generators: TypeScript/Flow types that reflect actual semantics (
email: EmailAddressvsemail: string) based on introspection - ✅ API Explorers: Schema introspection reveals
EmailAddresstype, enabling server-enhanced tooling (like WPGraphiQL plugins) to provide validation UI - ✅ Form Libraries: Can introspect schema and recognize
EmailAddressfields, automatically applying appropriate validation rules - ✅ Server-Side Tooling: WPGraphQL can provide enhanced GraphiQL experiences (plugins/extensions) that understand custom scalars and provide email-specific validation
3. Type Safety & Runtime Validation
- ✅ Catch Errors Early: Invalid emails rejected at the GraphQL layer, not deep in application code
- ✅ Consistent Behavior: Same validation rules across all extensions and custom types
- ✅ Self-Documenting: Schema communicates requirements without external docs
4. Foundation for Future Scalars
- ✅ EmailAddress establishes patterns for:
Date,URL,JSON,HTML,DateTime, etc. - ✅ Demonstrates WPGraphQL's commitment to semantic, type-safe APIs
- ✅ Creates competitive advantage that most REST APIs don't typically provide
Schema Introspection & Tooling Benefits
The EmailAddress scalar provides rich semantic information through GraphQL's introspection system, enabling tooling to understand that a field represents an email address rather than a generic string. This capability fundamentally differentiates GraphQL from most REST APIs which don't typically provide schema-level semantic information through introspection.
Building on GraphQL's Tooling Advantage:
As noted in the WPGraphQL vs WP REST API comparison, one of GraphQL's core strengths is improved tooling—tools like GraphiQL, GraphQL Playground, and GraphQL Voyager work with any GraphQL API because of the standardized specification and introspection capabilities.
Custom scalars amplify this advantage, though with an important nuance:
- Schema Introspection Reveals Semantic Types: Tools can query the schema via introspection and discover that a field is of type
EmailAddress(not justString), revealing semantic meaning even if the exact validation rules need server-side configuration - Server-Enhanced Tooling: While custom scalars are server-specific (unlike core GraphQL types), GraphQL servers can enhance tooling like GraphiQL with plugins/extensions that understand their custom scalars (e.g., WPGraphiQL plugins that provide email validation UI)
- Code Generation Benefits: Type generators can see
EmailAddressin the schema and generate appropriate types, even if they don't know the exact validation rules—they can still provide better type hints than generic strings - Self-Documenting Schema: The schema itself communicates that
EmailAddressexpects email format, making APIs self-documenting in ways most REST APIs don't typically provide
The Key Difference from REST:
This stands in stark contrast to REST APIs where:
- Field types like "email" are indistinguishable from generic strings in any schema
- No introspection capability exists to discover semantic types
- Each API requires entirely custom tooling with no standardization
- Code generators produce generic types with no semantic information
While custom scalars require server-specific tooling enhancements for full validation features, the schema introspection foundation enables tooling in ways that most REST APIs don't typically support through standardized introspection.
Note: While the GraphQL ecosystem continues to evolve support for advanced scalar features like
@specifiedBydirectives (graphql-php#1140) and@oneOfinput types (GraphiQL#3768), the foundational introspection capabilities already enable significant tooling improvements. Even basic scalar type information allows IDEs, code generators, and API explorers to provide better developer experiences today, with even richer functionality coming as the ecosystem matures.
Before (String Type)
# Introspection Query
{
__type(name: "MyType") {
fields {
name
type {
name
description
}
}
}
}
# Result - No semantic meaning for email fields
{
"data": {
"__type": {
"fields": [
{
"name": "email",
"type": {
"name": "String", # ❌ Generic string - tooling can't infer email validation
"description": null
}
}
]
}
}
}After (EmailAddress Scalar)
# Introspection Query
{
__type(name: "MyType") {
fields {
name
type {
name
description
specifiedByURL
}
}
}
}
# Result - Rich semantic information
{
"data": {
"__type": {
"fields": [
{
"name": "email",
"type": {
"name": "EmailAddress", # ✅ Semantic type information
"description": "The EmailAddress scalar type represents a valid email address, conforming to the HTML specification and RFC 5322.",
"specifiedByURL": "https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address"
}
}
]
}
}
}Tooling Benefits: Real-World Impact
GraphQL IDEs & Explorers:
- 🎯 GraphiQL/WPGraphiQL: Schema introspection reveals
EmailAddresstype; server can enhance GraphiQL with plugins that provide email-specific validation UI (impossible with REST) - 🎯 Schema Documentation: Automatically shows email format requirements via introspection—the schema is self-documenting
- 🎯 Type Hints: IDEs can distinguish
EmailAddressfromStringvia introspection, providing better autocomplete (reduces developer errors)
Code Generation Tools:
- 🎯 TypeScript/Flow: Generate proper email validation types (
type EmailAddress = string & { __brand: 'EmailAddress' }) instead of generic strings - 🎯 Mobile SDKs: Can generate email input fields with appropriate keyboard types automatically
- 🎯 Form Libraries: Automatically apply email validation rules based on schema introspection
API Testing Tools:
- 🎯 Postman/Insomnia: Recognize email fields and provide validation (schema-aware tooling)
- 🎯 Test Generators: Can create realistic test data for email fields (e.g.,
test+tag@example.com) - 🎯 Mock Servers: Can generate semantically correct mock data
Developer Productivity:
- 🎯 Reduced Bugs: Invalid emails caught at API boundary, not in business logic
- 🎯 Faster Development: No need to write validation code—it's in the schema
- 🎯 Better Onboarding: Schema is self-documenting, reducing need for external docs
- 🎯 Consistent Behavior: All extensions use the same validation rules automatically
Tradeoffs and Considerations: The Downsides of Custom Scalars
While custom scalars provide significant value, it's important to acknowledge their limitations and the friction they can introduce:
Server-Specific Friction:
⚠️ Not Universal: Custom scalars are unique to each GraphQL server implementation, unlike core GraphQL types (String, Int, Boolean) that are universal across all GraphQL APIs⚠️ Tooling Fragmentation: Universal GraphQL tools (like generic GraphiQL instances) don't automatically understand WPGraphQL's custom scalars—they require server-specific enhancements⚠️ Learning Curve: Developers working across multiple GraphQL APIs must learn each server's custom scalar types (WPGraphQL'sEmailAddressvs another API's email scalar implementation)⚠️ Code Generation Challenges: Code generators may not know the exact validation rules forEmailAddresswithout WPGraphQL-specific configuration, potentially generating generic string types or requiring manual intervention
Ecosystem Fragmentation Risk:
⚠️ Multiple Implementations: Different WordPress plugins might implement their ownEmailAddressscalars with varying validation rules, creating inconsistency⚠️ Migration Complexity: If custom scalars change validation rules, clients may need updates (though this is also true for validation in business logic)
Why This Matters for the Experiment:
These tradeoffs are precisely why this is an experiment rather than a core feature:
- We need to validate the value outweighs the friction—do developers find the semantic types and validation worth the server-specific nature?
- We need to test ecosystem adoption—will extensions adopt this scalar, or will fragmentation occur?
- We need to measure tooling impact—can we provide sufficient WPGraphQL-specific tooling to minimize friction?
- We need community feedback—do developers prefer custom scalars despite the friction, or would they rather use generic types?
The Comparison:
| Aspect | Core GraphQL Types (String, Int) | Custom Scalars (EmailAddress) |
|---|---|---|
| Universality | ✅ Work identically across all GraphQL APIs | |
| Tooling Support | ✅ Universal tooling works out-of-the-box | |
| Semantic Meaning | ❌ Generic, no semantic information | ✅ Provides semantic meaning and validation |
| Code Generation | ✅ Universal support | |
| Learning Curve | ✅ Standard across ecosystem |
The Value Proposition:
Despite the friction, custom scalars provide value that core types cannot:
- Semantic API Design: Express intent in the schema (
EmailAddressvsString) - Validation at API Boundary: Catch errors before they reach business logic
- Reduced Technical Debt: Validation lives in schema, not duplicated code
- Self-Documenting: Schema communicates requirements without external docs
The experiment will determine if the value of semantic types and validation outweighs the friction of server-specific implementation.
Why an Experiment?
As detailed in the Tradeoffs and Considerations section, custom scalars introduce server-specific friction that core GraphQL types don't have. This experiment is necessary to validate whether the value outweighs the friction.
Strategic Importance:
This experiment isn't just about adding an email scalar—it's about validating WPGraphQL's competitive positioning through custom scalars. We need to prove that:
- Custom scalars provide measurable value over generic strings—enough to justify the server-specific friction
- Developer adoption validates the approach—will developers choose custom scalars despite the learning curve?
- Ecosystem cohesion—will extensions adopt this scalar, or will fragmentation occur?
- Performance is acceptable for production use
- The pattern scales to other scalar types (Date, URL, JSON, etc.)
- Tooling can minimize friction—can WPGraphQL-specific tooling make custom scalars feel as seamless as core types?
Needs Validation At Scale:
- We need to verify that WordPress's
is_email()function covers all real-world use cases - Need to test performance impact of validation on large datasets
- Want community feedback on whether this validation is too strict or too lenient
- Measure developer adoption to validate the competitive value proposition
Design Decisions Needing Input:
- Should we allow filter hooks to customize validation rules?
- Should we provide a
specifiedByURLfor the email format specification? - Should validation happen on serialize, parseValue, parseLiteral, or all three?
- Does this establish patterns for future custom scalars?
Not Breaking (when standalone):
- Adding a new scalar type doesn't break existing functionality
- Existing String fields remain unchanged
- Opt-in adoption allows gradual ecosystem transition
Implementation Plan
- Create EmailAddressScalarExperiment class
- Implement EmailAddress scalar with validation/sanitization
- Register scalar type in schema
- Add serialize, parseValue, and parseLiteral methods
- Write comprehensive unit tests (59 tests, 194 assertions)
- Document scalar usage and behavior
- Create experiment README with examples
- Add
specifiedByURLfor email specification - Pass all code quality checks (PHPCS, PHPStan)
- Gather community feedback
- Monitor performance in production use
Implementation Status:
✅ Complete and Ready for Testing
- All unit tests passing
- Code quality verified (PHPCS & PHPStan)
- Comprehensive documentation
- Located in:
src/Experimental/Experiment/EmailAddressScalarExperiment/
Success Criteria
For Graduation:
- Community Validation: Positive feedback from 5+ production implementations over 2+ releases
- Stability: No critical bugs or validation issues reported
- Performance: Validation overhead < 1ms per field resolution
- Coverage: Validation handles international email formats correctly
- Consensus: Community agrees the validation rules are appropriate
- Strategic Value Validated: Developers cite custom scalars as differentiating value over REST APIs
- Ecosystem Adoption: Extensions adopt the scalar, demonstrating pattern viability
- Tooling Integration: Code generators and IDEs leverage scalar type information
Metrics to Track:
- Number of sites using the experiment
- Validation error rates
- Performance benchmarks
- Community feedback sentiment
- Developer testimonials about custom scalar value
- Extension adoption rate
- Tooling ecosystem integration (code generators, IDEs, form libraries)
Migration Plan
If Graduated:
Since this experiment only adds a new scalar type without changing existing fields, migration is opt-in:
- Documentation: Provide examples of using EmailAddress scalar in custom types
- Best Practices: Recommend EmailAddress for new email fields
- Extension Support: Help popular extensions adopt the scalar
- No Breaking Changes: Existing String email fields remain unchanged
Note: See separate proposal for "Email Address Scalar Fields" experiment which would update core types to use this scalar.
Open Questions
- International Support: Does WordPress's
is_email()handle all international domain formats (IDN, punycode)? - Custom Validation: Should we provide filters to allow stricter/looser validation?
- Error Messages: Should validation errors be more specific (e.g., "missing @" vs "invalid email")?
- Performance: Should we cache validation results within a request?
- Specification URL: Should we add
specifiedByURLpointing to RFC 5322 or similar?
Testing the Scalar
Quick Test Setup:
Add this to your functions.php or plugin:
add_action(
'graphql_register_types',
function() {
// Register a test field that returns a valid email
register_graphql_field(
'RootQuery',
'testEmailField',
[
'type' => 'EmailAddress',
'description' => 'Test field that returns a valid email address',
'resolve' => static function () {
return 'test@example.com';
},
]
);
// Register a test field that tries to return an invalid email
register_graphql_field(
'RootQuery',
'testInvalidEmailField',
[
'type' => 'EmailAddress',
'description' => 'Test field that returns an invalid email address',
'resolve' => static function () {
return 'not-an-email';
},
]
);
// Register a mutation that accepts an email address
register_graphql_mutation(
'testEmailMutation',
[
'inputFields' => [
'email' => [
'type' => 'EmailAddress',
],
],
'outputFields' => [
'email' => [
'type' => 'EmailAddress',
],
],
'mutateAndGetPayload' => static function ( $input ) {
return [
'email' => $input['email'],
];
},
]
);
}
);Test Queries:
- Query
testEmailField→ returns email address, no error - Query
testInvalidEmailField→ returns GraphQL error with validation message - Execute
testEmailMutationwith valid email → success - Execute
testEmailMutationwith invalid email → GraphQL error
Feedback Requested
From the Community:
- Does WordPress's
is_email()validation work for your use cases? - Are there email formats that should be valid but aren't?
- Have you encountered performance issues with email validation?
- Would you use this scalar in your custom types?
- Should validation be configurable via filters?
Testing Needed:
- Test with international domain names (IDN)
- Test with plus-addressing (user+tag@domain.com)
- Test with various TLD lengths
- Benchmark performance with large result sets
Related
- PR feat: EmailAddress Scalar #3423: Original EmailAddress Scalar implementation
- Separate Proposal: "Email Address Scalar Fields" (depends on this)
- WPGraphQL vs WP REST API: Comprehensive comparison highlighting GraphQL's schema enforcement, tooling advantages, and introspection capabilities that custom scalars extend
- Strategic Context: This experiment validates the pattern for future custom scalars (Date, URL, JSON, HTML, DateTime, etc.) that will differentiate WPGraphQL from most REST APIs by providing semantic types that REST APIs don't typically provide
Checklist
- I have searched for similar experiment proposals
- This is a proposed core feature that needs real-world validation
- This needs community input on validation rules
- This needs testing at scale for performance
Status: ✅ Implementation Complete, Ready for Community Feedback
Branch: experiment/email-address-scalar
Experiment Slug: email-address-scalar
Metadata
Metadata
Assignees
Labels
Type
Projects
Status