GraphQL Integration
Learn more about the Sentry GraphQL (sentry_link) integration for the Flutter SDK.
The sentry_link integration adds Sentry instrumentation to GraphQL clients built on the gql ecosystem.
It helps you capture:
- Link exceptions (transport/client failures such as network and parsing issues)
- GraphQL response errors (entries in
response.errors) - Tracing spans for queries, mutations, and subscriptions
- Breadcrumbs for successful GraphQL operations (operation name and duration)
- Request context (operation name, query, variables, and response data) attached to captured events
sentry_link works with the gql ecosystem and is commonly used with:
Other clients built on gql packages generally work too.
Add sentry_link and your GraphQL client dependency:
pubspec.yamldependencies:
sentry: ^9.24.0
sentry_link: ^9.24.0
graphql: ^5.1.3
dependencies:
sentry: ^9.24.0
sentry_link: ^9.24.0
graphql: ^5.1.3
After you initialize Sentry in your app, add SentryGql.link() to your GraphQL client:
Place SentryGql.link Before Your Terminating Link
SentryGql.link() must be placed before your terminating link (HttpLink, DioLink, etc.), since it works by wrapping the downstream request.
For the widest coverage, place SentryGql.link() as the first link in the chain. That way it captures errors and traces time spent in any middleware (for example, AuthLink) as well as in the terminating link.
import 'package:graphql/client.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry_link/sentry_link.dart';
final link = Link.from([
SentryGql.link(
shouldStartTransaction: false,
graphQlErrorsMarkTransactionAsFailed: false,
),
// Add any middleware links (for example, AuthLink) here.
HttpLink(
'https://your-graphql-endpoint.com/graphql',
httpClient: SentryHttpClient(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
final client = GraphQLClient(
cache: GraphQLCache(),
link: link,
);
import 'package:graphql/client.dart';
import 'package:sentry/sentry.dart';
import 'package:sentry_link/sentry_link.dart';
final link = Link.from([
SentryGql.link(
shouldStartTransaction: false,
graphQlErrorsMarkTransactionAsFailed: false,
),
// Add any middleware links (for example, AuthLink) here.
HttpLink(
'https://your-graphql-endpoint.com/graphql',
httpClient: SentryHttpClient(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
final client = GraphQLClient(
cache: GraphQLCache(),
link: link,
);
For better error context and grouping, add the recommended init options from Advanced Configuration.
| Parameter | Type | Default | Description |
|---|---|---|---|
shouldStartTransaction | bool | required | Set to true to start a transaction per GraphQL operation when no active span/transaction exists. |
graphQlErrorsMarkTransactionAsFailed | bool | required | Set to true to mark GraphQL spans/transactions as unknownError when response.errors exists. |
enableBreadcrumbs | bool | true | Records breadcrumbs for successful GraphQL operations. |
reportExceptions | bool | true | Captures LinkException failures as Sentry events. |
reportExceptionsAsBreadcrumbs | bool | false | Records LinkException failures as breadcrumbs instead of events. |
reportGraphQlErrors | bool | true | Captures GraphQL response errors as Sentry events. |
reportGraphQlErrorsAsBreadcrumbs | bool | false | Records GraphQL response errors as breadcrumbs instead of events. |
sentry_link reports two different failure layers:
- Link exceptions (
reportExceptions*): transport/client-side failures from the link chain, such asServerException,NetworkException, and parser/serialization failures. - GraphQL response errors (
reportGraphQlErrors*): application-layer errors returned inresponse.errors(for example, resolver, validation, or authorization errors).
Capturing transactions requires that you first set up tracing if you haven't already.
When you want GraphQL transactions and spans, enable tracing in your SDK initialization and start transactions in SentryGql.link():
import 'package:graphql/client.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
options.dsn = '___PUBLIC_DSN___';
options.tracesSampleRate = 1.0;
});
final link = Link.from([
SentryGql.link(
shouldStartTransaction: true,
graphQlErrorsMarkTransactionAsFailed: true,
),
HttpLink(
'https://your-graphql-endpoint.com/graphql',
httpClient: SentryHttpClient(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
final client = GraphQLClient(cache: GraphQLCache(), link: link);
import 'package:graphql/client.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
options.dsn = '___PUBLIC_DSN___';
options.tracesSampleRate = 1.0;
});
final link = Link.from([
SentryGql.link(
shouldStartTransaction: true,
graphQlErrorsMarkTransactionAsFailed: true,
),
HttpLink(
'https://your-graphql-endpoint.com/graphql',
httpClient: SentryHttpClient(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
final client = GraphQLClient(cache: GraphQLCache(), link: link);
Tracing behavior:
- Span descriptions follow
GraphQL: "{operationName}" {type}, for exampleGraphQL: "LoadPosts" query. - Span operations are
http.graphql.query,http.graphql.mutation, andhttp.graphql.subscription. - If no active transaction exists and
shouldStartTransactionistrue, the SDK creates one automatically. - Span status is set as follows:
- Success →
SpanStatus.ok() response.errorspresent andgraphQlErrorsMarkTransactionAsFailedistrue→SpanStatus.unknownError()response.errorspresent andgraphQlErrorsMarkTransactionAsFailedisfalse→SpanStatus.ok()- A thrown
LinkException→SpanStatus.unknownError()(regardless of flag values)
- Success →
SentryRequestSerializerandSentryResponseParseradd child spans with operationserialize.http.clientfor request serialization and response parsing.
For better context and issue grouping, we recommend this initialization setup:
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
// Filter duplicate HTTP breadcrumbs for GraphQL requests.
options.beforeBreadcrumb = graphQlFilter();
// Preserve nested LinkException causes for better error context.
options.addGqlExtractors();
// Improve stack trace grouping by excluding sentry_link internals.
options.addSentryLinkInAppExcludes();
});
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
// Filter duplicate HTTP breadcrumbs for GraphQL requests.
options.beforeBreadcrumb = graphQlFilter();
// Preserve nested LinkException causes for better error context.
options.addGqlExtractors();
// Improve stack trace grouping by excluding sentry_link internals.
options.addSentryLinkInAppExcludes();
});
If you use Dio, replace HttpLink with DioLink and use sentry_dio:
import 'package:dio/dio.dart';
import 'package:gql_link/gql_link.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_link/sentry_link.dart';
import 'package:gql_dio_link/gql_dio_link.dart';
final link = Link.from([
SentryGql.link(
shouldStartTransaction: true,
graphQlErrorsMarkTransactionAsFailed: true,
),
DioLink(
'https://your-graphql-endpoint.com/graphql',
client: Dio()..addSentry(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
import 'package:dio/dio.dart';
import 'package:gql_link/gql_link.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_link/sentry_link.dart';
import 'package:gql_dio_link/gql_dio_link.dart';
final link = Link.from([
SentryGql.link(
shouldStartTransaction: true,
graphQlErrorsMarkTransactionAsFailed: true,
),
DioLink(
'https://your-graphql-endpoint.com/graphql',
client: Dio()..addSentry(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
You can either disable HTTP breadcrumbs globally or filter only GraphQL HTTP duplicates. For targeted filtering, set beforeBreadcrumb with your own graphQlFilter() callback:
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
options.beforeBreadcrumb = graphQlFilter((breadcrumb, hint) {
// Add your custom filtering or mutation logic here.
return breadcrumb;
});
});
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
options.beforeBreadcrumb = graphQlFilter((breadcrumb, hint) {
// Add your custom filtering or mutation logic here.
return breadcrumb;
});
});
LinkException instances can contain nested causes. Add GraphQL extractors in your SDK initialization options callback so Sentry preserves that exception chain:
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
options.addGqlExtractors();
});
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
options.addGqlExtractors();
});
To keep sentry_link internals out of in-app stack frames, add:
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
options.addSentryLinkInAppExcludes();
});
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_link/sentry_link.dart';
await SentryFlutter.init((options) {
// ... your existing Sentry options
options.addSentryLinkInAppExcludes();
});
Run a query with an intentional schema mistake (for example, misspelling a field):
import 'package:sentry/sentry.dart';
import 'package:sentry_link/sentry_link.dart';
import 'package:graphql/client.dart';
final link = Link.from([
SentryGql.link(
shouldStartTransaction: false,
graphQlErrorsMarkTransactionAsFailed: false,
),
HttpLink(
'https://your-graphql-endpoint.com/graphql',
httpClient: SentryHttpClient(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
final client = GraphQLClient(cache: GraphQLCache(), link: link);
final result = await client.query(
QueryOptions(
operationName: 'LoadPosts',
document: gql(r'''
query LoadPosts($id: ID!) {
post(id: $id) {
id
titl
body
}
}
'''),
variables: {'id': 50},
),
);
import 'package:sentry/sentry.dart';
import 'package:sentry_link/sentry_link.dart';
import 'package:graphql/client.dart';
final link = Link.from([
SentryGql.link(
shouldStartTransaction: false,
graphQlErrorsMarkTransactionAsFailed: false,
),
HttpLink(
'https://your-graphql-endpoint.com/graphql',
httpClient: SentryHttpClient(),
serializer: SentryRequestSerializer(),
parser: SentryResponseParser(),
),
]);
final client = GraphQLClient(cache: GraphQLCache(), link: link);
final result = await client.query(
QueryOptions(
operationName: 'LoadPosts',
document: gql(r'''
query LoadPosts($id: ID!) {
post(id: $id) {
id
titl
body
}
}
'''),
variables: {'id': 50},
),
);
Open your project in sentry.io:
- In Issues, confirm a GraphQL event includes operation name, query, variables, and response details.
If you enabled tracing in Enable Tracing, also verify:
- In Performance, confirm a
GraphQL: "LoadPosts" querytransaction/span with GraphQL and serialization/parsing timing data.
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").