See the latest stable version instead.
Scalars
Scalars are the leaf types of a GraphQL schema — they represent concrete values like strings, numbers, and dates. Unlike object types, scalars cannot be decomposed further; they are where the query ends and actual data is returned.
Every scalar defines how values are converted between their GraphQL wire format (JSON) and .NET runtime representation. GraphQL includes five built-in scalars (String, Int, Float, Boolean, and ID), but you can also define custom scalars like DateTime, Uuid, or EmailAddress to add domain-specific validation and improve the clarity of your API. Hot Chocolate already comes with lots of additional scalars.
Built-in Scalars
The GraphQL specification defines five scalar types that every implementation must support.
String
type Product { description: String;}
This scalar represents a UTF-8 character sequence.
It is automatically inferred from the usage of the .NET string type.
Boolean
type Product { purchasable: Boolean;}
This scalar represents a Boolean value, which can be either true or false.
It is automatically inferred from the usage of the .NET bool type.
Int
type Product { quantity: Int;}
This scalar represents a signed 32-bit numeric non-fractional value.
It is automatically inferred from the usage of the .NET int type.
Float
type Product { price: Float;}
This scalar represents double-precision fractional values, as specified by IEEE 754.
It is automatically inferred from the usage of the .NET float or double type.
Note: We introduced a separate
Decimalscalar to handledecimalvalues.
ID
type Product { id: ID!;}
This scalar is used to facilitate technology-specific Ids, like int, string or Guid.
It is not automatically inferred and the IdType needs to be explicitly specified.
ID values are always represented as a String in client-server communication, but can be coerced to their expected type on the server.
public class Product{ [GraphQLType<IdType>] public int Id { get; set; }}
public class Query{ public Product GetProduct([GraphQLType<IdType>] int id) { // Omitted code for brevity }}
C#public class Product{ public int Id { get; set; }}
public class ProductType : ObjectType<Product>{ protected override void Configure(IObjectTypeDescriptor<Product> descriptor) { descriptor.Name("Product");
descriptor.Field(f => f.Id).Type<IdType>(); }}
public class QueryType : ObjectType{ protected override void Configure(IObjectTypeDescriptor descriptor) { descriptor.Name(OperationTypeNames.Query);
descriptor .Field("product") .Argument("id", a => a.Type<IdType>()) .Type<ProductType>() .Resolve(context => { var id = context.ArgumentValue<int>("id");
// Omitted code for brevity }); }}
Notice how our code uses int for the Id, but in a request / response it would be serialized as a string. This allows us to switch the CLR type of our Id, without affecting the schema and our clients.
.NET Scalars
In addition to the scalars defined by the specification, Hot Chocolate also supports the following set of scalar types:
| Type | Description |
|---|---|
| Any | The Any scalar type represents any valid GraphQL value. |
| Base64String | The Base64String scalar type represents an array of bytes encoded as a Base64 string. |
| Byte | The Byte scalar type represents a signed 8-bit integer. |
| Date | The Date scalar type represents a date in UTC. |
| DateTime | The DateTime scalar type represents a date and time with time zone offset information. |
| Decimal | The Decimal scalar type represents a decimal floating-point number with high precision. |
| LocalDate | The LocalDate scalar type represents a date without time or time zone information. |
| LocalDateTime | The LocalDateTime scalar type represents a date and time without time zone information. |
| LocalTime | The LocalTime scalar type represents a time of day without date or time zone information. |
| Long | The Long scalar type represents a signed 64-bit integer. |
| Short | The Short scalar type represents a signed 16-bit integer. |
| TimeSpan | The TimeSpan scalar type represents a duration of time. |
| UnsignedByte | The UnsignedByte scalar type represents an unsigned 8-bit integer. |
| UnsignedInt | The UnsignedInt scalar type represents an unsigned 32-bit integer. |
| UnsignedLong | The UnsignedLong scalar type represents an unsigned 64-bit integer. |
| UnsignedShort | The UnsignedShort scalar type represents an unsigned 16-bit integer. |
| URI | The URI scalar type represents a Uniform Resource Identifier (URI) as defined by RFC 3986. |
| URL | The URL scalar type represents a Uniform Resource Locator (URL) as defined by RFC 3986. |
| UUID | The UUID scalar type represents a Universally Unique Identifier (UUID) as defined by RFC 9562. |
DateTime Type
Any Type
The Any scalar is a special type that can be compared to object in C#.
Any allows us to specify any literal or return any output type.
Consider the following type:
type Query { foo(bar: Any): String}
Since our field foo specifies an argument bar of type Any all of the following queries would be valid:
{ a: foo(bar: 1) b: foo(bar: [1, 2, 3, 4, 5]) a: foo(bar: "abcdef") a: foo(bar: true) a: foo(bar: { a: "foo", b: { c: 1 } }) a: foo(bar: [{ a: "foo", b: { c: 1 } }, { a: "foo", b: { c: 1 } }])}
The same goes for the output side. Any can return a structure of data although it is a scalar type.
If we want to access the data we can either fetch data as an object or you can ask the context to provide it as a specific object.
object foo = context.ArgumentValue<object>("bar");Foo foo = context.ArgumentValue<Foo>("bar");
We can also ask the context which kind the current argument is:
ValueKind kind = context.ArgumentKind("bar");
The value kind will tell us by which kind of literal the argument is represented.
An integer literal can still contain a long value and a float literal could be a decimal but it also could just be a float.
public enum ValueKind{ String, Integer, Float, Boolean, Enum, Object, Null}
If we want to access an object dynamically without serializing it to a strongly typed model we can get it as IReadOnlyDictionary<string, object> or as ObjectValueNode.
Lists can be accessed generically by getting them as IReadOnlyList<object> or as ListValueNode.
Uuid Type
The Uuid scalar supports the following serialization formats.
| Specifier | Format |
|---|---|
| N | 00000000000000000000000000000000 |
| D (default) | 00000000-0000-0000-0000-000000000000 |
| B | {00000000-0000-0000-0000-000000000000} |
| P | (00000000-0000-0000-0000-000000000000) |
| X | {0x00000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}} |
The UuidType will always return the value in the specified format. In case it is used as an input type, it will first try to parse the result in the specified format. If the parsing does not succeed, it will try to parse the value in other formats.
To change the default format we have to register the UuidType with the specifier on the schema:
builder.Services .AddGraphQLServer() .AddType(new UuidType('D'));
Additional Scalars
We also offer a separate package with scalars for more specific use cases.
To use these scalars we have to add the HotChocolate.Types.Scalars package.
dotnet add package HotChocolate.Types.ScalarsHotChocolate.* packages need to have the same version.Available Scalars:
| Type | Description |
|---|---|
| EmailAddress | Email address, represented as UTF-8 character sequences, as defined in RFC5322 |
| HexColor | HEX color code |
| Hsl | CSS HSL color as defined here |
| Hsla | CSS HSLA color as defined here |
| IPv4 | IPv4 address as defined here |
| IPv6 | IPv6 address as defined in RFC8064 |
| Isbn | ISBN-10 or ISBN-13 number as defined here |
| Latitude | Decimal degrees latitude number |
| Longitude | Decimal degrees longitude number |
| MacAddress | IEEE 802 48-bit (MAC-48/EUI-48) and 64-bit (EUI-64) Mac addresses, represented as UTF-8 character sequences, as defined in RFC7042 and RFC7043 |
| PhoneNumber | A value that conforms to the standard E.164 format as defined here |
| Rgb | CSS RGB color as defined here |
| Rgba | CSS RGBA color as defined here |
| UtcOffset | A value of format ±hh:mm |
Most of these scalars are built on top of native .NET types. An Email Address for example is represented as a string, but just returning a string from our resolver would result in Hot Chocolate interpreting it as a StringType. We need to explicitly specify that the returned type (string) should be treated as an EmailAddressType.
[GraphQLType(typeof(EmailAddressType))]public string GetEmail() => "[email protected]";
Learn more about explicitly specifying GraphQL types
NodaTime
We also offer a package specifically for NodaTime.
It can be installed like the following.
dotnet add package HotChocolate.Types.NodaTimeHotChocolate.* packages need to have the same version.Available Scalars:
| Type | Description | Example |
|---|---|---|
| DateTimeZone | A NodaTime DateTimeZone | "Europe/Rome" |
| Duration | A NodaTime Duration | "-123:07:53:10.019" |
| Instant | A NodaTime Instant | "2020-02-20T17:42:59Z" |
| IsoDayOfWeek | A NodaTime IsoDayOfWeek | 7 |
| LocalDate | A NodaTime LocalDate | "2020-12-25" |
| LocalDateTime | A NodaTime LocalDateTime | "2020-12-25T13:46:78" |
| LocalTime | A NodaTime LocalTime | "12:42:13.03101" |
| OffsetDateTime | A NodaTime OffsetDateTime | "2020-12-25T13:46:78+02:35" |
| OffsetDate | A NodaTime OffsetDate | "2020-12-25+02:35" |
| OffsetTime | A NodaTime OffsetTime | "13:46:78+02:35" |
| Offset | A NodeTime Offset | "+02:35" |
| Period | A NodeTime Period | "P-3W3DT139t" |
| ZonedDateTime | A NodaTime ZonedDateTime | "2020-12-31T19:40:13 Asia/Kathmandu +05:45" |
When returning a NodaTime type from one of our resolvers, for example a NodaTime.Duration, we also need to explicitly register the corresponding scalar type. In the case of a NodaTime.Duration this would be the DurationType scalar.
public class Query{ public Duration GetDuration() => Duration.FromMinutes(3);}
builder.Services .AddGraphQLServer() .AddQueryType<Query>() .AddType<DurationType>();
This package was originally developed by @shoooe.
Binding behavior
Hot Chocolate binds most of the native .NET types automatically.
A System.String is for example automatically mapped to a StringType in the schema.
We can override these mappings by explicitly specifying type bindings.
builder.Services .AddGraphQLServer() .BindRuntimeType<string, StringType>();
Furthermore, we can also bind scalars to arrays or type structures:
builder.Services .AddGraphQLServer() .BindRuntimeType<byte[], Base64StringType>();
Hot Chocolate only exposes the used scalars in the generated schema, keeping it simple and clean.
Custom Converters
We can reuse existing scalar types and bind them to different runtime types by specifying converters.
We could for example register converters between NodaTime's OffsetDateTime and .NET's DateTimeOffset to reuse the existing DateTimeType.
public class Query{ public OffsetDateTime GetDateTime(OffsetDateTime offsetDateTime) { return offsetDateTime; }}
builder.Services .AddGraphQLServer() .AddQueryType<Query>() .BindRuntimeType<OffsetDateTime, DateTimeType>() .AddTypeConverter<OffsetDateTime, DateTimeOffset>( x => x.ToDateTimeOffset()) .AddTypeConverter<DateTimeOffset, OffsetDateTime>( x => OffsetDateTime.FromDateTimeOffset(x));
Scalar Options
Some scalars like TimeSpan or Uuid have options like their serialization format.
We can specify these options by registering the scalar explicitly.
builder.Services .AddGraphQLServer() .AddType(new UuidType('D'));
Custom Scalars
A scalar type converts values between their GraphQL wire format and their .NET runtime representation. Each custom scalar must handle four conversion scenarios:
| Method | Direction | Purpose |
|---|---|---|
| OnCoerceInputLiteral | GraphQL literal → .NET | Parses values embedded directly in a query, e.g. { field(arg: "value") } |
| OnCoerceInputValue | JSON → .NET | Parses values provided as variables in the request |
| OnCoerceOutputValue | .NET → JSON | Writes resolver results to the response |
| OnValueToLiteral | .NET → GraphQL literal | Converts default values for schema introspection |
The easiest way to create a custom scalar is to extend ScalarType<TRuntimeType, TLiteral>, which provides the basic scaffolding.
public sealed class CreditCardNumberType : ScalarType<string, StringValueNode>{ private readonly ICreditCardValidator _validator;
// we can inject services that have been registered // with the DI container public CreditCardNumberType(ICreditCardValidator validator) : base("CreditCardNumber") { _validator = validator;
Description = "Represents a credit card number"; }
protected override string OnCoerceInputLiteral(StringValueNode valueLiteral) { AssertCreditCardNumberFormat(valueLiteral.Value); return valueLiteral.Value; }
protected override string OnCoerceInputValue( JsonElement inputValue, IFeatureProvider context) { var value = inputValue.GetString()!; AssertCreditCardNumberFormat(value); return value; }
protected override void OnCoerceOutputValue( string runtimeValue, ResultElement resultValue) { AssertCreditCardNumberFormat(runtimeValue); resultValue.SetStringValue(runtimeValue); }
protected override StringValueNode OnValueToLiteral(string runtimeValue) { AssertCreditCardNumberFormat(runtimeValue); return new StringValueNode(runtimeValue); }
private void AssertCreditCardNumberFormat(string number) { if (!_validator.ValidateCreditCard(value)) { throw new LeafCoercionException( "The specified value is not a valid credit card number.", this); } }}
Specialized Base Classes
Hot Chocolate provides specialized base classes for common scalar patterns that handle much of the boilerplate for you.
Integer Scalars
Use IntegerTypeBase<T> for scalars that represent numeric values with min/max constraints. The base class handles parsing, validation, and range checking automatically.
public class TcpPortType : IntegerTypeBase<int>{ public TcpPortType() : base("TcpPort", min: 1, max: 65535) { Description = "A valid TCP port number (1-65535)"; }
protected override int OnCoerceInputLiteral(IntValueNode valueLiteral) => valueLiteral.ToInt32();
protected override int OnCoerceInputValue(JsonElement inputValue) => inputValue.GetInt32();
public override void OnCoerceOutputValue(int runtimeValue, ResultElement resultValue) => resultValue.SetNumberValue(runtimeValue);
public override IValueNode OnValueToLiteral(int runtimeValue) => new IntValueNode(runtimeValue);}
The IntegerTypeBase automatically validates that values fall within the specified range and throws a LeafCoercionException if they don't. To customize the error message, override the FormatError method:
protected override LeafCoercionException FormatError(int runtimeValue) => new LeafCoercionException( $"The value {runtimeValue} is not a valid TCP port. Must be between 1 and 65535.", this);
Hot Chocolate also provides a FloatTypeBase<T> for floating-point scalars (float, double, decimal) that need min/max range validation.
Regex-Based Scalars
Use RegexType for string scalars that must match a specific pattern. This is ideal for formats like phone numbers, postal codes, or identifiers.
public class HexColorType : RegexType{ public HexColorType() : base( "HexColor", @"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", "A hex color code, e.g. #FF5733 or #F53") { }}
You can also instantiate RegexType directly when registering scalars:
builder.Services .AddGraphQLServer() .AddType(new RegexType( "PostalCode", @"^\d{5}(-\d{4})?$", "US postal code in format 12345 or 12345-6789"));
Like IntegerTypeBase and FloatTypeBase, RegexType automatically validates values and throws a LeafCoercionException if they don't match the pattern. To customize the error message, override the FormatException method:
protected override LeafCoercionException FormatException(string runtimeValue) => new LeafCoercionException( $"'{runtimeValue}' is not a valid hex color. Expected format: #RGB or #RRGGBB.", this);