Skip to content

Fragments

Specify the data requirements for a component.

Thinking In Houdini: Fragments are your building blocks

Rather than leaving it up to coincidence that the right fields happen to be in the route’s query, we give each component its own fragment, and the parent query just spreads them in. As a component library grows, this keeps the coupling loose: adding a field to UserAvatar means updating UserAvatar’s fragment, not hunting down every query that renders it.

Think of fragments as the primary way to describe component data requirements in Houdini. A UserAvatar that starts out needing just initials can quietly grow to need an email and a favorite color without touching any of its callers.

Basic Usage

<script>
import { fragment, graphql } from '$houdini'
import type { UserAvatar } from '$houdini'
let { user }: { user: UserAvatar } = $props()
const data = $derived(fragment(user, graphql(`
fragment UserAvatar on User {
profilePicture
}
`)))
</script>
<img src={$data.profilePicture} />

Inline Fragments

Inline fragments are defined using fragment imported from $houdini.

Inputs

  1. The prop containing the fragment reference (passed by a parent component that mixed in the fragment)
  2. A string tagged with graphql containing a single fragment document

Return Value

fragment returns a store containing the fragment values. The store updates automatically as mutations, subscriptions, and other queries bring in new data.

External Documents

Fragment stores can also be created from external .gql files by using the .get method on the generated store class:

<script>
import { UserAvatarStore } from '$houdini'
import type { UserAvatar } from '$houdini'
let { user }: { user: UserAvatar } = $props()
const data = $derived(new UserAvatarStore().get(user))
</script>
<img src={$data.profilePicture} />

Fragment Arguments

In some situations it’s necessary to configure the documents inside of a fragment. For example, you might want to extend the UserAvatar component to allow for different sized profile pictures. To support this, houdini provides two directives @arguments and @with which declare arguments for a fragment and provide values, respectively.

Default values can be provided to fragment arguments with the default key:

fragment UserAvatar on User @arguments(width: { type: "Int", default: 50 }) {
profilePicture(width: $width)
}

In order to mark an argument as required, pass the type with a ! at the end. If no value is provided, an error will be thrown when generating your runtime.

fragment UserAvatar on User @arguments(width: { type: "Int!" }) {
profilePicture(width: $width)
}

Providing values for fragments is done with the @with decorator:

query AllUsers {
users {
...UserAvatar @with(width: 100)
}
}

If you are using fragment variables inside of a field flagged for list operations, you’ll have to pass a value for the variable when performing the operation.

For paginated fragments, see the Pagination guide.

Plural Fragments

A fragment usually describes a single record, so fragment hands back a store of one object. When the fragment is spread on a list field, though, the parent gives us an array of references instead. Wiring up a store for each item by hand gets tedious fast.

The @plural directive marks a fragment as list shaped. The reference becomes an array and the resulting store holds an array of data, so one component can render the whole list:

<script>
import { fragment, graphql } from '$houdini'
import type { UserListRow } from '$houdini'
let { users }: { users: UserListRow } = $props()
const data = $derived(fragment(users, graphql(`
fragment UserListRow on User @plural {
id
name
}
`)))
</script>
<ul>
{#each $data as user}
<li>{user.name}</li>
{/each}
</ul>

The parent spreads the fragment inside the list field and passes the whole list down:

query AllUsers {
users {
...UserListRow
}
}

A @plural fragment has to be spread on a list field, since that’s the only place the array of references can come from. Spreading it anywhere else is a codegen error.

Refetchable Fragments

Sometimes a component needs to reload its own data with different arguments without dragging the route that rendered it into the conversation. Mark the fragment with @refetchable and load it with refetchableFragment instead of fragment:

<script>
import { refetchableFragment, graphql } from '$houdini'
import type { UserInfo } from '$houdini'
let { user }: { user: UserInfo } = $props()
const userInfo = $derived(refetchableFragment(user, graphql(`
fragment UserInfo on User @refetchable @arguments(filter: { type: "String" }) {
friends(filter: $filter) {
name
}
}
`)))
</script>
{#each $userInfo.data.friends as friend}
<div>{friend.name}</div>
{/each}
<button onclick={() => userInfo.refetch({ filter: 'aki' })}>
filter friends
</button>

refetchableFragment returns a store carrying the fragment’s data and variables, plus a refetch method. Calling refetch re-runs the fragment against the network with the arguments we hand it, layered over whatever it was last loaded with, so we only pass the values we actually want to change. The record’s id is figured out for us from the data already on screen.

The initial values come from wherever the fragment is spread, the same as any fragment that takes arguments. The parent passes them with @with:

query UserPage {
user {
...UserInfo @with(filter: "aki")
}
}

The catch is that the fragment has to live on a type Houdini can look up on its own, which in practice means one that implements Node or has a custom resolver configured. That’s the same requirement paginated fragments carry, and for the same reason: under the hood we embed the fragment in a query keyed by the record’s id and re-run that.

@refetchable can’t be combined with @paginate on the same fragment. A paginated fragment is already refetchable on its own, so the two together is a compile-time error.

Fragment Masking

Fragment masking keeps components properly encapsulated by ensuring they can only access the fields they explicitly declared, not fields pulled in by sibling fragments. For a deeper look at why this matters, see the Fragment Colocation guide from gql.tada.

By default fields from a fragment are not included in a query to encourage separation of concerns. You can override this default behavior with the config option defaultFragmentMasking: "disable" for all fragment usages or individually per fragment:

query CurrentUser {
me {
uuid
...UserProfile @mask_disable
...UserMeta
}
}

With this directive in place, you can access all fields from the UserProfile fragment directly on the me object together with uuid. All fields from UserMeta stay masked.

This individual setting does not apply recursively unless the fragments inside UserProfile also carry the directive.