A simple helper function to generate GraphQL queries using plain JavaScript Objects (JSON).
npm install gql-query-builder
# or
pnpm add gql-query-builder
# or
yarn add gql-query-builderRequires Node.js >= 22 (works in browsers too). The package ships both ESM (import) and CommonJS (require) builds with TypeScript types included β no extra @types package needed.
import * as gql from 'gql-query-builder'
const query = gql.query(options: object)
const mutation = gql.mutation(options: object)
const subscription = gql.subscription(options: object)You can also import query or mutation or subscription individually:
import { query, mutation, subscription } from 'gql-query-builder'
query(options: object)
mutation(options: object)
subscription(options: object)options is { operation, fields, variables } or an array of options
| Name | Description | Type | Required | Example |
|---|---|---|---|---|
| operation | Name of operation to be executed on server | String | Object | Yes |
getThoughts, createThought
{ name: 'getUser', alias: 'getAdminUser' }
|
| fields | Selection of fields | Array | No |
['id', 'name', 'thought']
['id', 'name', 'thought', { user: ['id', 'email'] }]
|
| variables | Variables sent to the operation | Object | No |
{ key: value } eg: { id: 1 }
{ key: { value: value, required: true, type: GQL type, list: true, name: argument name } eg:
{
email: { value: "user@example.com", required: true },
password: { value: "123456", required: true },
secondaryEmails: { value: [], required: false, type: 'String', list: true, name: secondaryEmail }
}
|
An optional second argument adapter is a typescript/javascript class that implements the QueryAdapter, MutationAdapter, or SubscriptionAdapter interface (exported from the package root).
If adapter is undefined then the default adapter (DefaultQueryAdapter, DefaultMutationAdapter, or DefaultSubscriptionAdapter β also exported for reuse) is used.
import * as gql from 'gql-query-builder'
const query = gql.query(options: object, adapter?: MyCustomQueryAdapter, config?: object)
const mutation = gql.mutation(options: object, adapter?: MyCustomMutationAdapter, config?: object)
const subscription = gql.subscription(options: object, adapter?: MyCustomSubscriptionAdapter, config?: object)
| Name | Description | Type | Required | Example |
|---|---|---|---|---|
| operationName | Name of operation to be sent to the server | String | No | getThoughts, createThought |
- Query
- Query (with variables)
- Query (with nested fields selection)
- Query (with required variables)
- Query (with custom argument name)
- Query (with operation name)
- Query (with empty fields)
- Query (with alias)
- Query (with inline fragment)
- Query (with adapter defined)
- Mutation
- Mutation (with required variables)
- Mutation (with custom types)
- Mutation (with adapter defined)
- Mutation (with operation name)
- Subscription
- Subscription (with adapter defined)
- Query (with default variable values)
- Query (with named fragments)
- Query (with directives)
- Subscription (with operation name)
- Example with Fetch
- Example with Axios
import * as gql from 'gql-query-builder'
const query = gql.query({
operation: 'thoughts',
fields: ['id', 'name', 'thought']
})
console.log(query)
// Output
query {
thoughts {
id,
name,
thought
}
}import * as gql from 'gql-query-builder'
const query = gql.query({
operation: 'thought',
variables: { id: 1 },
fields: ['id', 'name', 'thought']
})
console.log(query)
// Output
query ($id: Int) {
thought (id: $id) {
id, name, thought
}
}
// Variables
{ "id": 1 }import * as gql from 'gql-query-builder'
const query = gql.query({
operation: 'orders',
fields: [
'id',
'amount',
{
user: [
'id',
'name',
'email',
{
address: [
'city',
'country'
]
}
]
}
]
})
console.log(query)
// Output
query {
orders {
id,
amount,
user {
id,
name,
email,
address {
city,
country
}
}
}
}import * as gql from 'gql-query-builder'
const query = gql.query({
operation: 'userLogin',
variables: {
email: { value: 'jon.doe@example.com', required: true },
password: { value: '123456', required: true }
},
fields: ['userId', 'token']
})
console.log(query)
// Output
query ($email: String!, $password: String!) {
userLogin (email: $email, password: $password) {
userId, token
}
}
// Variables
{
email: "jon.doe@example.com",
password: "123456"
}import * as gql from 'gql-query-builder'
const query = gql.query([{
operation: "someoperation",
fields: [{
operation: "nestedoperation",
fields: ["field1"],
variables: {
id2: {
name: "id",
type: "ID",
value: 123,
},
},
}, ],
variables: {
id1: {
name: "id",
type: "ID",
value: 456,
},
},
}, ]);
console.log(query)
// Output
query($id2: ID, $id1: ID) {
someoperation(id: $id1) {
nestedoperation(id: $id2) {
field1
}
}
}
// Variables
{
"id1": 456,
"id2": 123
}import * as gql from 'gql-query-builder'
const query = gql.query({
operation: 'userLogin',
fields: ['userId', 'token']
}, null, {
operationName: 'someoperation'
})
console.log(query)
// Output
query someoperation {
userLogin {
userId
token
}
}import * as gql from 'gql-query-builder'
const query = gql.query([
{ operation: "getFilteredUsersCount" },
{ operation: "getAllUsersCount", fields: [] },
{ operation: "getFilteredUsers", fields: [{ count: [] }] },
]);
console.log(query)
// Output
query {
getFilteredUsersCount
getAllUsersCount
getFilteredUsers {
count
}
}import * as gql from 'gql-query-builder'
const query = gql.query({
operation: {
name: 'thoughts',
alias: 'myThoughts',
},
fields: ['id', 'name', 'thought']
})
console.log(query)
// Output
query {
myThoughts: thoughts {
id,
name,
thought
}
}import * as gql from 'gql-query-builder'
const query = gql.query({
operation: "thought",
fields: [
"id",
"name",
"thought",
{
operation: "FragmentType",
fields: ["emotion"],
fragment: true,
},
],
});
console.log(query)
// Output
query {
thought {
id,
name,
thought,
... on FragmentType {
emotion
}
}
}For example, to inject SomethingIDidInMyAdapter in the operationWrapperTemplate method.
import * as gql from 'gql-query-builder'
import MyQueryAdapter from 'where/adapters/live/MyQueryAdapter'
const query = gql.query({
operation: 'thoughts',
fields: ['id', 'name', 'thought']
}, MyQueryAdapter)
console.log(query)
// Output
query SomethingIDidInMyAdapter {
thoughts {
id,
name,
thought
}
}Take a peek at DefaultQueryAdapter to get an understanding of how to make a new adapter.
import * as gql from 'gql-query-builder'
const query = gql.mutation({
operation: 'thoughtCreate',
variables: {
name: 'Tyrion Lannister',
thought: 'I drink and I know things.'
},
fields: ['id']
})
console.log(query)
// Output
mutation ($name: String, $thought: String) {
thoughtCreate (name: $name, thought: $thought) {
id
}
}
// Variables
{
"name": "Tyrion Lannister",
"thought": "I drink and I know things."
}import * as gql from 'gql-query-builder'
const query = gql.mutation({
operation: 'userSignup',
variables: {
name: { value: 'Jon Doe' },
email: { value: 'jon.doe@example.com', required: true },
password: { value: '123456', required: true }
},
fields: ['userId']
})
console.log(query)
// Output
mutation ($name: String, $email: String!, $password: String!) {
userSignup (name: $name, email: $email, password: $password) {
userId
}
}
// Variables
{
name: "Jon Doe",
email: "jon.doe@example.com",
password: "123456"
}import * as gql from 'gql-query-builder'
const query = gql.mutation({
operation: "userPhoneNumber",
variables: {
phone: {
value: { prefix: "+91", number: "9876543210" },
type: "PhoneNumber",
required: true
}
},
fields: ["id"]
})
console.log(query)
// Output
mutation ($phone: PhoneNumber!) {
userPhoneNumber (phone: $phone) {
id
}
}
// Variables
{
phone: {
prefix: "+91", number: "9876543210"
}
}For example, to inject SomethingIDidInMyAdapter in the operationWrapperTemplate method.
import * as gql from 'gql-query-builder'
import MyMutationAdapter from 'where/adapters/live/MyQueryAdapter'
const query = gql.mutation({
operation: 'thoughts',
fields: ['id', 'name', 'thought']
}, MyMutationAdapter)
console.log(query)
// Output
mutation SomethingIDidInMyAdapter {
thoughts {
id,
name,
thought
}
}Take a peek at DefaultMutationAdapter to get an understanding of how to make a new adapter.
import * as gql from 'gql-query-builder'
const query = gql.mutation({
operation: 'thoughts',
fields: ['id', 'name', 'thought']
}, undefined, {
operationName: 'someoperation'
})
console.log(query)
// Output
mutation someoperation {
thoughts {
id
name
thought
}
}import { subscription } from 'gql-query-builder'
const sub = subscription({
operation: 'thoughtCreate',
variables: {
name: 'Tyrion Lannister',
thought: 'I drink and I know things.'
},
fields: ['id']
})
console.log(sub)
// Output
subscription ($name: String, $thought: String) {
thoughtCreate (name: $name, thought: $thought) {
id
}
}
// Variables
{
"name": "Tyrion Lannister",
"thought": "I drink and I know things."
}For example, to inject SomethingIDidInMyAdapter in the operationWrapperTemplate method.
import * as gql from 'gql-query-builder'
import MySubscriptionAdapter from 'where/adapters/live/MyQueryAdapter'
const query = gql.subscription({
operation: 'thoughts',
fields: ['id', 'name', 'thought']
}, MySubscriptionAdapter)
console.log(query)
// Output
subscription SomethingIDidInMyAdapter {
thoughts {
id,
name,
thought
}
}Take a peek at DefaultSubscriptionAdapter to get an understanding of how to make a new adapter.
Add a default to a variable descriptor to emit a GraphQL default value. Strings are quoted and input objects use unquoted keys automatically; wrap enum literals with rawGraphQL():
import { query, rawGraphQL } from 'gql-query-builder'
const q = query({
operation: 'hero',
variables: {
episode: { value: 'EMPIRE', type: 'Episode', default: rawGraphQL('JEDI') },
first: { value: 10, default: 5 }
},
fields: ['name']
})
// Output
query ($episode: Episode = JEDI, $first: Int = 5) {
hero (episode: $episode, first: $first) {
name
}
}Define reusable named fragments with the fragments config option and spread them with a plain '...name' field string. Works for queries, mutations, and subscriptions:
import { query } from 'gql-query-builder'
const q = query({
operation: 'hero',
fields: ['...heroFields']
}, null, {
fragments: [
{ name: 'heroFields', on: 'Character', fields: ['name', { friends: ['name'] }] }
]
})
// Output
query {
hero {
...heroFields
}
}
fragment heroFields on Character { name, friends { name } }Directives such as @include(if:) and @skip(if:) (or @defer/@stream where supported) can be attached to any field β field strings are passed through verbatim:
import { query } from 'gql-query-builder'
const q = query({
operation: 'hero',
variables: { withFriends: { value: true, type: 'Boolean', required: true } },
fields: ['name', 'friends @include(if: $withFriends)']
})
// Output
query ($withFriends: Boolean!) {
hero (withFriends: $withFriends) {
name,
friends @include(if: $withFriends)
}
}The same passthrough works for field-level aliases ('empireHero: name') and meta fields ('__typename').
import { subscription } from 'gql-query-builder'
const q = subscription({
operation: 'postAdded',
variables: { topic: 'news' },
fields: ['id']
}, null, {
operationName: 'OnPostAdded'
})
// Output
subscription OnPostAdded ($topic: String) {
postAdded (topic: $topic) {
id
}
}Example with Fetch
No extra dependencies needed β fetch is built into modern browsers, Node.js >= 18, Deno, and Bun. Unlike Axios, you serialize the body and set the Content-Type header yourself.
Query:
import { query } from "gql-query-builder";
async function getThoughts() {
try {
const response = await fetch("http://api.example.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(
query({
operation: "thoughts",
fields: ["id", "name", "thought"],
})
),
});
const result = await response.json();
console.log(result);
} catch (error) {
console.log(error);
}
}Mutation:
import { mutation } from "gql-query-builder";
async function saveThought() {
try {
const response = await fetch("http://api.example.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(
mutation({
operation: "thoughtCreate",
variables: {
name: "Tyrion Lannister",
thought: "I drink and I know things.",
},
fields: ["id"],
})
),
});
const result = await response.json();
console.log(result);
} catch (error) {
console.log(error);
}
}Example with Axios
Query:
import axios from "axios";
import { query } from "gql-query-builder";
async function getThoughts() {
try {
const response = await axios.post(
"http://api.example.com/graphql",
query({
operation: "thoughts",
fields: ["id", "name", "thought"],
})
);
console.log(response);
} catch (error) {
console.log(error);
}
}Mutation:
import axios from "axios";
import { mutation } from "gql-query-builder";
async function saveThought() {
try {
const response = await axios.post(
"http://api.example.com/graphql",
mutation({
operation: "thoughtCreate",
variables: {
name: "Tyrion Lannister",
thought: "I drink and I know things.",
},
fields: ["id"],
})
);
console.log(response);
} catch (error) {
console.log(error);
}
}Prefer to learn by poking at it? example/ is a runnable single-page app that turns every feature below into a live, editable recipe β nested selections, aliases, fragments, the full variable-descriptor vocabulary, mutations, subscriptions and custom adapters. The query recipes run against a real GraphQL server (the public Rick & Morty API), so you can watch the generated string come back with real data.
cd example
pnpm install && pnpm dev # β http://localhost:5173This repo uses pnpm, Biome for lint/format, Vitest for tests, tsup for the dual ESM/CJS build, and Changesets for releases.
pnpm install # install dependencies
pnpm test # run the test suite
pnpm lint # lint + format check
pnpm typecheck # TypeScript, no emit
pnpm build # build ESM + CJS + types into dist/
pnpm changeset # describe your change for the next releaseEvery user-facing PR should include a changeset. Merging to main opens/updates an automated "Version Packages" PR; merging that publishes to npm. Publishing uses npm trusted publishing (OIDC).
A compact, machine-friendly API reference lives in docs/api.md; agent/LLM entry points are AGENTS.md and llms.txt.
Parts of this project were written with the help of AI tools.
The MIT License (http://www.opensource.org/licenses/mit-license.php)