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).
BindFields binds int16, uint8 and uint16 struct fields as Int where upstream binds them as String, and binds slices of them as [Int] where upstream panics. A schema built this way changes type for those fields. uint, uint32 and uint64 stay String, because values above 2³¹−1 do not fit Int.
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. |