Sometimes an application may need to allow users and third-party extensions to create non-predefined settings, where their type could be any of the GraphQL built-in types (String, Int, Boolean, Float and ID) or even a custom scalar type (such as DateTime), and this value is dynamically created and stored in the database.

Because we don't know in advance the name of these settings, or what type they will have, we are then unable to create their corresponding fields on the GraphQL schema.

type Root {
  foo: String
  bar: String
  baz: Int
}

Then, how can we provide support for dynamic settings in GraphQL?

Creating a single option field

A possibility is to create a single option field which, given the name of the settings (for any settings), fetches its value. Ideally, this field could be a Union type of scalars to cover all possibilities, like this:

type ScalarUnion = String | Int | Boolean | Float | DateTime

type Root {
  option(name: String!): ScalarUnion
}

However, this is currently not possible, because the GraphQL spec does not allow to use scalars in union types. The reason is that scalars do not have a __typename field (or any field, for that matter) that would enable to identify what is the type of the object retrieved via a union, leading to ambiguity in some cases.

For instance, in this example, we couldn't know if the type of scalarUnionResult is of type String or DateTime:

{
  "data": {
    "scalarUnionResult": "2021-01-01T00:00:00Z"
  }
}

Using a Mixed custom scalar type

Another solution that comes to mind is a hack: to use a Mixed custom scalar type, where Mixed behaves like ScalarUnion in that it can retrieve values of any scalar type:

scalar Mixed

type Root {
  option(name: String!): Mixed
}

We assign aliases to the fields to recreate their settings name. For instance, executing this query:

query {
  siteURL: option(name: "home")
  siteName: option(name: "blogname")
  siteDescription: option(name: "blogdescription")
}

... produces this response:

{
  "data": {
    "siteURL": "https://newapi.getpop.org",
    "siteName": "PoP API Demo",
    "siteDescription": "Just another WordPress site"
  }
}

If this approach works, why is it then a hack?

The implementation of this strategy can work well on the server-side, where we know the expected behavior of this custom scalar, and can have the resolver coerce the field's value to the desired type without any problem.

However, this strategy will break the strong typing behavior from GraphQL on the client-side, because tools will not know that Mixed and String (or Boolean or Int) can be used interchangeably.

For instance, this query has input variable of type Mixed, and its value is provided as a String:

query EchoExportedValue($_postTitle: String! = "") {
  post(id: 1) {
    title @export(as: "_postTitle")
  }
  exportedValue: echoVar(variable: $_postTitle)
}

The GraphQL server can resolve the query without any problem, however GraphiQL will permanently show an error, indicating that there is a type mismatch. And this little error is very, very annoying.

GraphiQL error showing a type mismatch

Summary

Fortunately, the GraphQL Working Group is working on a solution to this overall problem. It is first tackling union of scalars as inputs, under RFC: GraphQL Input Union. Since this RFC is currently under active development, we can expect it to be resolved in the foreseeable future.

Once that happens, this RFC can offer a path forward to supporting union of scalars also for outputs. This development would then enable field option to return different scalar types without breaking clients.

Until then, the Mixed custom scalar hack will have to do.


Editor's note: We thank Leo for another interesting hot tip for working with GraphQL. To learn about StepZen's custom directives and features that help you build GraphQL APIs, see the StepZen docs.