Open-source project
taion/graphql-type-json avatar
taion/graphql-type-json

graphql-type-json: a JSON scalar for GraphQL.js schemas

JSON scalar type for GraphQL.js

983 stars54 forksJavaScriptMIT

At a glance

What is it?
graphql-type-json is a small MIT licensed package that adds a JSON scalar, and a JSON-object-only scalar, to GraphQL.js so that a schema can carry arbitrary JSON-shaped values. It suits teams with a documented JSON payload that has to travel through a typed API, and it is the wrong answer when the shape of that payload is known and should be typed.
Who is it for?
Adopt graphql-type-json when you have a JSON payload that clients already send and receive, when that shape is deliberately untyped, and when several services need the same scalar definition so the contract does not drift between codebases. Do not adopt it to model data you could describe in the schema, because a JSON scalar moves validation out of the schema and into runtime code.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A way to carry JSON through a typed schema

GraphQL's type system is the point of the technology, and it is also its main friction with real backends. Most services store and return data that is not a neat table of scalars: a settings blob, a webhook payload, a nested configuration object that the owning team controls and nobody wants to model field by field. GraphQL offers no built-in JSON type, so a schema cannot describe such a field without either inventing a placeholder type or leaving a hole in the contract.

graphql-type-json fills that hole. It is described as a JSON scalar type for GraphQL.js and exports a scalar that can be attached to any field. The audience is narrow and real: teams with a stable JSON payload crossing a GraphQL boundary, usually where the schema is published to third parties and the payload contract must be explicit even though its inner fields are not.

The package is small by design. The repository holds src/, test/, types/, a .babelrc.js, an .eslintrc.js, a .travis.yml, a renovate.json and a yarn.lock, with no example application and no framework assumptions. The author is credited as Jimmy Jia, the licence is MIT, and the package is published on npm as graphql-type-json.

Two scalars with different contracts

The package exports two things, and the difference between them is the design decision worth understanding before you pick one. GraphQLJSON is the general case and can represent any JSON-serializable value, which the README states includes scalars, arrays and objects. GraphQLJSONObject is the narrow case and represents specifically JSON objects.

That second type exists because objects are what most practical JSON fields actually hold, and narrowing the contract buys something real: a client that knows the field is an object can rely on it rather than discovering at runtime that it received a string or an array. The trade is that the object type still says nothing about the keys inside it, so the validation win is modest rather than large.

Both are consumed the same way. The README shows the ES module import first:

js
import GraphQLJSON from 'graphql-type-json';

and the named export for the object variant:

js
import { GraphQLJSONObject } from 'graphql-type-json';

CommonJS is supported as well, with both types pulled from one require:

js
const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json');

Attaching the scalar to a programmatically built schema

The first integration path is a schema you construct in code. The README describes using the scalars in a programmatically-constructed schema as with any other scalar type, which means they are plain GraphQL type objects that you place in a field definition:

js
import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json';

export default new GraphQLObjectType({
  name: 'MyType',

  fields: {
    myValue: { type: GraphQLJSON },
    myObject: { type: GraphQLJSONObject },
  },
});

The important detail is that a JSON field is just another field. It takes arguments like any other, it can appear in queries and fragments, and it can be returned by a resolver exactly like a string field. Nothing about the surrounding schema changes shape because one field carries a JSON value.

The second path is SDL, where the schema is written as text. The README shows that when using SDL with GraphQL-tools, you define GraphQLJSON as the resolver for the appropriate scalar type in your schema. The type definitions declare the scalar and use it:

js
const typeDefs = `
scalar JSON
scalar JSONObject

type MyType {
  myValue: JSON
  myObject: JSONObject
}
`;

and the resolvers map those names onto the imported scalar implementations, after which the executable schema is built:

js
const resolvers = {
  JSON: GraphQLJSON,
  JSONObject: GraphQLJSONObject,
};

export default makeExecutableSchema({ typeDefs, resolvers });

The wiring is a name-to-implementation map, and the names in the SDL are yours to choose, so a schema can call them Json, JsonObject or something else as long as the map agrees with it.

A Babel-built package published in two module formats

The build setup in package.json explains how the two import styles above both work. The package field lists lib and es as the published files, main points at lib/index.js for CommonJS, module points at es/index.js for bundlers that understand ES modules, and types points at lib/index.d.ts for TypeScript. The build script chains three steps, a CommonJS build with babel into lib, an ES module build with babel under an esm environment into es, and a type copy step that copies the .d.ts files from types/ into lib.

So the source is written once in src/ and compiled twice, with the TypeScript declarations maintained separately in types/ and copied rather than generated. The type definitions are then checked by dtslint in the test script, which means the declarations are held to the same standard as the implementation rather than being an afterthought that drifts.

The rest of the tooling is conventional. Formatting and linting run through eslint and prettier, with prettier configured for a print width of 79, single quotes and trailing commas. A husky pre-commit hook runs lint-staged, which formats staged JavaScript and Markdown or TypeScript files before a commit lands. The jest configuration collects coverage from src/ and runs in a node test environment.

The release gap behind a recent push

Here is the fact worth checking before you depend on the package. The releases list shows v0.3.2 from 2020-06-10, v0.3.1 from 2019-11-20 and v0.3.0 from 2019-04-12. The last push to the repository was on 2026-10-16, so the code is being touched, but no new version has been published to npm since 2020.

What kind of touching is a reasonable guess from the repository contents. A renovate.json at the root is the configuration for automated dependency updates, and the continuous integration configuration is a .travis.yml file, which is an older hosted CI service rather than a GitHub Actions workflow. The README badges point at Travis and at Codecov for coverage. Those are signs of a project whose dependency graph is being kept current by a bot while the package itself has not needed a functional change.

The practical reading for an adopter is that this is a stable, small, widely copied utility rather than an actively evolving library. The API surface is two exports and will not change shape, but you also cannot expect new behaviour, and the published tarball is what you get regardless of what is in the repository today.

Where a JSON scalar is the wrong answer

The failure mode of this approach is not a bug, it is a design consequence. A JSON scalar tells the client that a field holds a JSON value and stops there. It does not describe the keys, the types inside, or which of them are required, so the schema stops being the contract for that part of the payload. Validation moves from the schema layer, where it is declarative and shared, into resolver code or into the client.

That matters most in the places where GraphQL earns its keep. A field's arguments can be validated, its type is introspectable, and generated clients can be typed from the schema. A JSON field gives all of that up for that field. If you have a payload with a stable shape that several clients depend on, modelling it as a proper object type with named fields would preserve the schema's usefulness, at the cost of writing the type out.

The second limitation is runtime validation. Nothing in the package describes what happens when a resolver returns a value that is not JSON-serializable, and the README does not document behaviour for that case. The third is that the two scalars are shallow by design: GraphQLJSONObject constrains the outer level to an object and says nothing about the nested values, so it will not catch a malformed nested structure either.

A fourth practical point: the README documents no install command, only the imports, so you are expected to know that the package name on npm is graphql-type-json and to add it to your dependency list the way your project normally does.

Against defining the scalar inside your own repository

The alternative to a published package is to write the scalar yourself. A JSON scalar is a small object with a name, a description, a serialize function and a parseValue function, and a team that needs one in a single service can write it in an afternoon inside its own source tree.

The difference in approach is about where the definition lives and who has to agree on it. A local implementation is versioned with the service that uses it, has no dependency to track, and can be shaped to that service's validation rules. A published package is a shared artefact, so every service that uses it pins the same implementation, the scalar name and its description stay consistent across codebases, and there is one place to look when the behaviour needs to change.

For one service with one JSON field, the local implementation is genuinely better, because the dependency buys almost nothing. The case for the package appears when the same JSON field exists in a public schema, when more than one service exposes the same payload, or when the organisation wants a single reviewed definition of how JSON values enter and leave the API.

The third alternative is to model the payload in the schema properly, with an object type and named fields. It costs more up front and gives up the flexibility of accepting a payload the schema has not seen, but it keeps introspection, validation and client generation working, which is the whole reason to use GraphQL.

Editorial conclusion

Adopt graphql-type-json when you have a JSON payload that clients already send and receive, when that shape is deliberately untyped, and when several services need the same scalar definition so the contract does not drift between codebases. Do not adopt it to model data you could describe in the schema, because a JSON scalar moves validation out of the schema and into runtime code. Verify first that the scalar semantics your clients expect match the ones here, since GraphQLJSON accepts any JSON-serializable value while GraphQLJSONObject is restricted to objects. Also note that the last published release is v0.3.2 from 2020-06-10 even though the last push was on 2026-10-16, so pinning the version you install is the safer habit.

Frequently asked questions

How do I import the JSON scalar in graphql-type-json?

The package exports GraphQLJSON as the default export and GraphQLJSONObject as a named export, so ES modules use import GraphQLJSON from 'graphql-type-json' and import { GraphQLJSONObject } from 'graphql-type-json'. CommonJS users can destructure both from a single require of 'graphql-type-json'.

What is the difference between GraphQLJSON and GraphQLJSONObject?

GraphQLJSON can represent any JSON-serializable value, including scalars, arrays and objects, while GraphQLJSONObject represents specifically JSON objects. The narrower type covers many practical JSON field use cases.

How do I use the JSON scalar with an SDL schema?

Declare the scalars in your type definitions, for example scalar JSON and scalar JSONObject, then map those names to the imported implementations in the resolvers object and build the schema with makeExecutableSchema from graphql-tools.

What is the latest published version of graphql-type-json?

The releases list shows v0.3.2 from 2020-06-10, following v0.3.1 from 2019-11-20 and v0.3.0 from 2019-04-12. The repository itself was pushed to on 2026-10-16, so the code moves while the published version does not.

What licence is graphql-type-json released under?

MIT, stated in the repository licence field and in the license entry of package.json, with the LICENSE file at the repository root. Read that file for the exact terms before you depend on it.

Does the package ship TypeScript types?

Yes. The types field points at lib/index.d.ts, and the build copies the declaration files from the types/ directory into lib. The test script runs dtslint against those declarations, and the repository also carries a yarn.lock for the toolchain.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. taion/graphql-type-json on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/taion-graphql-type-json.svg)](https://hysenlabs.com/projects/taion-graphql-type-json)

Was this useful?