Maybe
In GraphQL, there’s an important distinction between a field that is null and
a field that is completely absent from the input. Strawberry’s Maybe type
allows you to differentiate between these states:
Maybe is the recommended way to handle optional input fields in Strawberry. If
you’re using strawberry.UNSET for this purpose, we encourage migrating to
Maybe for better type safety and clearer semantics. See
Migrating from UNSET for details.
For Maybe[str] :
- Field present with a value:
Some("hello") - Field completely absent:
None
For Maybe[str | None] (when you need to handle explicit nulls):
- Field present with a value:
Some("hello") - Field present but explicitly null:
Some(None) - Field completely absent:
None
This is particularly useful for update operations where you need to distinguish between “set this field to null” and “don’t change this field at all”.
The design is inspired by Rust’s
Option<T> type and similar patterns
in functional programming languages like Haskell’s Maybe and Scala’s Option .
What problem does strawberry.Maybe solve?
Consider this common scenario: you have a user profile with an optional phone number, and you want to provide an update mutation. With traditional nullable types, you can’t distinguish between:
- “Set the phone number to null” (remove the phone number)
- “Don’t change the phone number” (leave it as is)
Both would be represented as phone: null in your GraphQL mutation.
Basic Usage
Here’s how to use Maybe in your Strawberry schema:
import strawberry
@strawberry.input
class UpdateUserInput:
name: str | None = None # Traditional optional field
phone: strawberry.Maybe[str | None] # Maybe field
@strawberry.type
class User:
name: str
phone: str | None
@strawberry.type
class Mutation:
@strawberry.mutation
def update_user(self, user_id: str, input: UpdateUserInput) -> User:
user = get_user(user_id) # Your user retrieval logic
# Traditional optional field - only update if provided
if input.name is not None:
user.name = input.name
# Maybe field - check if field was provided at all
if input.phone is not None: # Field was provided
user.phone = input.phone.value # Access the actual value
# If input.phone is None, the field wasn't provided - no change
return userUnderstanding Some()
When a Maybe field has a value (including null ), it’s wrapped in a Some()
container:
# Field provided with a string value
phone = strawberry.Some("555-1234")
print(phone.value) # "555-1234"
# Field provided with null value
phone = strawberry.Some(None)
print(phone.value) # None
# Field not provided at all
phone = None
print(phone) # NoneGraphQL Schema
When you use Maybe in your schema, it appears as a nullable field in GraphQL:
@strawberry.input
class UpdateUserInput:
phone: strawberry.Maybe[str | None]Generates this GraphQL schema:
input UpdateUserInput {
phone: String
}Common Patterns
Input Types for Updates
Maybe is most commonly used in input types for update operations:
@strawberry.input
class UpdatePostInput:
# Maybe[T] - value or absent, null is INVALID
# Use when the field must have a value if provided
title: strawberry.Maybe[str]
content: strawberry.Maybe[str]
published: strawberry.Maybe[bool]
# Maybe[T | None] - value, null, or absent
# Use when null is a valid value (e.g., to clear/remove the field)
tags: strawberry.Maybe[list[str] | None]
@strawberry.type
class Mutation:
@strawberry.mutation
def update_post(self, post_id: str, input: UpdatePostInput) -> Post:
post = get_post(post_id)
# Only update fields that were explicitly provided
if input.title is not None:
post.title = input.title.value
if input.content is not None:
post.content = input.content.value
if input.published is not None:
post.published = input.published.value
if input.tags is not None:
post.tags = input.tags.value # Could be None to clear tags
return postMaybe vs Optional vs Nullable
The key distinction is between Maybe[T] and Maybe[T | None] :
-
Maybe[T]: Two states - value provided or absent. Use when the field cannot be set to null (e.g., a requiredtitlethat you either update or leave unchanged). -
Maybe[T | None]: Three states - value provided, null provided, or absent. Use when null is meaningful (e.g., clearing an optionalphonenumber).
Both Maybe[T] and Maybe[T | None] generate the same GraphQL schema (a
nullable field). The distinction between them is enforced by Strawberry’s
internal validation, not by the GraphQL spec. When a client sends null to a
Maybe[T] field, Strawberry returns a validation error before the resolver is
called.
Full comparison:
| Type | Python | GraphQL | Absent | Null | Value |
|---|---|---|---|---|---|
str |
Required | String! |
❌ Error | ❌ Error | ✅ Value |
str | None |
Optional | String |
✅ None | ✅ None | ✅ Value |
strawberry.Maybe[str] |
Maybe | String |
✅ None | ❌ Error | ✅ Some(value) |
strawberry.Maybe[str | None] |
Maybe+Null | String |
✅ None | ✅ Some(None) | ✅ Some(value) |
Best Practices
When to Use Maybe
Use Maybe when you need to distinguish between:
- Field not provided (no change)
- Field provided with null (clear/remove)
- Field provided with value (set/update)
Common use cases:
- Update mutations
- Patch operations
- Optional filters that need to distinguish between “not filtering” and “filtering by null”
When to Use Optional Instead
Use regular optional types (str | None ) when:
- You only need two states: value or null
- The field absence and null have the same meaning
- You’re defining output types (GraphQL responses)
Error Handling
Always check if a Maybe field was provided before accessing its value:
# Good
if input.phone is not None:
user.phone = input.phone.value
# Bad - will raise AttributeError if phone is None
user.phone = input.phone.valueHelper Functions
You can create helper functions to make Maybe handling cleaner:
def update_if_provided(obj, field_name: str, maybe_value):
"""Update object field only if Maybe value was provided."""
if maybe_value is not None:
setattr(obj, field_name, maybe_value.value)
# Usage
update_if_provided(user, "phone", input.phone)
update_if_provided(user, "email", input.email)Migrating from UNSET
If you're currently using strawberry.UNSET to differentiate between absent and
null values, we recommend migrating to Maybe for better type safety and
clearer semantics.
Why Migrate?
Maybe provides advantages over UNSET :
- Type Safety:
Maybe[T]is a proper generic type that works correctly with type checkers, whereasUNSETis typed asAnywhich defeats static analysis - Explicit Nullability: With
UNSET, you writefield: str | None = UNSETwhere it’s ambiguous whetherNoneis a valid value or represents “absent”. WithMaybe,Maybe[str]means null is invalid, whileMaybe[str | None]explicitly allows null as a value
Migration Example
Before (using UNSET):
import strawberry
@strawberry.input
class UpdateUserInput:
name: str | None = strawberry.UNSET
phone: str | None = strawberry.UNSET
@strawberry.type
class Mutation:
@strawberry.mutation
def update_user(self, input: UpdateUserInput) -> User:
if input.name is not strawberry.UNSET:
user.name = input.name # Could be a string or None
if input.phone is not strawberry.UNSET:
user.phone = input.phone
return userAfter (using Maybe):
import strawberry
@strawberry.input
class UpdateUserInput:
name: strawberry.Maybe[str | None]
phone: strawberry.Maybe[str | None]
@strawberry.type
class Mutation:
@strawberry.mutation
def update_user(self, input: UpdateUserInput) -> User:
if input.name is not None:
user.name = input.name.value # Access via .value
if input.phone is not None:
user.phone = input.phone.value
return userKey Differences
| Aspect | UNSET | Maybe |
|---|---|---|
| Check absent | value is strawberry.UNSET |
value is None |
| Access value | value (direct) |
value.value (via Some) |
| Type annotation | T | None = UNSET |
Maybe[T] or Maybe[T | None] |
| Null handling | Implicit | Explicit with T | None |
Codemod
If you have existing Maybe[T] annotations that need to accept explicit null
values, Strawberry provides a codemod to convert them to Maybe[T | None] :
python -m libcst.tool codemod strawberry.codemods.maybe_optional.ConvertMaybeToOptional . Note: This codemod is for updating Maybe annotations, not for migrating from
UNSET to Maybe . Migration from UNSET requires manual changes to update
both the type annotations and the value access patterns (from value to
value.value ).
Related Types
- Input Types - Using Maybe in input type definitions
- Resolvers - Using Maybe in resolver arguments
- Union Types - Combining Maybe with union types
- Scalars - Custom scalar types with Maybe