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. GraphQL includes five built-in scalars (String, Int, Float, Boolean, and ID), and Hot Chocolate adds many more for common .NET types.

.NET Type to GraphQL Scalar Mapping

Hot Chocolate automatically maps .NET types to GraphQL scalars. When you use a string property, it becomes a String field in your schema without any configuration.

.NET TypeGraphQL ScalarNotes
stringStringUTF-8 character sequence
boolBooleantrue or false
intIntSigned 32-bit integer
float, doubleFloatIEEE 754 double-precision
decimalDecimalHigh-precision decimal (separate from Float)
longLongSigned 64-bit integer
shortShortSigned 16-bit integer
DateTimeDateTimeDate and time with time zone offset
DateTimeOffsetDateTimeDate and time with time zone offset
DateOnlyDateDate without time or time zone
TimeOnlyLocalTimeTime of day without date or time zone
TimeSpanDurationDuration of time (renamed from TimeSpan in v16)
GuidUUIDUniversally unique identifier (RFC 9562)
UriURIUniform resource identifier (new in v16, replaces URL for System.Uri)
byte[]Base64StringBase64-encoded byte array (new in v16, replaces deprecated ByteArray)
byteUnsignedByteUnsigned 8-bit integer (renamed in v16)
sbyteByteSigned 8-bit integer (renamed in v16)
ushortUnsignedShortUnsigned 16-bit integer (new in v16)
uintUnsignedIntUnsigned 32-bit integer (new in v16)
ulongUnsignedLongUnsigned 64-bit integer (new in v16)
JsonElementAnyAny valid GraphQL value (v16 merged Json into Any)

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

Built-in Spec Scalars

String

Represents a UTF-8 character sequence. Automatically inferred from string.

SDL
type Product {
description: String
}

Boolean

Represents true or false. Automatically inferred from bool.

SDL
type Product {
purchasable: Boolean
}

Int

Represents a signed 32-bit integer. Automatically inferred from int.

SDL
type Product {
quantity: Int
}

Float

Represents double-precision fractional values (IEEE 754). Automatically inferred from float or double.

SDL
type Product {
price: Float
}

Hot Chocolate provides a separate Decimal scalar for decimal values, giving you higher precision than Float.

ID

Represents a unique identifier. The ID type is not automatically inferred. You must annotate it explicitly.

ID values are always serialized as strings in client-server communication, but you can use any CLR type (int, string, Guid) on the server side. This allows you to change the underlying type without affecting the schema or your clients.

C#
// Types/Product.cs
public sealed class Product
{
[GraphQLType<IdType>]
public int Id { get; set; }
}
// Types/ProductQueries.cs
[QueryType]
public static partial class ProductQueries
{
public static Product GetProduct([GraphQLType<IdType>] int id)
{
// Omitted code for brevity
}
}

Learn more about explicit types

Extended Scalars

Beyond the five spec scalars, Hot Chocolate provides these scalars out of the box:

TypeDescription
AnyRepresents any valid GraphQL value
Base64StringBase64-encoded byte array
ByteSigned 8-bit integer
DateDate in UTC
DateTimeDate and time with time zone offset
DecimalHigh-precision decimal floating-point number
DurationDuration of time
LocalDateDate without time or time zone
LocalDateTimeDate and time without time zone
LocalTimeTime of day without date or time zone
LongSigned 64-bit integer
ShortSigned 16-bit integer
UnsignedByteUnsigned 8-bit integer
UnsignedIntUnsigned 32-bit integer
UnsignedLongUnsigned 64-bit integer
UnsignedShortUnsigned 16-bit integer
URIUniform resource identifier (RFC 3986)
UUIDUniversally unique identifier (RFC 9562)

DateTime Configuration

HotChocolate.Types.DateTimeOptions configures the built-in BCL-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 7.
  • ValidateInputFormat controls whether input is validated against the expected scalar format before parsing.

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.

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

C#
// Program.cs
builder
.AddGraphQL()
.AddType(new DateTimeType(new DateTimeOptions
{
OutputPrecision = 3
}))
.AddType(new LocalDateTimeType(new DateTimeOptions
{
OutputPrecision = 3
}))
.AddType(new LocalTimeType(new DateTimeOptions
{
OutputPrecision = 3
}));

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#
// Program.cs
builder
.AddGraphQL()
.AddType(new UuidType('N'));

The 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#
// Types/MetadataQueries.cs
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#
// Program.cs
builder
.AddGraphQL()
.AddJsonTypeConverter();

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

C#
// Types/MetadataQueries.cs
[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#
// Program.cs
builder
.AddGraphQL()
.AddTypeConverter<TimeZoneInfo, JsonElement>(
value => JsonSerializer.SerializeToElement(value.Id));

The resolver can then return the type directly:

C#
// Types/SettingsQueries.cs
[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#
// Types/UserQueries.cs
[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#
// Program.cs
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#
// Program.cs
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.

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#
// Program.cs
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
}))
.AddType(new NodaTimeLocalDateTimeType(new NodaTimeDateTimeOptions
{
OutputPrecision = 3
}))
.AddType(new NodaTimeLocalTimeType(new NodaTimeDateTimeOptions
{
OutputPrecision = 3
}));

Binding Behavior

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

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

You can also bind scalars to arrays or complex types:

C#
// Program.cs
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#
// Types/ScheduleQueries.cs
public sealed class ScheduleQueries
{
public OffsetDateTime GetDateTime(OffsetDateTime offsetDateTime)
{
return offsetDateTime;
}
}
C#
// Program.cs
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#
// Types/CreditCardNumberType.cs
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#
// Types/TcpPortType.cs
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#
// Types/HexColorType.cs
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#
// Program.cs
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 April 13, 2026 by Michael Staib