Back to Graphql Platform

Scalars

website/src/docs/hotchocolate/v16/defining-a-schema/scalars.md

16.1.0-p.1.1225.1 KB
Original Source

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.

<ExampleTabs> <Implementation>
csharp
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
    }
}
</Implementation> <Code>
csharp
public sealed class Product
{
    public int Id { get; set; }
}

public sealed class ProductType : ObjectType<Product>
{
    protected override void Configure(IObjectTypeDescriptor<Product> descriptor)
    {
        descriptor.Name("Product");
        descriptor.Field(f => f.Id).ID();
    }
}

public sealed class QueryType : ObjectType
{
    protected override void Configure(IObjectTypeDescriptor descriptor)
    {
        descriptor.Name(OperationTypeNames.Query);

        descriptor
            .Field("product")
            .Argument("id", a => a.ID())
            .Type<ProductType>()
            .Resolve(context =>
            {
                var id = context.ArgumentValue<int>("id");

                // Omitted code for brevity
            });
    }
}
</Code> </ExampleTabs>

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)

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:

csharp
builder
    .AddGraphQL()
    .AddType(new DateTimeType(new DateTimeOptions
    {
        OutputPrecision = 3
    }))
    .AddType(new LocalDateTimeType(new DateTimeOptions
    {
        OutputPrecision = 3
    }))
    .AddType(new LocalTimeType(new DateTimeOptions
    {
        OutputPrecision = 3
    }));
<Video videoId="gO3bNKBmXZM" />

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:

csharp
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:

csharp
JsonElement value = context.ArgumentValue<JsonElement>("filter");

if (value.ValueKind == JsonValueKind.Object)
{
    string? name = value.GetProperty("name").GetString();
}

To deserialize into a strongly typed model:

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

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

csharp
ValueKind kind = context.ArgumentKind("filter");

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

csharp
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:

csharp
builder
    .AddGraphQL()
    .AddJsonTypeConverter();

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

csharp
[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:

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

The resolver can then return the type directly:

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

Additional Scalars Package

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

<PackageInstallation packageName="HotChocolate.Types.Scalars" />
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:

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

Learn more about explicit types

NodaTime Scalars

For NodaTime types, install the dedicated package:

<PackageInstallation packageName="HotChocolate.Types.NodaTime" />

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 TimeSpanNodaTime.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():

csharp
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:

csharp
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():

csharp
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:

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

You can also bind scalars to arrays or complex types:

csharp
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:

csharp
public sealed class ScheduleQueries
{
    public OffsetDateTime GetDateTime(OffsetDateTime offsetDateTime)
    {
        return offsetDateTime;
    }
}
csharp
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:

csharp
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.

csharp
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:

csharp
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.

csharp
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:

csharp
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:

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

Next Steps