Unions
The bookshop's search box should find anything: a book by its title, an author by name, a magazine by its masthead. Books and authors share almost nothing (an author has no price, a book has no list of books), so an interface full of shared fields would be forced. A union says only "the result is one of these types".
The rule
A union is "one of these objects", with no shared fields at all.
A union lists its member types, separated by |:
union SearchResult = Book | Author | Magazine
type Query {
search(text: String!): [SearchResult!]!
}Unlike an interface, a union promises no fields. So when a field returns a union, the only field you can select directly is __typename; everything else goes inside an inline fragment for each member you care about:
{
search(text: "salt") {
__typename
... on Book { title price }
... on Author { name }
... on Magazine { title issue }
}
}{ "data": { "search": [
{ "__typename": "Book", "title": "The Salt Road", "price": 499 },
{ "__typename": "Author", "name": "Nadia Saltwell" }
] } }The rules:
- Members must be object types. Scalars, enums, interfaces, input types and other unions cannot be members.
- A union is output only: it can be a field's return type, never an argument's type.
- If you skip a member's fragment, items of that type still appear, but with only the fields you asked for (perhaps just
__typename).
Like interfaces, unions need a small piece of server code that tells, for each value, which member type it is.
Interface or union? If the types share fields that mean the same thing (every product has a title and a price), use an interface, so clients can select those fields directly. If they are just different things that happen to appear together (books and authors in search), use a union.
The shortcut
Ask "what do these types honestly have in common?" Meaningful shared fields → interface. Nothing → union, and plan one inline fragment per member plus __typename.
List the types
Search can return books, authors and magazines.
The common mistake is selecting a field directly on a union, such as search(text: "salt") { title }. Even if two members have a title, the union itself has no fields.
Drills
Answer fast — trust the rule.
Why is { search(text: "salt") { title } } rejected?
Which type could be a member of SearchResult?
Books and authors should appear together in search results. Which fits best?
Books, magazines and audiobooks all have an ID, a title and a price. Which fits best for a list of them?
Can SearchResult be the type of an argument, as in save(item: SearchResult!)?
Remember
- A union is "one of these objects", with no shared fields at all.
- Shared meaningful fields → interface; nothing in common → union with a fragment per member.
- Trap: selecting fields directly on a union; only
__typenameworks there.
