Fragments

Fragments are reusable, type-safe field selections for GraphQL types. They define how to select and structure fields from your schema.

How soda-gql Fragments Differ from GraphQL

In standard GraphQL, fragments are defined as strings within your query documents:

fragment UserFields on User {
  id
  name
  email
}

soda-gql takes a different approach:

AspectGraphQLsoda-gql
DefinitionString-based, inside .graphql filesTypeScript functions with full IDE support
Type SafetyRequires external codegenBuilt-in type inference
VariablesNot supported in standard GraphQL fragmentsFirst-class support with $var
Composition...FragmentName spread syntax.embed() method with typed variable passing
IDE SupportLimited (depends on tooling)Full autocomplete, go-to-definition, refactoring
TIP

soda-gql fragments with variables are similar to Relay's @argumentDefinitions directive, but work without any GraphQL server extensions.

Defining a Fragment

Use the gql.default() pattern to define a fragment:

import { gql } from "@/graphql-system";

export const userFragment = gql.default(({ fragment }) =>
  fragment.User({}, ({ f }) => [
    //
    f.id(),
    f.name(),
    f.email(),
  ]),
);

The fragment.User call specifies the GraphQL type this fragment applies to. The field builder (f) provides type-safe access to all fields defined on that type.

Field Selection API

Basic Fields

Select scalar fields directly:

f.id()      // Select the id field
f.name()    // Select the name field

Fields with Arguments

Pass arguments as an object:

f.posts({ limit: 10, offset: 0 })
f.avatar({ size: "LARGE" })

Nested Selections

For fields that return object types, use curried syntax to select nested fields:

f.posts({ limit: 10 })(({ f }) => [
  //
  f.id(),
  f.title(),
  f.author()(({ f }) => [
    //
    f.id(),
    f.name(),
  ]),
])

Field Aliases

Rename fields in the response using the alias option:

f.id(null, { alias: "userId" })
f.name(null, { alias: "displayName" })

Fragment Variables

Unlike standard GraphQL fragments, soda-gql fragments can declare their own variables:

export const userFragment = gql.default(({ fragment }, { $var }) =>
  fragment.User(
    { variables: [$var("userId").scalar("ID:!")] },
    ({ f, $ }) => [
      //
      f.user({ id: $.userId })(({ f }) => [
        //
        f.id(),
        f.name(),
        f.email(),
      ]),
    ],
  ),
);

Variables are declared in an array using $var(). The variable reference ($) provides typed access to these variables within field arguments.

Embedding Fragments

Embed fragments in other fragments or operations using .embed():

export const postFragment = gql.default(({ fragment }) =>
  fragment.Post({}, ({ f }) => [
    //
    f.id(),
    f.title(),
    f.author()(({ f }) => [
      //
      userFragment.embed({ includeEmail: false }),
    ]),
  ]),
);

When embedding a fragment with variables, pass the values through the first argument:

// Parent operation with its own variable
export const getPostQuery = gql.default(({ query }, { $var }) =>
  query.operation(
    {
      name: "GetPost",
      variables: [
        //
        $var("postId").scalar("ID:!"),
        $var("showEmail").scalar("Boolean:?"),
      ],
    },
    ({ f, $ }) => [
      //
      f.post({ id: $.postId })(({ f }) => [
        //
        f.id(),
        f.title(),
        f.author()(({ f }) => [
          //
          // Pass parent variable to embedded fragment
          userFragment.embed({ includeEmail: $.showEmail }),
        ]),
      ]),
    ],
  ),
);

Type Inference

Extract TypeScript types from fragments using $infer:

// Input type (variables required to use this fragment)
type UserInput = typeof userFragment.$infer.input;
// { includeEmail?: boolean }

// Output type (shape of selected fields)
type UserOutput = typeof userFragment.$infer.output;
// { id: string; name: string; email?: string }

This is useful for typing function parameters or component props:

function UserCard({ user }: { user: typeof userFragment.$infer.output }) {
  return <div>{user.name}</div>;
}

Extending Fragments with attach()

The attach() method allows adding custom properties to fragments. This is useful for colocating related functionality:

import type { GqlElementAttachment } from "@soda-gql/core";

export const userFragment = gql
  .default(({ fragment }) =>
    fragment.User({}, ({ f }) => [
      //
      f.id(),
      f.name(),
    ]),
  )
  .attach({
    name: "displayName",
    createValue: (element) => (user: typeof element.$infer.output) =>
      user.name.toUpperCase(),
  });

// Use the attached function
const formatted = userFragment.displayName(userData);

For colocation patterns, see the Fragment Colocation guide.

Next Steps