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:
| Aspect | GraphQL | soda-gql |
|---|
| Definition | String-based, inside .graphql files | TypeScript functions with full IDE support |
| Type Safety | Requires external codegen | Built-in type inference |
| Variables | Not supported in standard GraphQL fragments | First-class support via ($name: Type!) syntax |
| Composition | ...FragmentName spread syntax | ${fragment} interpolation or .spread() method |
| IDE Support | Limited (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 with tagged template syntax to define a fragment:
import { gql } from "@/graphql-system";
export const userFragment = gql.default(({ fragment }) =>
fragment("UserFragment", "User")`{
id
name
email
}`(),
);
The tagged template specifies the GraphQL type and field selections directly. soda-gql validates the fragment against your schema at build time.
For advanced features like field aliases, you can use the tagged template with alias syntax:
export const userFragment = gql.default(({ fragment }) =>
fragment("UserFragment", "User")`{
userId: id
name
email
}`(),
);
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. In tagged template syntax, declare variables in the template:
export const userFragment = gql.default(({ fragment }) =>
fragment("UserFragment", "Query")`($userId: ID!) {
user(id: $userId) {
id
name
email
}
}`(),
);
Alternatively, the same fragment with variables in tagged template syntax:
export const userFragment = gql.default(({ fragment }) =>
fragment("UserFragment", "Query")`($userId: ID!) {
user(id: $userId) {
id
name
email
}
}`(),
);
Spreading Fragments
In tagged templates, use ${...} interpolation to spread fragments:
export const postFragment = gql.default(({ fragment }) =>
fragment("PostFragment", "Post")`{
id
title
author {
...${userFragment}
}
}`(),
);
For fragments with variables, use a callback function to pass variable bindings:
export const getPostQuery = gql.default(({ query }) =>
query("GetPost")`($postId: ID!, $showEmail: Boolean) {
post(id: $postId) {
id
title
author {
...${({ $ }) => userFragment.spread({ includeEmail: $.showEmail })}
}
}
}`(),
);
In the options-object path, use .spread() directly:
export const getPostQuery = gql.default(({ query }) =>
query("GetPost")({
variables: `($postId: ID!, $showEmail: Boolean)`,
fields: ({ f, $ }) => ({
...f("post", { id: $.postId })(({ f }) => ({
...f("id")(),
...f("title")(),
...f("author")(({ f }) => ({
// Pass parent variable to spread fragment
...userFragment.spread({ 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("UserFragment", "User")`{
id
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.
Fragment Keys
The optional key property gives a fragment a unique identifier. With tagged template syntax, the first argument to fragment() serves as the key:
export const userFragment = gql.default(({ fragment }) =>
fragment("UserFields", "User")`{
id
name
}`(),
);
The first argument to fragment() serves as the key in tagged template syntax:
export const userFragment = gql.default(({ fragment }) =>
fragment("UserFields", "User")`{
id
name
}`(),
);
When Keys Are Required
Fragments work correctly without keys at runtime. However, keys become important when using prebuilt types:
| Scenario | Key Required? |
|---|
| Runtime fragment execution | No |
Type inference with $infer | No |
| Prebuilt type generation | Yes |
Prebuilt Types Requirement
When generating prebuilt types with bun run soda-gql typegen, fragments without keys are silently skipped. If you need a fragment's types in the prebuilt registry, add a key property.
Operations do not need a separate key property - they use their name automatically.
For more details on prebuilt types, see the Prebuilt Types Guide.
Next Steps