Scalars

Scalars are the leaf types in 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 convert between the GraphQL wire format (JSON) and the .NET runtime representation. The GraphQL specification specifies five core scalars (String, Int, Float, Boolean, and ID), which form the foundation of every GraphQL server.

Hot Chocolate comes with many more scalars than the GraphQL core scalars, mapping to common .NET primitive types and structs.

.NET TypeGraphQL ScalarBindingNotesSpec
stringStringImplicitUTF-8 character sequenceSpec
boolBooleanImplicittrue or falseSpec
intIntImplicitSigned 32-bit integerSpec
float, doubleFloatImplicitIEEE 754 double-precisionSpec
string, int, GuidIDExplicitUnique identifier, always serialized as stringSpec
decimalDecimalImplicitHigh-precision decimal (separate from Float)Spec
longLongImplicitSigned 64-bit integerSpec
shortShortImplicitSigned 16-bit integerSpec
DateTimeDateTimeImplicitDate and time with time zone offsetSpec
DateTimeOffsetDateTimeImplicitDate and time with time zone offsetSpec
DateOnlyLocalDateImplicitDate without time or time zoneSpec
DateOnlyDateExplicitDate in UTCSpec
DateTimeLocalDateTimeExplicitDate and time without time zoneSpec
TimeOnlyLocalTimeImplicitTime of day without date or time zoneSpec
TimeSpanDurationImplicitDuration of timeSpec
GuidUUIDImplicitUniversally unique identifier (RFC 9562)Spec
UriURIImplicitUniform resource identifier (replaces URL for System.Uri)Spec
UriURLExplicitDeprecated, use URI insteadSpec
byte[]Base64StringImplicitBase64-encoded byte array (replaces deprecated ByteArray)Spec
byteUnsignedByteImplicitUnsigned 8-bit integerSpec
sbyteByteImplicitSigned 8-bit integerSpec
ushortUnsignedShortImplicitUnsigned 16-bit integerSpec
uintUnsignedIntImplicitUnsigned 32-bit integerSpec
ulongUnsignedLongImplicitUnsigned 64-bit integerSpec
JsonElementAnyImplicitAny valid GraphQL valueSpec

Note: Hot Chocolate only exposes scalars that your schema uses. Unused scalars do not appear in the generated schema.

ID

The GraphQL ID scalar is not automatically mapped to a .NET type because it is a semantic type representing a unique identifier. You must annotate fields explicitly to use ID.

ID values are always serialized as strings in responses, but clients can provide int or string values as variables or GraphQL literals. On the server side, you can use string, int, or Guid as the runtime type for ID fields.

C#
public sealed class Product
{
[ID]
public int Id { get; set; }
}
[QueryType]
public static partial class ProductQueries
{
public static Product GetProduct([ID] int id)
{
// Omitted code for brevity
}
}

DateTime Scalars

You can use HotChocolate.Types.DateTimeOptions to configure the built-in BCL-backed DateTime, LocalDateTime, and LocalTime scalars. With these options, you can:

  • Set how many fractional second digits are accepted during parsing (InputPrecision, up to 9)
  • Control how many fractional second digits are written during serialization (OutputPrecision, up to 7)
  • Require input to match the expected scalar format before parsing (ValidateInputFormat)
  • Always emit fractional seconds in serialized output (when OutputPrecision > 0), padded with trailing zeros up to OutputPrecision (AlwaysOutputFractionalSeconds)

Although the built-in scalars can parse up to 9 fractional second digits, the underlying BCL types only preserve up to 7 digits (100-nanosecond precision), so additional digits are rounded during parsing.

By default, trailing zeros are stripped from the fractional component and the fractional component is omitted entirely when zero (for example, 2023-12-24T15:30:00.5000000Z is emitted as 2023-12-24T15:30:00.5Z). Set AlwaysOutputFractionalSeconds = true to keep a fixed-width representation regardless of value. The option has no effect when OutputPrecision is 0, since there are no fractional second digits to emit.

To customize the built-in scalars, register configured scalar instances explicitly:

C#
builder
.AddGraphQL()
.AddType(new DateTimeType(new DateTimeOptions
{
OutputPrecision = 3,
AlwaysOutputFractionalSeconds = true
}))
.AddType(new LocalDateTimeType(new DateTimeOptions
{
OutputPrecision = 3,
AlwaysOutputFractionalSeconds = true
}))
.AddType(new LocalTimeType(new DateTimeOptions
{
OutputPrecision = 3,
AlwaysOutputFractionalSeconds = true
}));

UUID Format

The UUID scalar supports multiple serialization formats:

SpecifierFormat
N00000000000000000000000000000000
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 always returns values in the specified format. When parsing input, it tries the specified format first, then falls back to other formats.

To change the default format:

C#
builder
.AddGraphQL()
.AddType(new UuidType('N'));

Any Scalar

The Any scalar is comparable to object in C#. It accepts any literal and can return any output type.

SDL
type Query {
metadata(filter: Any): Any
}

All of the following queries are valid against an Any argument:

GraphQL
{
a: metadata(filter: 1)
b: metadata(filter: [1, 2, 3])
c: metadata(filter: "text")
d: metadata(filter: true)
e: metadata(filter: { key: "value", nested: { count: 1 } })
}

Runtime type

The Any scalar uses System.Text.Json.JsonElement as its .NET runtime type. Fields annotated with Any expect resolvers to return a JsonElement.

To access an argument dynamically:

C#
JsonElement value = context.ArgumentValue<JsonElement>("filter");
if (value.ValueKind == JsonValueKind.Object)
{
string? name = value.GetProperty("name").GetString();
}

To deserialize into a strongly typed model:

C#
MyFilter filter = context.ArgumentValue<MyFilter>("filter");

You can also inspect the value kind to determine how the argument was provided:

C#
ValueKind kind = context.ArgumentKind("filter");

The ValueKind enum tells you which kind of literal represents the argument:

C#
public enum ValueKind
{
String,
Integer,
Float,
Boolean,
Enum,
Object,
Null
}

An integer literal can contain a long value, and a float literal can be a decimal or a float.

Returning dictionaries and arbitrary .NET types

By default, Any expects a JsonElement. To return common .NET types such as Dictionary<string, object> or ExpandoObject, register the JSON type converter:

C#
builder
.AddGraphQL()
.AddJsonTypeConverter();

With the converter registered, resolvers can return dictionaries or any JSON-serializable object:

C#
[GraphQLType<AnyType>]
public object GetData() => new Dictionary<string, object>
{
{ "name", "John" },
{ "age", 30 }
};

Custom type serialization

For custom reference types, register a dedicated converter to control serialization. For example, to serialize TimeZoneInfo as its string ID instead of a full JSON object:

C#
builder
.AddGraphQL()
.AddTypeConverter<TimeZoneInfo, JsonElement>(
value => JsonSerializer.SerializeToElement(value.Id));

The resolver can then return the type directly:

C#
[GraphQLType<AnyType>]
public TimeZoneInfo GetTimezone() => TimeZoneInfo.Utc; // serializes as "UTC"

Additional Scalars Package

For more specific use cases, install the HotChocolate.Types.Scalars package:

Bash
dotnet add package HotChocolate.Types.Scalars
Warning
All HotChocolate.* packages need to have the same version.
TypeDescription
EmailAddressEmail address as defined in RFC 5322
HexColorHEX color code
HslCSS HSL color as defined here
HslaCSS HSLA color as defined here
IPv4IPv4 address as defined here
IPv6IPv6 address as defined in RFC 8064
IsbnISBN-10 or ISBN-13 number as defined here
LatitudeDecimal degrees latitude number
LongitudeDecimal degrees longitude number
MacAddressIEEE 802 48-bit (MAC-48/EUI-48) and 64-bit (EUI-64) Mac addresses as defined in RFC 7042 and RFC 7043
PhoneNumberE.164 format phone number as defined here
RgbCSS RGB color as defined here
RgbaCSS RGBA color as defined here
UtcOffsetA value of format ±hh:mm

Many of these scalars are built on native .NET types. An email address, for example, is represented as a string, but returning a string from your resolver causes Hot Chocolate to interpret it as a StringType. You need to specify the scalar type explicitly:

C#
[GraphQLType<EmailAddressType>]
public string GetEmail() => "[email protected]";

Learn more about explicit types

NodaTime Scalars

For NodaTime types, install the dedicated package:

Bash
dotnet add package HotChocolate.Types.NodaTime
Warning
All HotChocolate.* packages need to have the same version.

HotChocolate.Types.NodaTime provides alternative implementations of the same five built-in date and time scalars defined by the specifications on scalars.graphql.org:

GraphQL ScalarNodaTime Runtime TypeReplaces Built-in Mapping
DateTimeOffsetDateTimeDateTimeOffset
DurationDuration(see note below for TimeSpan)
LocalDateLocalDateDateOnly
LocalDateTimeLocalDateTimeDateTime
LocalTimeLocalTimeTimeOnly

Note: The Duration scalar uses NodaTime.Duration as its runtime type. Calling AddNodaTime() does not automatically bind System.TimeSpan to DurationType or register TimeSpan↔NodaTime.Duration converters, as the runtime types are not compatible.

These NodaTime scalars expose the same @specifiedBy URLs and implement the same GraphQL scalar specifications as the built-in versions, but they use NodaTime runtime types and may differ subtly in behavior. For example, the NodaTime implementations support up to 9 fractional second digits (nanosecond precision), whereas the equivalent BCL types only support up to 7 fractional second digits (100-nanosecond precision).

Register them with AddNodaTime():

C#
builder
.AddGraphQL()
.AddNodaTime();

AddNodaTime() registers the five scalar types above and configures the related CLR bindings and converters automatically.

If you prefer, you can still register individual scalar types explicitly. For example:

C#
using NodaTimeDurationType = HotChocolate.Types.NodaTime.DurationType;
builder
.AddGraphQL()
.AddType<NodaTimeDurationType>();

NodaTime scalar options

HotChocolate.Types.NodaTime.DateTimeOptions configures the NodaTime-backed DateTime, LocalDateTime, and LocalTime scalars:

  • InputPrecision controls how many fractional second digits are accepted during parsing, up to 9.
  • OutputPrecision controls how many fractional second digits are written during serialization, up to 9.
  • AlwaysOutputFractionalSeconds always emits fractional seconds in serialized output when OutputPrecision > 0, padded with trailing zeros up to OutputPrecision. By default, trailing zeros are stripped and the fractional component is omitted entirely when zero. The option has no effect when OutputPrecision is 0.

Unlike the built-in BCL-backed scalars, the NodaTime implementations preserve up to 9 fractional second digits (nanosecond precision).

If you need non-default NodaTime precision settings, register those scalar types individually instead of using AddNodaTime():

C#
using NodaTimeDateTimeOptions = HotChocolate.Types.NodaTime.DateTimeOptions;
using NodaTimeDateTimeType = HotChocolate.Types.NodaTime.DateTimeType;
using NodaTimeLocalDateTimeType = HotChocolate.Types.NodaTime.LocalDateTimeType;
using NodaTimeLocalTimeType = HotChocolate.Types.NodaTime.LocalTimeType;
builder
.AddGraphQL()
.AddType(new NodaTimeDateTimeType(new NodaTimeDateTimeOptions
{
OutputPrecision = 3,
AlwaysOutputFractionalSeconds = true
}))
.AddType(new NodaTimeLocalDateTimeType(new NodaTimeDateTimeOptions
{
OutputPrecision = 3,
AlwaysOutputFractionalSeconds = true
}))
.AddType(new NodaTimeLocalTimeType(new NodaTimeDateTimeOptions
{
OutputPrecision = 3,
AlwaysOutputFractionalSeconds = true
}));

Binding Behavior

You can override the default .NET-to-scalar mappings by specifying type bindings explicitly:

C#
builder
.AddGraphQL()
.BindRuntimeType<string, StringType>();

You can also bind scalars to arrays or complex types:

C#
builder
.AddGraphQL()
.BindRuntimeType<byte[], Base64StringType>();

Custom Converters

You can reuse existing scalar types with different runtime types by registering converters. For example, to map NodaTime's OffsetDateTime to the existing DateTimeType:

C#
public sealed class ScheduleQueries
{
public OffsetDateTime GetDateTime(OffsetDateTime offsetDateTime)
{
return offsetDateTime;
}
}
C#
builder
.AddGraphQL()
.AddQueryType<ScheduleQueries>()
.BindRuntimeType<OffsetDateTime, DateTimeType>()
.AddTypeConverter<OffsetDateTime, DateTimeOffset>(
x => x.ToDateTimeOffset())
.AddTypeConverter<DateTimeOffset, OffsetDateTime>(
x => OffsetDateTime.FromDateTimeOffset(x));

Custom Scalars

A custom scalar converts values between the GraphQL wire format and a .NET runtime type. Each custom scalar handles four conversion scenarios:

MethodDirectionPurpose
OnCoerceInputLiteralGraphQL literal to .NETParses values embedded in a query, e.g. { field(arg: "value") }
OnCoerceInputValueJSON to .NETParses values provided as variables in the request
OnCoerceOutputValue.NET to JSONWrites resolver results to the response
OnValueToLiteral.NET to GraphQL literalConverts default values for schema introspection

Extend ScalarType<TRuntimeType, TLiteral> to create a custom scalar:

C#
public sealed class CreditCardNumberType : ScalarType<string, StringValueNode>
{
private readonly ICreditCardValidator _validator;
// You can inject services 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 value)
{
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.

Integer scalars

Use IntegerTypeBase<T> for numeric scalars with min/max constraints. The base class handles parsing, validation, and range checking automatically.

C#
public sealed 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();
protected override void OnCoerceOutputValue(int runtimeValue, ResultElement resultValue)
=> resultValue.SetNumberValue(runtimeValue);
protected override IValueNode OnValueToLiteral(int runtimeValue)
=> new IntValueNode(runtimeValue);
}

IntegerTypeBase validates that values fall within the specified range and throws a LeafCoercionException if they do not. To customize the error message, override FormatError:

C#
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 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 works well for formats like phone numbers, postal codes, or identifiers.

C#
public sealed 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:

C#
builder
.AddGraphQL()
.AddType(new RegexType(
"PostalCode",
@"^\d{5}(-\d{4})?$",
"US postal code in format 12345 or 12345-6789"));

To customize the error message for pattern validation failures, override FormatException:

C#
protected override LeafCoercionException FormatException(string runtimeValue)
=> new LeafCoercionException(
$"'{runtimeValue}' is not a valid hex color. Expected format: #RGB or #RRGGBB.",
this);

Next Steps

Last updated on May 20, 2026 by Michael Staib