GraphQL is a language and set of technologies that make querying data easier for modern frontend applications. Named the technology developers are most interested in learning in the “2019 State of JavaScript” report, GraphQL is recognized as a scalable and production-ready tool that is used by many industry giants such as Github, Netflix, and Shopify, to name a few.
While most features of GraphQL are well-understood (e.g., directives, input types, resolvers), enum types are often underrated and their usefulness largely unknown. In this tutorial, we’ll shed light on why enums are important and explore the essential role this simple type plays in building robust GraphQL APIs.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
GraphQL APIs rely on GraphQL schemas to expose data. GraphQL provides it owns scalar types (Int, Float, String, Boolean, and ID) and lets you create your own data types using the following keywords.
type to define complex object types used by queries (read data)input to define input types used as arguments by mutations (create, update, delete data)We’ll base all our examples on the following schema describing a user management API.
# A User (profile) is linked to some Accounts that carries all authentication information
type Account {
id: ID! # "!" indicate a non-null (mandatory) field
authType: String! # "google-auth", "github-auth", "outlook-auth"
email: String!
token: String
archived: Boolean!
disabled: Boolean!
verified: Boolean!
}
type User {
id: ID!
accounts: [Account!]! # (a non-null array of non-null Account values)
givenName: String!
}
# a single query is exposed to list all users and filter by status and authType
type Query {
users(first: Int!, from: ID, status: String, authType: String): [User!]!
}
# we define an "input" type for our updateAccount mutation
input UpdateAccountInput {
id: ID!
state: String! # "verified", "disabled", "archived"
}
type Mutation {
updateAccount(input: UpdateAccountInput!): Account!
}
Our GraphQL API allows us to list all users with some optional filters.
status, of String type, allows us to filter the multiple boolean fields of User using a simple String value: “verified”, “disabled”, “archived”authType is based on Account.authTypeThe API uses the Account.authType value (“google-auth”, “github-auth”, “outlook-auth”) internally to leverage the proper provider to authenticate but also to get user profile information. “google-auth” leads to the appropriate logic under the GoogleProfileProvide namespace.
We can get the first 10 active users to use Google by doing the following.
user(first: 10, authType: "google-auth", status: "verified") {
id
# ...
}
Our API also exposes an updateAccount mutation, which allows us to update an account status during its lifecycle.

This schema is functional as-is. However, we can make it way more robust by leveraging GraphQL enums.
Although when using String, Account.authType, and UpdateAccountInput.state we can expect to receive specific values, this can introduce issues at multiple levels.
Below is an example of how you might use the users query.
query {
users(first: 10, status: "Verified") {
id
}
}
GraphQL-wise, on the client side (e.g., Apollo), this query is entirely valid. However, can you spot the typo?
The status value “Verified”, should be written as “verified”.
Having a free-form String type here might trigger unexpected 500s, silent errors, or, worse, security vulnerabilities.
To prevent this, we’ll have to add some validation logic to our users query resolver.
interface UsersArgs {
first: number,
from?: string,
status?: String,
authType?: string,
}
const STATUSES = ['', '', ]
export const resolvers = {
Query: {
users: (_record: never, args: UsersArgs, _context: never) {
if (args?.status && !STATUSES.includes(args.status)) {
// throw error
}
// ...
}
}
}
This is quite counterproductive since the principal value of GraphQL type-based schemas is to provide validation out of the box on operations and data. Using enums will limit the status value to a given set of defined values and avoid duplicate validations logic.
We saw that Account.authType relys on a String type, which leads to the following kind of users query.
query {
users(first: 10, authType: "google-auth") {
id
}
}
Such a pattern is not optimal because exposing internal values goes against the principle that APIs should expose a simple, decoupled interface with data.
Again, we can improve our users query resolver to allows public-friendly values for filtering.
interface UsersArgs {
first: number,
from?: string,
status?: String,
authType?: string,
}
const AUTH_TYPES_FILTER_MAP = {
'GOOGLE': 'google-auth',
'GITHUB': 'github-auth,
'OUTLOOK': 'outlook-auth',
}
export const resolvers = {
Query: {
users: (_record: never, args: UsersArgs, _context: never) {
// does both mapping and validation (invalid value will be ignored)
const authType = AUTH_TYPES_FILTER_MAP[args.authType]
// ...
}
}
}
This would allow the following query.
query {
users(first: 10, authType: "GOOGLE") {
id
}
}
However, here again, we have to build a feature already provided by GraphQL via enums.
In the next section, we’ll walk through how to map enums to internal values by migrating our schema to enums.
The last downside of relying on the String type for filter- or choice-based values is discoverability.
If you have an UpdateAccountInput.status that relies on the String type, GraphQL won’t be able to inspect the possible values of UpdateAccountInput.status during the introspection query since only the resolvers know about them.
GraphQL introspection is one of the core features of GraphQL that allows clients to dynamically inspect the schema definition by doing a GraphQL query (inception).
Here’s an example listing all the queries defined on our schema:
query {
__schema {
queryType {
fields {
name
args {
name
type {
name
description
}
}
description
deprecationReason
}
}
}
}
{
"data": {
"__schema": {
"queryType": {
"fields": [
{
"args": [
{
"name": "authType",
"type": {
"name": "String",
"description": null
}
},
{
"name": "first",
"type": {
"name": null,
"description": null
}
},
{
"name": "from",
"type": {
"name": "ID",
"description": null
}
},
{
"name": "status",
"type": {
"name": "String",
"description": null
}
}
],
"deprecationReason": null,
"name": "users",
"description": "perform the action: \"users\""
}
]
}
}
}
}
Tools like GraphiQL use introspection to provide autocomplete functionality. GraphQL Code Generator also uses it to generate TypeScript types.
String types do not allow us to inspect the possible values.

GraphQL enums are introspectable, allowing the app to autocomplete possible values and generate corresponding TypeScript types.
Relying on String may lead to security issues, unnecessary validation logic on the resolvers side, and a poor developer experience.
Let’s make our GraphQL Schema more robust by migrating to enums.
GraphQL enums can be defined using the enum keyword.
enum AuthType {
GOOGLE
GITHUB
OUTLOOK
}
According to the official specification, you should name enums values using the all-caps convention. Enum values don’t need any common prefix since they are namespaced by the enum’s name.
Let’s take a look at our new enums-based schema:
enum AuthType {
GOOGLE
GITHUB
OUTLOOK
}
# A User (profile) is linked to some Accounts that carries all authentication information
type Account {
id: ID! # "!" indicate a non-null (mandatory) field
authType: AuthType!
email: String!
token: String
archived: Boolean!
disabled: Boolean!
verified: Boolean!
}
type User {
id: ID!
accounts: [Account!]! # (a non-null array of non-null Account values)
givenName: String!
}
# a single query is exposed to list all users and filter by status and authType
type Query {
users(first: Int!, from: ID, status: String, authType: String): [User!]!
}
enum AccountState {
VERIFIED,
DISABLED,
ARCHIVED
}
# we define an "input" type for our updateAccount mutation
input UpdateAccountInput {
id: ID!
state: AccountState!
}
type Mutation {
updateAccount(input: UpdateAccountInput!): Account!
}
Enum internal values
Enum value resolution (at resolver and client side) is an implementation detail, meaning that each library chooses how to resolve a given enum value to a language-specific value.
With Apollo Server, each enum value will resolve to its string equivalent.
AuthType.GOOGLE resolves to “GOOGLE”AuthType.GITHUB resolves to “GITHUB”AuthType.OUTLOOK resolves to “OUTLOOK”However, our API expected manipulated internal values (“google-auth”, “github-auth”, “outlook-auth”) to operate appropriately.
Per the official documentation, Apollo Server enables you to provide enum resolvers, as shown below with our updated users query resolver.
JavaScript version:
export const resolvers = {
AuthType: {
GOOGLE: 'google-auth',
GITHUB: 'github-auth',
OUTLOOK: 'outlook-auth',
},
Query: {
users: (_record, args, _context) {
// args.authType will always be 'google-auth' or 'github-auth' or 'outlook-auth'
// ...
}
}
}
TypeScript version:
enum AuthType {
GOOGLE = 'google-auth',
GITHUB = 'github-auth',
OUTLOOK = 'outlook-auth',
}
interface UsersArgs {
first: number,
from?: string,
status?: String,
authType?: AuthType,
}
export const resolvers = {
AuthType,
Query: {
users: (_record: never, args: UsersArgs, _context: never) {
// args.authType will always be 'google-auth' or 'github-auth' or 'outlook-auth'
// ...
}
}
}
Note that with TypeScript, an enum type can be directly passed as a resolver.
Apollo Server will resolve enum values to their proper internal values (resolvers.AuthType) for both input and output data (Mutation arguments and Query returned data).
Now that our schema relies on enums, let’s see how it improves our queries and mutations usage.
We can now query our users and filter using our AuthType enum.
query {
users (first: 10, authType: GOOGLE) {
id
}
}
Our query is now safe, typoless, and fully discoverable.

But the enum value as String is not valid. The field using an enum type requires an enum reference, so passing the enum value isn’t considered valid.

GraphQL enums are introspectable, as shown below.

Introspectable enums mean that tools like GraphQL Code Generator will generate the corresponding TypeScript enum type for your frontend application.
With our schema, GraphQL code generator will be able to create the corresponding TypeScript enum:
export enum AuthType {
GOOGLE = 'google-auth',
GITHUB = 'github-auth',
OUTLOOK = 'outlook-auth',
}
This will come in handy for later use (useQuery(), useMutation()):
const ActiveUsersList = () => {
const { loading, data } = useQuery(
usersQueryDocument,
{
variables: {
first: 10,
authType: AuthType.GOOGLE,
}
})
// ...
}
Relying on both GraphQL enums and TypeScript-generated enums makes for a robust and powerful developer experience.
Even on small GraphQL APIs, such as the User Management API, enums bring a lot of consistency and make your schema more robust.
In this tutorial, we explored how GraphQL enums can help:
While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.
LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.
LogRocket's Galileo AI watches sessions for you, instantly aggregating and reporting on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

line-clamp to trim lines of textMaster the CSS line-clamp property. Learn how to truncate text lines, ensure cross-browser compatibility, and avoid hidden UX pitfalls when designing modern web layouts.

Discover seven custom React Hooks that will simplify your web development process and make you a faster, better, more efficient developer.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
4 Replies to "What you need to know about GraphQL enums"
Great write-up, thank you!
I have a question though: It isn’t clear to me how the client side can know the internal values for the enum. Since Apollo has devs define those internal values in a resolver, they don’t show up in the server schema itself, and therefore when you tell codegen to run for the client, it can’t know about the internal values. Am I missing some helpful setting or something? Do you have a complete version of your example in a repo?
Hi Micah,
Thank you for you comment!
Although I’m not an expert of Apollo Server inner working, I do believe that Apollo Server is using enums “internal values” passed as resolvers to build the schema and pass them as default value to graphql-js (https://github.com/graphql/graphql-js/issues/1604)
Since those enum internal values are passed to the Schema definition “magically” by Apollo Server, it explains how you end-up having them in the introspection query results used by tools like GraphQL code generator.
Please let me know if it answers your question!
Thanks for this excellent article!
You left out one important thing. Enums can’t be nullable.