Metadata

Metadata allows you to attach runtime information to operations. This is useful for HTTP headers and application-specific configuration.

How soda-gql Metadata Differs from GraphQL

Standard GraphQL has limited support for request-level metadata:

AspectGraphQLsoda-gql
Request HeadersHandled outside the queryDeclared with the operation
Custom DataNot standardizedFirst-class custom property
Type SafetyNoneFull TypeScript inference

Metadata Structure

All metadata has two base properties:

PropertyTypePurpose
headersRecord<string, string>HTTP headers to include with the request
customRecord<string, unknown>Application-specific values

Defining Metadata

Add metadata to an operation using the metadata option:

export const getUserQuery = gql.default(({ query, $var }) =>
  query.operation({
    name: "GetUser",
    variables: { ...$var("id").ID("!") },
    metadata: ({ $, document }) => ({
      headers: {
        "X-Request-ID": "get-user-query",
      },
      custom: {
        requiresAuth: true,
        cacheTtl: 300,
      },
    }),
    fields: ({ f, $ }) => ({
      ...f.user({ id: $.id })(({ f }) => ({
        ...f.id(),
        ...f.name(),
      })),
    }),
  }),
);

Metadata Callback Parameters

The metadata callback receives:

ParameterDescription
$Variable references (same as in field selections)
documentThe compiled GraphQL document string
$varHelper for accessing variable inner values

$var Helper Methods

The $var object provides methods to inspect VarRef values:

MethodDescription
$var.getName(ref)Get the variable name from a VarRef
$var.getValue(ref)Get the const value from a VarRef
$var.getInner(ref)Get the raw inner structure of a VarRef
$var.getNameAt(ref, selector)Get variable name at a path in nested structure
$var.getValueAt(ref, selector)Get const value at a path in nested structure
$var.getVariablePath(ref, selector)Get path segments to a variable

Dynamic Metadata

Use variables to create dynamic metadata:

metadata: ({ $, $var }) => ({
  headers: {
    "X-Trace-ID": `user-${$var.getName($.id)}`,
  },
  custom: {
    trackedVariables: [$var.getName($.id)],
  },
}),

Accessing Nested Input Values

When a variable is defined with an input type (like String_comparison_exp), you can extract specific fields from it using getValueAt:

gql.default(({ fragment, $var }) =>
  fragment.Query({
    variables: {
      ...$var("userId").ID("!"),
      ...$var("categoryId").String_comparison_exp("?"),
    },
    metadata: ({ $ }) => ({
      custom: {
        // Extract the _eq field from the comparison expression
        categoryId: $var.getValueAt($.categoryId, (p) => p._eq),
      },
    }),
    fields: ({ f, $ }) => ({
      ...f.user({ id: $.userId })(({ f }) => ({
        ...f.posts({})(({ f }) => ({
          ...f.id(),
          ...f.title(),
        })),
      })),
    }),
  }),
);

This is useful when you need to access a specific field from a complex input type variable for metadata purposes (e.g., logging, caching keys, or custom headers).

Use Cases

Authentication Headers

Attach authorization headers that your GraphQL client can use:

metadata: () => ({
  headers: {
    "Authorization": "Bearer ${token}",
  },
  custom: {
    requiresAuth: true,
    authScopes: ["user:read"],
  },
}),

Request Tracing

Add trace information for debugging:

metadata: ({ document }) => ({
  headers: {
    "X-Operation-Name": "GetUser",
    "X-Document-Hash": hashDocument(document),
  },
  custom: {
    tracing: true,
  },
}),

Cache Control

Define caching behavior for your application:

metadata: () => ({
  headers: {},
  custom: {
    cache: {
      ttl: 3600,
      staleWhileRevalidate: true,
      tags: ["user", "profile"],
    },
  },
}),

Feature Flags

Control operation behavior based on features:

metadata: () => ({
  headers: {},
  custom: {
    features: {
      enableNewUserFields: true,
      useOptimizedResolver: false,
    },
  },
}),

Accessing Metadata

After defining metadata, access it through the operation:

// Get metadata for a specific variable set
const meta = getUserQuery.metadata({ id: "123" });

console.log(meta.headers);    // { "X-Request-ID": "get-user-query" }
console.log(meta.custom);     // { requiresAuth: true, cacheTtl: 300 }

Integrating with GraphQL Clients

Use metadata in your GraphQL client implementation:

async function graphqlClient<T>(operation: {
  document: string;
  variables: Record<string, unknown>;
  metadata?: (vars: Record<string, unknown>) => Metadata;
}) {
  const meta = operation.metadata?.(operation.variables) ?? {};

  const response = await fetch("/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...meta.headers,
    },
    body: JSON.stringify({
      query: operation.document,
      variables: operation.variables,
    }),
  });

  return response.json();
}

Type Inference

Metadata types are inferred from your definition:

type QueryMeta = typeof getUserQuery.$infer.metadata;
// {
//   headers: { "X-Request-ID": string };
//   custom: { requiresAuth: boolean; cacheTtl: number };
// }

Document Transformation

For build-time AST transformation based on metadata, see the Adapter Guide. Document transforms can:

  • Add directives based on metadata (e.g., @cached, @auth)
  • Modify field selections
  • Inject tracing or debugging information

Next Steps