How to Pass JSON Object in GraphQL
One of the most common questions when working with GraphQL is: how do I pass a JSON object as an argument? Unlike REST, where you can send arbitrary JSON bodies, GraphQL requires you to define explicit input types for complex arguments.
Option 1: Use Input Types (Recommended)
The idiomatic GraphQL approach is to define an input type that mirrors your JSON structure. Input types are like object types but used specifically for arguments.
input UserProfileInput {
displayName: String!
bio: String
avatarUrl: String
preferences: JSON
}
type Mutation {
updateProfile(input: UserProfileInput!): User
} Then call it from your client:
mutation {
updateProfile(input: {
displayName: "Ada Lovelace"
bio: "First programmer"
preferences: { theme: "dark", lang: "en" }
}) {
id
displayName
}
} Option 2: Custom JSON Scalar
When your JSON structure is dynamic or unknown at schema design time, use a custom JSON scalar. This accepts any valid JSON value. Libraries like graphql-type-json (Node.js) or graphql-java-extended-scalars provide ready-made implementations.
scalar JSON
type Mutation {
submitAnalytics(data: JSON!): Boolean
} The server-side implementation (JavaScript example):
import { GraphQLJSON } from 'graphql-type-json';
const resolvers = {
JSON: GraphQLJSON,
Mutation: {
submitAnalytics: (_, { data }) => {
console.log('Received:', data.event, data.properties);
return true;
},
},
}; Option 3: Stringify and Parse
A simple but less elegant approach: pass the JSON as a String argument and parse it on the server. This works but sacrifices type safety and validation.
mutation {
saveConfig(jsonString: "{\"theme\": \"dark\", \"lang\": \"en\"}")
} Which Approach Should You Use?
- Input types — Best for structured, known data. Provides full type safety, validation, and IDE autocomplete.
- JSON scalar — Best for dynamic or semi-structured data where the shape isn't known in advance.
- Stringified JSON — Quick workaround but loses all GraphQL's type-safety benefits.
Nested Input Objects
Input types can be nested just like object types:
input AddressInput {
street: String!
city: String!
zipCode: String
country: String!
}
input CreateUserInput {
name: String!
email: String!
address: AddressInput!
tags: [String!]
} This maps naturally to JSON and lets you pass deeply nested structures while maintaining type safety at every level.