Error Handling
EntityGraphQL provides comprehensive error handling that follows the GraphQL specification. This page covers all aspects of how errors work in EntityGraphQL, from validation errors to execution errors and partial results.
Types of Errors
EntityGraphQL handles several types of errors differently:
1. Validation Errors
These occur during query parsing and validation, before execution begins:
- Query syntax errors: Invalid GraphQL syntax
- Schema validation: Fields that don't exist, wrong argument types, etc.
- Argument validation: Using
[Required],[Range],[StringLength]attributes
public class PersonArgs
{
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
[Range(0, 150, ErrorMessage = "Age must be between 0 and 150")]
public int Age { get; set; }
}
Validation errors:
- Prevent query execution entirely
- Are returned in the
errorsarray withdata: null
2. Execution Errors
These occur during field execution:
[GraphQLMutation]
public Person AddPerson(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Name cannot be empty");
// ... rest of logic
}
Execution errors:
- Allow partial results (other fields can still succeed)
- Are returned alongside successful data
Error Response Format
Validation Error Response
{
"data": null,
"errors": [
{
"message": "Field 'nonExistentField' does not exist on type 'Query'"
}
]
}
Execution Error Response
{
"data": {
"successfulField": "some data",
"failedField": null
},
"errors": [
{
"message": "Name cannot be empty",
"path": ["failedField"]
}
]
}
Partial Results
EntityGraphQL supports partial results according to the GraphQL specification. When multiple fields are requested and some fail, you get results from successful fields plus error information.
How It Works
EntityGraphQL executes each top-level field separately:
query MultipleFields {
users {
id
name
} # Succeeds
posts {
id
title
} # Fails
comments {
id
text
} # Succeeds
}
Response:
{
"data": {
"users": [{ "id": "1", "name": "Alice" }],
"posts": null,
"comments": [{ "id": "1", "text": "Great post!" }]
},
"errors": [
{
"message": "Access denied to posts",
"path": ["posts"]
}
]
}
Nullable vs Non-Nullable Fields
Field nullability affects error behavior:
Nullable Fields
// This field is nullable
schema.Query().AddField("optionalData", ctx => MightFail());
- Failed nullable fields return
null - Error included in
errorsarray - Other fields continue executing
Non-Nullable Fields
// This field is non-nullable
schema.Query().AddField("requiredData", ctx => MightFail()).IsNullable(false);
- Failed non-nullable fields bubble up to the next nullable parent
- May cause entire
datato becomenullif error reaches the root - Follows GraphQL spec error propagation rules
Aliases
When using field aliases, the path uses the alias name:
query {
primaryUser: user(id: 1) {
name
}
backupUser: user(id: 2) {
name
}
}
Error path: ["primaryUser"] or ["backupUser"]
Using IGraphQLValidator
For collecting multiple validation errors in mutations:
[GraphQLMutation]
public Person AddPersonWithValidation(PersonInput input, IGraphQLValidator validator)
{
if (string.IsNullOrEmpty(input.Name))
validator.AddError("Name is required");
if (input.Age < 0)
validator.AddError("Age must be positive");
if (input.Age > 150)
validator.AddError("Age seems unrealistic");
// Check for errors before proceeding
if (validator.HasErrors)
return null; // Errors automatically included with field path
return CreatePerson(input);
}
Register the validator:
services.AddGraphQLValidator(); // In ASP.NET Core
This returns multiple errors for a single field:
{
"data": { "addPersonWithValidation": null },
"errors": [
{
"message": "Name is required",
"path": ["addPersonWithValidation"]
},
{
"message": "Age must be positive",
"path": ["addPersonWithValidation"]
}
]
}
Exception Handling
Development vs Production
EntityGraphQL gates exception-message disclosure behind an IsDevelopment flag. In production mode (the default for ASP.NET deployments), any exception not on the allow-list is replaced with the generic message "Error occurred":
{
"message": "Field 'reports' - Error occurred"
}
In development mode, the raw message is forwarded as-is:
{
"message": "Field 'reports' - Object reference not set to an instance of an object"
}
How IsDevelopment is set
ASP.NET (recommended) — AddGraphQLSchema auto-detects IWebHostEnvironment. When the environment is "Development", messages are disclosed; every other named environment (Staging, Production, etc.) masks them. You never need to set this manually in typical setups.
To override the auto-detection — for example to enable development messages in a staging environment during an incident — set IsDevelopment explicitly:
builder.Services.AddGraphQLSchema<MyContext>(options =>
{
options.Schema.IsDevelopment = true; // force dev messages even in non-Development env
});
Direct schema creation (non-ASP.NET) — SchemaProviderOptions.IsDevelopment defaults to true, so messages are disclosed unless you opt out:
var schema = SchemaBuilder.FromObject<MyContext>(
schemaOptions: new SchemaProviderOptions { IsDevelopment = false }
);
Allowed Exceptions
Even in production mode, certain exceptions can be allowed to pass their message through. EntityGraphQL's own exception types (EntityGraphQLException, EntityGraphQLFieldException, EntityGraphQLSchemaException) are always allowed.
For your own domain exceptions, use [AllowedException] on the class or add them to the allow-list at schema setup:
// Mark your exception class directly
[AllowedException]
public class ValidationException : Exception
{
public ValidationException(string message) : base(message) { }
}
// Or add them at schema creation
builder.Services.AddGraphQLSchema<MyContext>(options =>
{
options.Schema.AllowedExceptions.Add(new AllowedException(typeof(ArgumentException)));
// Pass exactMatch: true to require the exact type (no subclasses)
options.Schema.AllowedExceptions.Add(new AllowedException(typeof(DomainException), exactMatch: true));
});
Custom Error Extensions
Add custom data to errors:
[GraphQLMutation]
public Person AddPerson(PersonInput input, IGraphQLValidator validator)
{
if (input.Age < 18)
{
validator.AddError("Age restriction", new Dictionary<string, object>
{
["code"] = "AGE_RESTRICTION",
["minimumAge"] = 18,
["providedAge"] = input.Age
});
return null;
}
return CreatePerson(input);
}
Response:
{
"errors": [
{
"message": "Age restriction",
"path": ["addPerson"],
"extensions": {
"code": "AGE_RESTRICTION",
"minimumAge": 18,
"providedAge": 15
}
}
]
}