Data Masking β
Data masking prevents components from accessing GraphQL fields they didn't explicitly request. This creates loosely coupled components that are more resistant to breaking changes.
Recommended: Use with GraphQL Codegen
Data masking works best with GraphQL Codegen for type-safe masked types. See the TypeScript page for setup instructions, including the required type augmentation to make masked types work correctly.
The Problem β
Consider a parent component that fetches posts and a child component that displays post details:
const GET_POSTS = gql`
query GetPosts {
posts {
id
...PostDetailsFragment
}
}
${POST_DETAILS_FRAGMENT}
`
const { current } = useQuery(GET_POSTS)
// Filter by publishedAt (defined in PostDetailsFragment)
const published = current.value.result?.posts.filter(post => post.publishedAt)export const POST_DETAILS_FRAGMENT = gql`
fragment PostDetailsFragment on Post {
title
publishedAt
}
`If PostDetails removes publishedAt from its fragment (because it no longer displays it), the parent component breaks silently. This implicit dependency between components becomes harder to track as applications grow.
Enabling Data Masking β
Enable data masking in the Apollo Client constructor:
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://api.example.com/graphql' }),
cache: new InMemoryCache(),
dataMasking: true, // Enable data masking
})With data masking enabled, fields defined in fragments are hidden from components that don't own them. The parent component can only access fields it explicitly requests.
Reading Masked Data β
Use useFragment to read masked fragment data in components:
<script setup lang="ts">
import { useFragment } from '@vue/apollo-composable'
const {
post
} = defineProps<{
post: { __typename: 'Post', id: string }
}>()
const { current } = useFragment({
fragment: POST_DETAILS_FRAGMENT,
from: () => post,
})
</script>
<template>
<div v-if="current.resultState === 'complete'">
<h2>{{ current.result.title }}</h2>
<p>{{ current.result.publishedAt }}</p>
</div>
</template>The data from useFragment contains only the fields defined in the fragmentβnot fields from parent queries or sibling fragments.
Fixing the Parent Component β
With data masking, the parent component must explicitly request any fields it needs:
const GET_POSTS = gql`
query GetPosts {
posts {
id
publishedAt # Now explicit - won't break if fragment changes
...PostDetailsFragment
}
}
${POST_DETAILS_FRAGMENT}
`Now if PostDetails removes publishedAt from its fragment, the parent query still works because it requests the field directly.
The @unmask Directive β
Use @unmask to selectively disable masking for specific fragments:
query GetPosts {
posts {
id
...PostDetailsFragment @unmask
}
}Use sparingly
The @unmask directive is an escape hatch. Prefer adding needed fields to the parent query explicitly. @unmask is primarily useful during incremental adoption.
Migrate Mode β
During migration, use @unmask(mode: "migrate") to get development warnings when accessing would-be masked fields:
query GetPosts {
posts {
id
...PostDetailsFragment @unmask(mode: "migrate")
}
}This logs warnings in development when you access fields that would be masked, helping you identify implicit dependencies.
What Gets Masked β
Data masking applies to all operation types that read data:
| API | Masked |
|---|---|
useQuery result | Yes |
useMutation result | Yes |
useSubscription result | Yes |
useMutation update callback | No |
useMutation refetchQueries callback | No |
subscribeToMore updateQuery callback | No |
Cache APIs (readQuery, readFragment) | No |
Incremental Adoption β
For existing applications, adopt data masking incrementally:
1. Add @unmask to All Fragments β
Before enabling data masking, add @unmask(mode: "migrate") to all fragment spreads to prevent breaking changes:
query GetPosts {
posts {
id
...PostDetailsFragment @unmask(mode: "migrate")
}
}2. Enable Data Masking β
const client = new ApolloClient({
dataMasking: true,
// ...
})3. Configure TypeScript (if applicable) β
If you're using TypeScript with GraphQL Codegen, you need to:
- Update your codegen config to generate masked types (see TypeScript setup)
- Create the type augmentation file (see Enabling Data Masking Types)
4. Refactor Components β
Gradually refactor components to use useFragment and remove @unmask directives:
- Look for console warnings about accessing masked fields
- Update components to use
useFragmentfor their fragment data - Add any needed fields explicitly to parent queries
- Remove
@unmaskwhen no warnings remain
Next Steps β
- Fragments - Learn about GraphQL fragments and
useFragment - TypeScript - Set up GraphQL Codegen for type-safe data masking