An implementation of GraphQL in Go. Follows the official reference implementation graphql-js.
Supports: queries, mutations & subscriptions.
This is the IodeSystems fork of graphql-go/graphql.
Current with upstream as of 6acef35 (2026-06-22). Every upstream commit through that point is either absorbed here or recorded with a reason for refusing it, and every open upstream PR has been triaged. UPSTREAM.md holds the ledger and the sync procedure; bin/upstream reports anything new.
Work flows both ways. Upstream merged this fork's query-plan cache (#740), so PlanQuery / ExecutePlan now ship upstream as well; an upstream fix to that same code (#747) came back here.
What this fork adds on top of upstream today:
- Byte-path request entry points (
DoWriter,DoAppend) — write the response body straight to anio.Writeror your own buffer, skipping themap[string]interface{}result tree and the marshal passDorequires. No schema changes: built-in scalars emit natively, custom scalars fall back automatically, introspection routes to the slow path on its own. End-to-end on a 100-field query, no plan cache: 1.52× faster, 2.4× less memory, 36% fewer allocations thanDo+json.Marshal. Against the executor alone (cached plan) the append walker is 3.8× with 87× less garbage. Servers should default toDoWriter;Doremains for callers that want a Go value to inspect or filter. - Resolver-side append API (
Field.ResolveAppend,ScalarConfig.AppendJSON) — a further ~1.55× on top of the above, and the only part that asks anything of schema authors: aResolveAppendresolver emits its own JSON bytes and the executor does not inspect them. Experimental; the signature may change before this fork's 1.0. - Substantial parser and executor perf work — parser geomean 2.25× faster with 91% fewer allocations. See the Performance section below.
- Pooled resolver arguments and assorted execution-path tuning, applied transparently.
Source-compatible except for one API removal, plus two defaults that change behavior without changing signatures.
Removed: ResolveInfo.Path and the ResponsePath type. Responses are unaffected — errors[].path is still populated — so the only thing that stops compiling is a resolver reading info.Path.
Changed default. RetainArgs is an ExecuteParams flag; setting it restores upstream behavior:
| Flag | Default | Difference from upstream |
|---|---|---|
RetainArgs |
false |
ResolveParams.Args is pooled and reused across resolver calls. Read p.Args freely, but do not retain it past the call — in a struct field, channel, or goroutine. Set true if you do: there is no compile error and no panic, just wrong data. |
Two further differences need no opt-in and should only ever return more data, never less: introspection returns __schema.types and __type.fields in sorted order (upstream's ordering is map-random), and DefaultResolveFn resolves fields promoted from embedded structs following Go's own promotion rules (upstream returns null for them).
godoc: https://pkg.go.dev/github.com/IodeSystems/graphql-go/v2
Comparison against upstream/master. Regenerate with bin/perf (see bin/perf for usage).
Generated by bin/perf on 2026-09-01. A=upstream/master (6acef35), B=HEAD (d664994). B is 76 commits ahead of A, 0 behind. count=6, benchtime=2s.
40 benchmarks across 4 group(s).
execute — sec/op (× = base/val; >1× faster)
| Benchmark | 6acef35 | d664994 |
|---|---|---|
| ListQuery_1-24 | 161.46µs (1.00x) | 155.27µs (~) |
| ListQuery_100-24 | 625.89µs (1.00x) | 482.82µs (1.30x) |
| ListQuery_1K-24 | 4.82ms (1.00x) | 3.22ms (1.49x) |
| ListQuery_10K-24 | 49.51ms (1.00x) | 32.76ms (1.51x) |
| ListQuery_100K-24 | 322.36ms (1.00x) | 215.55ms (1.50x) |
| WideQuery_1_1-24 | 112.89µs (1.00x) | 105.23µs (1.07x) |
| WideQuery_10_1-24 | 271.22µs (1.00x) | 277.76µs (~) |
| WideQuery_100_1-24 | 1.85ms (1.00x) | 1.75ms (1.06x) |
| WideQuery_1K_1-24 | 17.70ms (1.00x) | 17.29ms (~) |
| WideQuery_1_10-24 | 131.25µs (1.00x) | 116.28µs (1.13x) |
| WideQuery_10_10-24 | 383.86µs (1.00x) | 352.06µs (1.09x) |
| WideQuery_100_10-24 | 2.73ms (1.00x) | 2.51ms (1.09x) |
| WideQuery_1K_10-24 | 26.10ms (1.00x) | 24.21ms (1.08x) |
| UncachedExecute_WideQuery_100_10-24 | 2.76ms (1.00x) | 2.49ms (1.11x) |
| UncachedExecute_ListQuery_1K-24 | 5.01ms (1.00x) | 3.45ms (1.45x) |
| UncachedExecute_WideQuery_100_10_Varied-24 | 5.48ms (1.00x) | 5.23ms (1.05x) |
| geomean | 2.49ms (1.00x) | 2.12ms (1.17x) |
Full benchstat output
goos: linux
goarch: amd64
pkg: root
cpu: AMD Ryzen 9 3900X 12-Core Processor
│ 6acef35-execute.txt │ d664994-execute.txt │
│ sec/op │ sec/op vs base │
ListQuery_1-24 161.5µ ± 2% 155.3µ ± 12% ~ (p=0.240 n=6)
ListQuery_100-24 625.9µ ± 4% 482.8µ ± 6% -22.86% (p=0.002 n=6)
ListQuery_1K-24 4.819m ± 4% 3.224m ± 8% -33.09% (p=0.002 n=6)
ListQuery_10K-24 49.51m ± 5% 32.76m ± 8% -33.83% (p=0.002 n=6)
ListQuery_100K-24 322.4m ± 6% 215.5m ± 3% -33.13% (p=0.002 n=6)
WideQuery_1_1-24 112.9µ ± 9% 105.2µ ± 7% -6.79% (p=0.026 n=6)
WideQuery_10_1-24 271.2µ ± 4% 277.8µ ± 8% ~ (p=0.394 n=6)
WideQuery_100_1-24 1.854m ± 5% 1.755m ± 5% -5.35% (p=0.041 n=6)
WideQuery_1K_1-24 17.70m ± 8% 17.29m ± 7% ~ (p=0.180 n=6)
WideQuery_1_10-24 131.2µ ± 3% 116.3µ ± 4% -11.41% (p=0.002 n=6)
WideQuery_10_10-24 383.9µ ± 1% 352.1µ ± 5% -8.28% (p=0.002 n=6)
WideQuery_100_10-24 2.734m ± 7% 2.514m ± 6% -8.03% (p=0.002 n=6)
WideQuery_1K_10-24 26.10m ± 6% 24.21m ± 3% -7.26% (p=0.002 n=6)
UncachedExecute_WideQuery_100_10-24 2.760m ± 3% 2.493m ± 7% -9.68% (p=0.002 n=6)
UncachedExecute_ListQuery_1K-24 5.008m ± 6% 3.453m ± 10% -31.04% (p=0.002 n=6)
UncachedExecute_WideQuery_100_10_Varied-24 5.483m ± 13% 5.233m ± 10% -4.56% (p=0.041 n=6)
geomean 2.486m 2.123m -14.61%
│ 6acef35-execute.txt │ d664994-execute.txt │
│ B/op │ B/op vs base │
UncachedExecute_WideQuery_100_10-24 1.157Mi ± 0% 1.077Mi ± 0% -6.92% (p=0.002 n=6)
UncachedExecute_ListQuery_1K-24 3.647Mi ± 0% 3.235Mi ± 0% -11.31% (p=0.002 n=6)
UncachedExecute_WideQuery_100_10_Varied-24 2.238Mi ± 0% 2.206Mi ± 0% -1.43% (p=0.002 n=6)
geomean 2.113Mi 1.973Mi -6.64%
│ 6acef35-execute.txt │ d664994-execute.txt │
│ allocs/op │ allocs/op vs base │
UncachedExecute_WideQuery_100_10-24 15.72k ± 0% 12.07k ± 0% -23.21% (p=0.002 n=6)
UncachedExecute_ListQuery_1K-24 34.55k ± 0% 18.76k ± 0% -45.70% (p=0.002 n=6)
UncachedExecute_WideQuery_100_10_Varied-24 26.94k ± 0% 23.66k ± 0% -12.19% (p=0.002 n=6)
geomean 24.46k 17.50k -28.46%
plan — sec/op (× = base/val; >1× faster)
| Benchmark | 6acef35 | d664994 |
|---|---|---|
| PlanQueryAbstractFan/types=8/depth=6-24 | 584ns (1.00x) | 674ns (0.87x) |
| PlanQueryAbstractFan/types=16/depth=8-24 | 585ns (1.00x) | 677ns (0.86x) |
| PlanQueryAbstractFan/types=32/depth=10-24 | 592ns (1.00x) | 675ns (0.88x) |
| PlannedExecute_WideQuery_100_10-24 | 980.74µs (1.00x) | 693.13µs (1.41x) |
| PlannedExecute_ListQuery_1K-24 | 4.68ms (1.00x) | 3.17ms (1.48x) |
| PlannedExecute_WideQuery_100_10_Varied-24 | 1.31ms (1.00x) | 1.05ms (1.26x) |
| PlanCache_HotLoop_NativeVars-24 | 1.32ms (1.00x) | 1.04ms (1.27x) |
| PlanCache_HotLoop_Normalized-24 | 1.90ms (1.00x) | 1.55ms (1.23x) |
| PlanCache_HotLoop_NoNorm-24 | 4.89ms (1.00x) | 4.37ms (1.12x) |
| PlannedExecute_WideQuery_100_10_StaticArg-24 | 1.31ms (1.00x) | 1.03ms (1.27x) |
| PlannedAppend_WideQuery_100_10-24 | 181.24µs | |
| PlannedAppend_ListQuery_1K-24 | 895.74µs | |
| PlannedAppend_WideQuery_100_10_Varied-24 | 263.09µs | |
| PlannedAppend_WideQuery_100_10_StaticArg-24 | 264.37µs | |
| PlannedAppendResolveAppend_WideQuery_100_10-24 | 117.58µs | |
| PlannedAppend_EnumList_1K-24 | 364.38µs | |
| PlannedAppend_DefaultResolve_Struct50-24 | 9.24µs | |
| geomean | 169.62µs (1.00x) | 157.64µs (1.14x) |
Blank cells: benchmark absent from that ref. The geomean × is benchstat's, computed over the benchmarks present in both; each column's geomean value covers only its own rows.
Full benchstat output
goos: linux
goarch: amd64
pkg: root
cpu: AMD Ryzen 9 3900X 12-Core Processor
│ 6acef35-plan.txt │ d664994-plan.txt │
│ sec/op │ sec/op vs base │
PlanQueryAbstractFan/types=8/depth=6-24 584.2n ± 3% 674.1n ± 7% +15.38% (p=0.002 n=6)
PlanQueryAbstractFan/types=16/depth=8-24 585.2n ± 2% 676.8n ± 1% +15.64% (p=0.002 n=6)
PlanQueryAbstractFan/types=32/depth=10-24 591.8n ± 1% 674.9n ± 3% +14.04% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10-24 980.7µ ± 4% 693.1µ ± 6% -29.33% (p=0.002 n=6)
PlannedExecute_ListQuery_1K-24 4.682m ± 3% 3.166m ± 1% -32.39% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10_Varied-24 1.314m ± 4% 1.045m ± 1% -20.48% (p=0.002 n=6)
PlanCache_HotLoop_NativeVars-24 1.324m ± 1% 1.042m ± 3% -21.27% (p=0.002 n=6)
PlanCache_HotLoop_Normalized-24 1.899m ± 3% 1.546m ± 6% -18.58% (p=0.002 n=6)
PlanCache_HotLoop_NoNorm-24 4.889m ± 3% 4.368m ± 6% -10.66% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10_StaticArg-24 1.313m ± 8% 1.032m ± 3% -21.46% (p=0.002 n=6)
PlannedAppend_WideQuery_100_10-24 181.2µ ± 4%
PlannedAppend_ListQuery_1K-24 895.7µ ± 8%
PlannedAppend_WideQuery_100_10_Varied-24 263.1µ ± 3%
PlannedAppend_WideQuery_100_10_StaticArg-24 264.4µ ± 3%
PlannedAppendResolveAppend_WideQuery_100_10-24 117.6µ ± 3%
PlannedAppend_EnumList_1K-24 364.4µ ± 3%
PlannedAppend_DefaultResolve_Struct50-24 9.239µ ± 1%
geomean 169.6µ 157.6µ -12.60%
│ 6acef35-plan.txt │ d664994-plan.txt │
│ B/op │ B/op vs base │
PlanQueryAbstractFan/types=8/depth=6-24 336.0 ± 0% 392.0 ± 0% +16.67% (p=0.002 n=6)
PlanQueryAbstractFan/types=16/depth=8-24 336.0 ± 0% 392.0 ± 0% +16.67% (p=0.002 n=6)
PlanQueryAbstractFan/types=32/depth=10-24 336.0 ± 0% 392.0 ± 0% +16.67% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10-24 699.3Ki ± 0% 612.6Ki ± 0% -12.40% (p=0.002 n=6)
PlannedExecute_ListQuery_1K-24 3.605Mi ± 0% 3.191Mi ± 0% -11.48% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10_Varied-24 982.6Ki ± 0% 940.9Ki ± 0% -4.25% (p=0.002 n=6)
PlanCache_HotLoop_NativeVars-24 984.9Ki ± 0% 943.0Ki ± 0% -4.25% (p=0.002 n=6)
PlanCache_HotLoop_Normalized-24 1.204Mi ± 0% 1.167Mi ± 0% -3.13% (p=0.002 n=6)
PlanCache_HotLoop_NoNorm-24 2.237Mi ± 0% 2.205Mi ± 0% -1.44% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10_StaticArg-24 982.3Ki ± 0% 939.7Ki ± 0% -4.34% (p=0.002 n=6)
PlannedAppend_WideQuery_100_10-24 7.068Ki ± 0%
PlannedAppend_ListQuery_1K-24 62.77Ki ± 0%
PlannedAppend_WideQuery_100_10_Varied-24 6.933Ki ± 0%
PlannedAppend_WideQuery_100_10_StaticArg-24 6.567Ki ± 0%
PlannedAppendResolveAppend_WideQuery_100_10-24 3.940Ki ± 0%
PlannedAppend_EnumList_1K-24 31.89Ki ± 0%
PlannedAppend_DefaultResolve_Struct50-24 120.0 ± 0%
geomean 109.4Ki 33.27Ki +0.31%
│ 6acef35-plan.txt │ d664994-plan.txt │
│ allocs/op │ allocs/op vs base │
PlanQueryAbstractFan/types=8/depth=6-24 6.000 ± 0% 7.000 ± 0% +16.67% (p=0.002 n=6)
PlanQueryAbstractFan/types=16/depth=8-24 6.000 ± 0% 7.000 ± 0% +16.67% (p=0.002 n=6)
PlanQueryAbstractFan/types=32/depth=10-24 6.000 ± 0% 7.000 ± 0% +16.67% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10-24 7.040k ± 0% 3.791k ± 0% -46.15% (p=0.002 n=6)
PlannedExecute_ListQuery_1K-24 33.78k ± 0% 18.02k ± 0% -46.66% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10_Varied-24 7.871k ± 0% 5.580k ± 0% -29.11% (p=0.002 n=6)
PlanCache_HotLoop_NativeVars-24 7.884k ± 0% 5.591k ± 0% -29.08% (p=0.002 n=6)
PlanCache_HotLoop_Normalized-24 12.860k ± 0% 9.271k ± 0% -27.91% (p=0.002 n=6)
PlanCache_HotLoop_NoNorm-24 26.94k ± 0% 23.65k ± 0% -12.20% (p=0.002 n=6)
PlannedExecute_WideQuery_100_10_StaticArg-24 7.866k ± 0% 5.575k ± 0% -29.13% (p=0.002 n=6)
PlannedAppend_WideQuery_100_10-24 727.0 ± 0%
PlannedAppend_ListQuery_1K-24 2.007k ± 0%
PlannedAppend_WideQuery_100_10_Varied-24 515.0 ± 0%
PlannedAppend_WideQuery_100_10_StaticArg-24 511.0 ± 0%
PlannedAppendResolveAppend_WideQuery_100_10-24 707.0 ± 0%
PlannedAppend_EnumList_1K-24 1.008k ± 0%
PlannedAppend_DefaultResolve_Struct50-24 3.000 ± 0%
geomean 1.241k 654.1 -20.34%
parse — sec/op (× = base/val; >1× faster)
| Benchmark | 6acef35 | d664994 |
|---|---|---|
| Parse_Tiny-24 | 5.60µs (1.00x) | 3.34µs (1.68x) |
| Parse_Wide_100-24 | 51.93µs (1.00x) | 19.85µs (2.62x) |
| Parse_Wide_1K-24 | 490.61µs (1.00x) | 172.09µs (2.85x) |
| Parse_Wide_10K-24 | 6.98ms (1.00x) | 1.86ms (3.74x) |
| Parse_Deep_10-24 | 11.74µs (1.00x) | 6.53µs (1.80x) |
| Parse_Deep_100-24 | 173.93µs (1.00x) | 76.40µs (2.28x) |
| Parse_Args_100-24 | 696.09µs (1.00x) | 365.35µs (1.91x) |
| Parse_Args_1K-24 | 7.50ms (1.00x) | 4.13ms (1.82x) |
| geomean | 238.93µs (1.00x) | 106.08µs (2.25x) |
Full benchstat output
goos: linux
goarch: amd64
pkg: language/parser
cpu: AMD Ryzen 9 3900X 12-Core Processor
│ 6acef35-parse.txt │ d664994-parse.txt │
│ sec/op │ sec/op vs base │
Parse_Tiny-24 5.603µ ± 3% 3.341µ ± 2% -40.37% (p=0.002 n=6)
Parse_Wide_100-24 51.93µ ± 6% 19.85µ ± 3% -61.76% (p=0.002 n=6)
Parse_Wide_1K-24 490.6µ ± 4% 172.1µ ± 3% -64.92% (p=0.002 n=6)
Parse_Wide_10K-24 6.980m ± 4% 1.865m ± 7% -73.28% (p=0.002 n=6)
Parse_Deep_10-24 11.744µ ± 3% 6.534µ ± 1% -44.36% (p=0.002 n=6)
Parse_Deep_100-24 173.93µ ± 7% 76.40µ ± 5% -56.07% (p=0.002 n=6)
Parse_Args_100-24 696.1µ ± 1% 365.4µ ± 11% -47.51% (p=0.002 n=6)
Parse_Args_1K-24 7.498m ± 7% 4.131m ± 4% -44.91% (p=0.002 n=6)
geomean 238.9µ 106.1µ -55.60%
│ 6acef35-parse.txt │ d664994-parse.txt │
│ B/op │ B/op vs base │
Parse_Tiny-24 2.219Ki ± 0% 3.555Ki ± 0% +60.21% (p=0.002 n=6)
Parse_Wide_100-24 28.73Ki ± 0% 27.70Ki ± 0% -3.59% (p=0.002 n=6)
Parse_Wide_1K-24 271.5Ki ± 0% 232.2Ki ± 0% -14.49% (p=0.002 n=6)
Parse_Wide_10K-24 3.254Mi ± 0% 2.563Mi ± 0% -21.24% (p=0.002 n=6)
Parse_Deep_10-24 3.680Ki ± 0% 5.609Ki ± 0% +52.44% (p=0.002 n=6)
Parse_Deep_100-24 30.40Ki ± 0% 35.98Ki ± 0% +18.38% (p=0.002 n=6)
Parse_Args_100-24 241.1Ki ± 0% 216.1Ki ± 0% -10.37% (p=0.002 n=6)
Parse_Args_1K-24 2.320Mi ± 0% 2.004Mi ± 0% -13.61% (p=0.002 n=6)
geomean 88.30Ki 92.53Ki +4.79%
│ 6acef35-parse.txt │ d664994-parse.txt │
│ allocs/op │ allocs/op vs base │
Parse_Tiny-24 64.00 ± 0% 19.00 ± 0% -70.31% (p=0.002 n=6)
Parse_Wide_100-24 530.00 ± 0% 24.00 ± 0% -95.47% (p=0.002 n=6)
Parse_Wide_1K-24 5036.00 ± 0% 45.00 ± 0% -99.11% (p=0.002 n=6)
Parse_Wide_10K-24 50050.0 ± 0% 230.0 ± 0% -99.54% (p=0.002 n=6)
Parse_Deep_10-24 111.00 ± 0% 31.00 ± 0% -72.07% (p=0.002 n=6)
Parse_Deep_100-24 921.0 ± 0% 218.0 ± 0% -76.33% (p=0.002 n=6)
Parse_Args_100-24 6.967k ± 0% 2.155k ± 0% -69.07% (p=0.002 n=6)
Parse_Args_1K-24 69.07k ± 0% 21.20k ± 0% -69.31% (p=0.002 n=6)
geomean 2.128k 186.4 -91.24%
print — sec/op (× = base/val; >1× faster)
| Benchmark | 6acef35 | d664994 |
|---|---|---|
| Print-24 | 592.44µs (1.00x) | 592.10µs (~) |
| geomean | 592.44µs (1.00x) | 592.10µs (1.00x) |
Full benchstat output
goos: linux
goarch: amd64
pkg: language/printer
cpu: AMD Ryzen 9 3900X 12-Core Processor
│ 6acef35-print.txt │ d664994-print.txt │
│ sec/op │ sec/op vs base │
Print-24 592.4µ ± 4% 592.1µ ± 1% ~ (p=0.818 n=6)
Friendly reminder links are available in case you would like to contribute back into our commitment with Go and open-source.
| Author | PayPal Link |
|---|---|
| Hafiz Ismail | Not available yet. |
| Chris Ramón | https://www.paypal.com/donate/?hosted_button_id=WHUQQYEMTRQBJ |
To install the library, run:
go get github.com/IodeSystems/graphql-go/v2The following is a simple example which defines a schema with a single hello string-type field and a Resolve method which returns the string world. A GraphQL query is performed against this schema with the resulting output printed in JSON format.
package main
import (
"encoding/json"
"fmt"
"log"
"github.com/IodeSystems/graphql-go/v2"
)
func main() {
// Schema
fields := graphql.Fields{
"hello": &graphql.Field{
Type: graphql.String,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return "world", nil
},
},
}
rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
schema, err := graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
// Query
query := `
{
hello
}
`
params := graphql.Params{Schema: schema, RequestString: query}
r := graphql.Do(params)
if len(r.Errors) > 0 {
log.Fatalf("failed to execute graphql operation, errors: %+v", r.Errors)
}
rJSON, _ := json.Marshal(r)
fmt.Printf("%s \n", rJSON) // {"data":{"hello":"world"}}
}For more complex examples, refer to the examples/ directory and graphql_test.go.
| Name | Author | Description |
|---|---|---|
| graphql-go-handler | Hafiz Ismail | Middleware to handle GraphQL queries through HTTP requests. |
| graphql-relay-go | Hafiz Ismail | Lib to construct a graphql-go server supporting react-relay. |
| golang-relay-starter-kit | Hafiz Ismail | Barebones starting point for a Relay application with Golang GraphQL server. |
| dataloader | Nick Randall | DataLoader implementation in Go. |