graphql_codegen 1.2.3 copy "graphql_codegen: ^1.2.3" to clipboard
graphql_codegen: ^1.2.3 copied to clipboard

Simple, opinionated, codegen library for GraphQL. It allows you to generate serializers and client helpers to easily call and parse your data.

GraphQL Codegen #

HELP US SURVIVE, BECOME A SPONSOR! #

This is an opinionated code-generation tool from GraphQL to Dart/Flutter.

It'll allow you to generate Dart serializers and client helpers with minimal config. The framework makes no assumption on how you structure your fragments or queries, for each operation.graphql the framework will generate a operation.graphql.dart file containing dart classes.

Read more about the tool and motivation at the GraphQL Codegen deep-dive and on how you can structure your flutter apps with the tool on Structure your Flutter GraphQL apps.

The framework does not fetch your schema for you, so before you run this, you'll need to add your schema to your project. In Android Studio this can be done with the GraphQL plugin.

Installation #

Dev dependencies #

  • build_runner generates files from dart code. Read more here
$ flutter pub add --dev graphql_codegen build_runner
copied to clipboard

Dependencies #

  • graphql (optional) to use generated types with graphql. See options

  • graphql_flutter (optional) to use generated types with graphql_flutter. See options

  • flutter_hooks (optional) to use generated operations hooks. Will be inside HookWidgets

$ flutter pub add graphql graphql_flutter flutter_hooks
copied to clipboard

Basic Usage #

To generate dart classes from GraphQL schema, firstly you have to create a schema.graphql file and GraphQL document files.

For instance:

Given schema

# ./lib/schema.graphql

type Query {
  fetch_person(id: ID!): Person
}

type Person {
  full_name: String!
  nickname: String
  website: URL
  date_of_birth: ISODateTime
  parents: [Person!]
  siblings: [Person!]
  children: [Person!]
}

scalar ISODateTime

scalar URL
copied to clipboard

and a query

# ./lib/person.graphql

query FetchPerson($id: ID!) {
  fetch_person(id: $id) {
    name: full_name
  }
}
copied to clipboard

and then you can generate dart classes with:

$ dart run build_runner build
copied to clipboard

afterwards, you can parse the result with

// person.dart

import 'person.graphql.dart';

main () {
    final data = fetchDataFromSomewhereMaybeOuterSpace();
    final parsedData = Query$FetchPerson.fromJson(data);
    final name = parsedData.fetchPerson?.name;
    print(name);
}
copied to clipboard

Using fragments #

Fragments are a great tool to re-use queries throughout your app. These are used to create "interfaces" which allow you to easily parse your data around. Given the schema above and the query

# parents_and_children.graphql

fragment PersonSummary on Person {
  full_name
}

query FetchParentsAndChildren {
  fetch_person(id: "1") {
    parents {
      ...PersonSummary
      nickname
    }

    children {
      ...PersonSummary
    }
  }
}
copied to clipboard

this will allow you to do the following

// parents_and_children.dart

import 'parents_and_children.graphql.dart';

printPerson(FragmentPersonSummary person) {
    print(person.fullName);
}

main () {
    final data = fetchDataFromTheVoid();
    final parsedData = Query$FetchParentsAndChildren.fromJson(data);
    for (final parent in parsedData?.fetchPerson.parents ?? []) {
        printPerson(parent);
        print(parent.dob);
    }
    for (final child in parsedData?.fetchPerson.children ?? []) {
        printPerson(child);
    }
}
copied to clipboard

The Fragment$PersonSummary is a class on the shape of

...
class Fragment$PersonSummary {
    String get fullName;
}
...
copied to clipboard

and will be available in the generated .graphql.dart file for the .graphql file containing the fragment.

Inline fragments #

Inline fragment spreads work just like fragment spreads with the exception that they don't generate any explicit Fragment$YourFragment classes.

So let's have the schema

type Query {
  account: Account!
}

union Account = PersonalAccount | BusinessAccount

type PersonalAccount {
  personName: String!
}

type BusinessAccount {
  businessName: String!
}
copied to clipboard

and the query

query FetchAccount {
  account {
    ... on PersonalAccount {
      personName
    }
    ... on BusinessAccount {
      businessName
    }
  }
}
copied to clipboard

the generated classes will allow you to handle the data appropriately with code along the lines of


void printAccount(Query$FetchAccount$account account) {
  if (account is Query$FetchAccount$account$$PersonalAccount) print(account.personName);
  if (account is Query$FetchAccount$account$$BusinessAccount) print(account.businessName);
}

void printQuery(Query$FetchAccount query) {
  printAccount(query.account);
}
copied to clipboard

This works but is a long class name! In these cases I usually opt to using named fragments

fragment PersonalAccount on PersonalAccount {
  personName
}

fragment BusinessAccount on BusinessAccount {
  businessName
}

query FetchAccount {
  account {
    ...BusinessAccount
    ...PersonalAccount
  }
}
copied to clipboard

which allows you to do the following


void printAccount(Query$FetchAccount$account account) {
  if (account is Fragment$PersonalAccount) print(account.personName);
  if (account is Fragment$BusinessAccount) print(account.businessName);
}

void printQuery(Query$FetchAccount query) {
  printAccount(query.account);
}
copied to clipboard

Additionally, you can use the when and maybeWhen methods to avoid is type tests. NOTE This also works the same without the inline fragments.


void printAccount(Query$FetchAccount$account account) {
  // specify all the cases (and an else in case there's a new type in the response that wasn't previously known)
  account.when(
    personalAccount: (personalAccount) => print(personalAccount.personName),
    businessAccount: (businessAccount) => print(businessAccount.businessName),
    orElse: () => print('Some other unexpected type'),
  )

  // specify only the cases you want to handle (and an else)
  account.maybeWhen(
    personalAccount: (personalAccount) => print(personalAccount.personName),
    orElse: () => print('Anything else, including BusinessAccount'),
  )
}

void printQuery(Query$FetchAccount query) {
  printAccount(query.account);
}
copied to clipboard

Options #

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          # all options go here
copied to clipboard
Option Default Description More info
clients {} Graphql clients to generate helper functions for. Supported types are graphql and graphql_flutter Clients
scalars {} Allows custom JSON-Dart transformations. Builder will warn if scalars are not recognized. Unless using primitive types, you will need fromJsonFunctionName, toJsonFunctionName, type, and import Custom scalars
enums {} Allows custom enum implementation. You can define fromJsonFunctionName, toJsonFunctionName, type, and import Custom enums
addTypename true Whether to automatically insert the __typename field in requests Add typename
addTypenameExcludedPaths [] When addTypename is true, the paths to exclude Excluding typenames
outputDirectory "." Location where to output generated types relative to each .graphql file Change output directory
assetsPath "lib/**.graphql" Path to .graphql files see above
generatedFileHeader "" A string to add at the beginning of all graphql.dart files Generated file headers
scopes ["**.graphql"] For multiple schemas, the globs for each schema Multiple Schemas
namingSeparator "$" The separator to use for generated names Change naming separator
extraKeywords [] A way to specify fields that are also keywords Extra keywords
disableCopyWithGeneration false Allow you to disable generation of copy-with classes and methods
allowMissingNullableKeysInFromJson false Support passing objects with missing entries for nullable fields.
setOperationName false Set the operation name. fields.

Clients #

Parsing data is all fine and well, but practically not extremely useful. Therefore, we can generate clients to call your API.

Clients can be enabled in the build.yaml file with:

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          clients:
            - graphql
            - graphql_flutter
copied to clipboard

Currently, we support two clients:

Client graphql #

Once you've set up your graphql client (see pub.dev/packages/graphql), you can use GraphQL Codegen to generate new queries or mutators on the client.

With the query from above:

# person.graphql

query FetchPerson($id: ID!) {
  fetch_person(id: $id) {
    name: full_name
  }
}
copied to clipboard

we can now access the client:

import 'person.graphql.dart';


main () async {
  final client = GraphQLClient();
  final result = await client.query$FetchPerson(
    Options$Query$FetchPerson(
      variables: Variables$Query$FetchPerson(id: "1"),
    ),
  );
  final parsedData = result.parsedData;
  print(parsedData?.fetchPerson?.name);
}

copied to clipboard

Cache access

You can also manipulate the cache directly using the generated readFragmentX, writeFragmentX, readQueryX, and writeQueryX methods.

Given the fragment:

fragment PersonSummary on Person {
  name
}
copied to clipboard

you can update the fragment with


main () {
  // ...
  final person = client.readFragment$PersonSummary(
    idFields: {'__typename': 'Person', 'id': '1'},
  );
  assert(person != null);
  client.writeFragment$PersonSummary(
    idFields: {'__typename': 'Person', 'id': '1'},
    data: person.copyWith(name: 'Kurt'),
  );
}
copied to clipboard

the query methods work similarly.

Client graphql_flutter #

Once you've set up your graphql_flutter client (see pub.dev/packages/graphql_flutter), you can use GraphQL Codegen to generate new Query or Mutation widgets.

With the query from above:

# person.graphql

query FetchPerson($id: ID!) {
  fetch_person(id: $id) {
    name: full_name
  }
}
copied to clipboard

we can query with the widget

import 'person.graphql.dart';
import 'package:flutter/widgets.dart';

class PersonWidget extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Query$FetchPerson$Widget(
      options: Options$Query$FetchPerson(
        variables: Variables$Query$FetchPerson(id: 'id'),
      ),
      builder: (result, {fetchMore, refetch}) {
        return Text(
          result.parsedData?.fetchPerson?.name ?? '...loading'
        );
      }
    );
  }
}
copied to clipboard

or the hook

import 'person.graphql.dart';
import 'package:flutter/widgets.dart';
import 'flutter_hooks/flutter_hooks.dart';

class PersonWidget extends HookWidget {

  @override
  Widget build(BuildContext context) {
    final result = useQuery$FetchPerson(
      Options$Query$FetchPerson(
        variables: Variables$Query$FetchPerson(id: 'id'),
      ),
    );
    return Text(result.parsedData?.fetchPerson?.name ?? '...loading');
  }
}
copied to clipboard

Custom scalars #

Out of the box, the standard scalars are supported and mapped to relevant dart types. You can add new mappings for your custom scalars or overwrite existing configurations.

In the schema above, you can see that we have defined the ISODateTime scalar. In this example, it contains a string with an ISO formatted date-time string. We would like to map this to a String type by adding the following configuration to the build.yaml file:

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          scalars:
            ISODateTime:
              type: String
            JSON:
              type: Map<String, dynamic>
copied to clipboard

The library supports converting to and from DateTime automatically. So, we can write our config as

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          scalars:
            ISODateTime:
              type: DateTime
            JSON:
              type: Map<String, dynamic>
copied to clipboard

and it'll work as expected.

Assume we want to use a CustomDateTime class instead, we can add

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          scalars:
            ISODateTime:
              type: CustomDateTime
              fromJsonFunctionName: customDateTimeFromJson
              toJsonFunctionName: customDateTimeToJson
              import: package:my_app/scalar.dart
copied to clipboard

and create a scalar.dart file with your converters

// scalar.dart

class CustomDateTime {
  final DateTime dt;
  CustomDateTime(this.dt);
}

CustomDateTime customDateTimeFromJson(dynamic data) => CustomDateTime(DateTime(data as String));
dynamic customDateTimeToJson(CustomDateTime time) => time.dt.toIso8601String();
copied to clipboard

and now all fields using ISODateTime will be a CustomDateTime instance.

Custom Enums #

Per default, the library will build enum serializers. If you want to provide your own implementation of an Enum, you can follow a similar pattern as Custom scalars.

Given the enum

enum GraphQLEnum {
  FOO
  BAR
  BAZ
}
copied to clipboard

the config

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          enums:
            GraphQLEnum:
              type: DartEnum
              fromJsonFunctionName: fromJson
              toJsonFunctionName: toJson
              import: package:my_app/enum.dart
copied to clipboard

and the implementation

// enum.dart

enum DartEnum {
  foo, bar, baz
}

DartEnum fromJson(String v) {
  switch(v) {
    case 'FOO': return DartEnum.foo;
    case 'BAR': return DartEnum.bar;
    default: return DartEnum.baz;
  }
}

String toJson(DartEnum v) {
  switch(v) {
    case DartEnum.foo: return 'FOO';
    case DartEnum.bar: return 'BAR';
    case DartEnum.baz: return 'BAZ';
  }
}

copied to clipboard

the generator will work as expected.

Use a custom fallback value #

Per default, the code-generator provides a default fallback value called $unknown. This is used to handle any new enum values when parsing the enum. Without a fallback value, your app would break when you add a new enum value.

You can select an existing enum value to be the fallback enum value. This is done by specifying the fallbackEnumValue option on the enum. So given the GraphQL:

enum MyEnum {
  FIRST
  LAST
  OTHER
}
copied to clipboard

and the configuration

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          enums:
            MyEnum:
              fallbackEnumValue: OTHER
copied to clipboard

no $unknown value will be added to your enum and all new values will be mapped to MyEnum.OTHER.

Add typename #

Please note that before toggling addTypename to true as it might just sound like a quick win for every GraphQL document, you might want to consider that this option could end up generating documents that wouldn't be up-to-spec with the main GraphQL specification. In particular, given that having several root fields on subscriptions is forbidden you probably want to make sure that the server you'll be consuming the GraphQL endpoint against might enforce this rule and add an exception to it, such as:

addTypenameExcludedPaths:
  - subscription.*
copied to clipboard

By default, the addTypename option is enabled. This'll add the __typename introspection field to every selection set. E.g.,

query Foo {
  bar {
    baz
  }
}
copied to clipboard

becomes

query Foo {
  bar {
    baz
    __typename
  }
  __typename
}
copied to clipboard

This ensures the best conditions for caching.

Excluding some selections from adding typename #

Any query, mutation, subscription, or fragment can be excluded from adding the __typename introspection by the addTypenameExcludedPaths option:

Setting

addTypenameExcludedPaths:
  - subscription
copied to clipboard

or

addTypenameExcludedPaths:
  - Foo
copied to clipboard

will both transform

subscription Foo {
  bar {
    baz
  }
}
copied to clipboard

to

subscription Foo {
  bar {
    baz
    __typename
  }
}
copied to clipboard

where

addTypenameExcludedPaths:
  - subscription.bar
copied to clipboard

or

addTypenameExcludedPaths:
  - subscription.*
copied to clipboard

or

addTypenameExcludedPaths:
  - **.bar
copied to clipboard

will transform to

subscription Foo {
  bar {
    baz
  }
  __typename
}
copied to clipboard

Change output directory #

By default, the dart files are generated relative to the *.graphql file.

You can change this by specifying the outputDirectory folder.

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          outputDirectory: __generated
copied to clipboard

which place the files in the __generated folder relative to the .graphql file. E.g.,

/lib/document.graphql -> /lib/__generated/document.graphql
copied to clipboard

You may also specify an absolute path, e.g,

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          outputDirectory: /lib/__generated
          assetsPath: graphql/**
copied to clipboard

this in combination with an asset path will place the folders in

/graphql/document.graphql -> /lib/__generated/document.graphql
/graphql/fragments/document.graphql -> /lib/__generated/fragments/document.graphql
copied to clipboard

NOTICE: For build_runner to consider files outside of the "default package layout" you'll need to add the graphql/** to the source options.

Generated file headers #

Generated .graphql.dart files can have any string inserted at the beginning of the file with the generatedFileHeader option.

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          generatedFileHeader: "// GENERATED FILE\n// DO NOT MODIFY\n"
copied to clipboard

One way to use this might be to ignore lint warnings with

generatedFileHeader: "// ignore_for_file: type=lint\n"
copied to clipboard

But since the .graphql.g.dart files also might have warnings it might be easier to ignore the generated file directories from analysis_options.yaml

analyzer:
  exclude:
    - lib/**/__generated__/*
copied to clipboard

Multiple schemas #

To support multiple schemas, the code generator has a concept of scopes. Consider the following configuration:

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          scopes:
            - lib/schema1/**
            - lib/schema2/**
copied to clipboard

here the generator will perform independent analysis for the GraphQL files matching the relevant scope. E.g., any GraphQL file in the lib/schema1 folder will be built relative to the schema in this folder, ignoring all other files completely.

Change naming separator #

The library will generate a lot of serializers and other classes. The class names are a combination of operation, field, and type names. To avoid name collisions, the library will separate each of these names with $.

E.g.,

query Q {
  name
}
copied to clipboard

might yield the class

class Query$Q$name { ... }
copied to clipboard

This should work for most, but some other libraries might not support $. Therefore, you can configure the naming separator with the namingSeparator option. E.g., the configuration:

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          namingSeparator: "___"
copied to clipboard

will change the above-yielded code to


class Query___Q___name { ... }
copied to clipboard

Null and input serializers #

Per default, when you construct an input any null field provided in the constructor will be omitted. E.g., given the input

input I {
  s: String
  b: Boolean
}
copied to clipboard

the following holds

Input$I(s: "Foo").toJson(); // {"s": "Foo"}
Input$I(s: "Foo", b: null).toJson(); // {"s": "Foo"}
Input$I(s: "Foo", b: false).toJson(); // {"s": "Foo", "b": false}
Input$I(s: "Foo").copyWith(b: null).toJson(); // {"s": "Foo", "b": null}
copied to clipboard

So to explicitly set a (nullable) field to null, you'll need to use the copyWith function.

Extra keywords #

Some APIs will generate fields that are in some way keywords and will break code generation. These might be fields with type names.

You may specify extra keywords with the option

# build.yaml

targets:
  $default:
    builders:
      graphql_codegen:
        options:
          extraKeywords:
            - String
copied to clipboard
133
likes
160
points
73.8k
downloads

Publisher

verified publisherheft.app

Weekly Downloads

2024.09.08 - 2025.03.23

Simple, opinionated, codegen library for GraphQL. It allows you to generate serializers and client helpers to easily call and parse your data.

Repository (GitHub)

Documentation

API reference

License

MIT (license)

Dependencies

build, built_collection, code_builder, dart_style, glob, gql, gql_code_builder, json_annotation, path, recase

More

Packages that depend on graphql_codegen