Reducing Payload Bloat with GraphQL @skip and @include Directives
Learn how to use GraphQL @skip and @include directives to conditionally fetch data, reducing payload size and simplifying client‑side UI logic for different user roles.
11 Dec 2025, 03:09 UTC

The Problem: Over-fetching for Conditional UIs
Modern frontends often handle multiple user roles or feature flags within a single view. For example, a user profile page might show basic information to everyone, but reveal administrative controls and internal IDs only to admins. Traditionally, developers solve this by creating multiple query variants—one for the guest and one for the admin—or by fetching everything and filtering the data in the browser.
Fetching everything leads to over-fetching, which increases latency and wastes bandwidth. Maintaining multiple queries leads to duplication and synchronization errors. The goal is to have a single, declarative query that adapts its response based on the client’s current state without requiring schema changes on the server.
The solution lies in GraphQL’s built-in conditional directives: @include and @skip.
How Conditional Directives Work
Directives are markers that can be attached to fields or fragments to change the execution behavior of a query. Both @include and @skip take a single required boolean argument, typically passed as a variable from the client.
@include(if: Boolean): The field is included in the result only if the argument istrue.@skip(if: Boolean): The field is omitted from the result if the argument istrue.
These are logically inverse operations. While you can use either, @include is generally more intuitive for "opt‑in" features (like admin panels), while @skip is useful for "opt‑out" scenarios (like hiding a default header in a specific view).
Worked Example: Conditional Admin Data
Consider a schema where a User type contains a public username and a sensitive role field. We want to fetch the role only when the client‑side application confirms the user has the necessary permissions.
The Query:
query GetUserProfile($userId: ID!, $showAdmin: Boolean!) {
user(id: $userId) {
username
role @include(if: $showAdmin)
}
}
Execution Context: Run this query from your GraphQL client (e.g., Apollo, Relay, or GraphiQL) with the following variables:
Scenario A: User is not an admin
{
"userId": "user_123",
"showAdmin": false
}
Expected Result: The role field is entirely absent from the JSON response, reducing the payload size.
Scenario B: User is an admin
{
"userId": "user_123",
"showAdmin": true
}
Expected Result: The role field is returned along with the username.
Engineering Trade-offs and Limitations
While these directives simplify client‑side logic, they introduce specific architectural considerations:
- Resolver Execution: In a standard GraphQL implementation, if a field is skipped via a directive, the server should not execute the resolver for that field. However, this depends on the server implementation. If you have custom middleware or a non‑standard execution engine, verify that the resolver is actually bypassed to avoid unnecessary database hits.
- Query Complexity: As you add more conditional fields, the query string becomes harder to read. To manage this, group conditional fields into fragments and apply the directive to the fragment spread rather than individual fields.
- Validation: The server still validates the entire query against the schema regardless of the boolean value. You cannot use directives to "hide" fields that the user doesn’t have permission to access at a schema level; authorization must still be handled inside the resolver.
Practical Verification
To verify that your server is correctly handling these directives and not performing "ghost" work, follow these steps:
- Add a
console.logor a trace point inside the resolver for a field marked with@include. - Execute the query with the variable set to
false. - Check the server logs. If the resolver log appears despite the field being missing from the JSON response, your server is over‑working, and you may need to optimize your execution layer.
Closing Recommendation
Use @include and @skip to synchronize your data fetching with your UI state. This prevents the "all‑or‑nothing" approach to data fetching and keeps your API requests lean. To maintain readability, limit the number of conditional variables per query and lean on fragments for complex UI components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.