Global IDs

Are Base64-encoded, type-safe identifiers for node-based entity retrieval in Viaduct.

Global IDs in Viaduct combines two pieces of information:

  • Type: the GraphQL type name (for example, “Character”, “Film”, “Planet”).
  • Internal ID: your application’s internal identifier for that entity.

Format and encoding

The raw form is "<Type>:<InternalID>", which is then base64-encoded.

// Encoded form for Character with internal ID "1":
val gid: String = Character.Reflection.globalId("1") // "Q2hhcmFjdGVyOjE="

When building objects in resolvers, use the execution context helper to attach a typed Global ID:

viaduct.api.grts.Starship.Builder(ctx)
    .id(ctx.globalIDFor<viaduct.api.grts.Starship>(starship.id))
    .name(starship.name)

Treat Global IDs as opaque. They are intended for retrieval via node queries, not as human-facing identifiers.

Using Global IDs in node resolvers

Node resolvers receive a parsed Global ID; use the internal ID to load the entity:

@Resolver
class CharacterNodeResolver : NodeResolvers.Character() {
// Node resolvers can also be batched to optimize multiple requests
// tag::node_batch_resolver_example[21] Example of a node resolver
override suspend fun batchResolve(contexts: List<Context>): List<FieldValue<Character>> {
    // Extract all unique character IDs from the contexts
    val characterIds = contexts.map { it.id.internalID }

    // Perform a single batch query to get film counts for all characters
    // We only compute one time for each character, despite multiple requests
    val characters = characterIds.mapNotNull {
        CharacterRepository.findById(it)
    }

    // For each context gets the character ID and map to the viaduct object
    return contexts.map { ctx ->
        val characterId = ctx.id.internalID

Client usage via node(id:)

Clients pass a Global ID to retrieve a specific entity, independent of the underlying storage key format:

query ($id: ID!) {
  node(id: $id) {
    ... on Character {
      id
      name
    }
  }
}

Schema hinting with @idOf

Annotate ID fields and arguments with @idOf to bind them to a concrete GraphQL type, enabling type-safe handling in resolvers and tooling:

"""
Search by character ID
"""
byId: ID @idOf(type: "Character")

type Character implements Node @scope(to: ["default"]) @resolver {
"""
The GlobalID of this character - uniquely identifies this Character in the graph (internal use only)
"""
id: ID!

Do and don’t

  • Do treat Global IDs as opaque and stable across the API surface.
  • Do generate them in resolvers using ctx.globalIDFor or <Type>.Reflection.globalId(...).
  • Do use @idOf on schema fields/arguments carrying Global IDs.
  • Don’t expose internal IDs or rely on clients decoding base64.
  • Don’t embed business logic or access control information in IDs.