{"record":{"id":"ab9fb0b2530c4caa","repo":"grafana/k6","slug":"exported-setup-must-be-a-function","errorCode":null,"errorMessage":"exported 'setup' must be a function","messagePattern":"exported 'setup' must be a function","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/bundle.go","lineNumber":224,"sourceCode":"\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdec := json.NewDecoder(bytes.NewReader(data))\n\t\t\t\tdec.DisallowUnknownFields()\n\t\t\t\tif err = dec.Decode(&b.Options); err != nil {\n\t\t\t\t\tif uerr := json.Unmarshal(data, &b.Options); uerr != nil {\n\t\t\t\t\t\t// Beautify the error so we can try to show the user the key and potential value that is failing to parse\n\t\t\t\t\t\tuerr = beautifyOptionsJSONUnmarshalError(data, uerr)\n\t\t\t\t\t\terr = errext.WithAbortReasonIfNone(\n\t\t\t\t\t\t\terrext.WithExitCodeIfNone(uerr, exitcodes.InvalidConfig),\n\t\t\t\t\t\t\terrext.AbortedByScriptError,\n\t\t\t\t\t\t)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tb.preInitState.Logger.WithError(err).Warn(\"There were unknown fields in the options exported in the script\")\n\t\t\t\t\terr = nil\n\t\t\t\t}\n\t\t\tcase consts.SetupFn:\n\t\t\t\terr = errors.New(\"exported 'setup' must be a function\")\n\t\t\t\treturn\n\t\t\tcase consts.TeardownFn:\n\t\t\t\terr = errors.New(\"exported 'teardown' must be a function\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n\t<-ch\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(b.callableExports) == 0 {\n\t\treturn errors.New(\"no exported functions in script\")\n\t}\n\n\treturn nil\n}","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/bundle.go#L206-L242","documentation":"While scanning the entry module's exports, k6 special-cases the lifecycle names. An export named `setup` that is not a function cannot be invoked once before VU iterations, so the bundle loader rejects it immediately (bundle.go:224).","triggerScenarios":"`export const setup = 'x'`, `export let setup = 42`, or re-exporting a non-function binding named `setup` from the main module; fires during script load, before anything runs.","commonSituations":"Typos like `export function setups()`; refactors that turned setup into a config object or constant; codegen emitting `export const setup = {...}`.","solutions":["Declare it as a function: `export function setup() { ... }`","If the export is data, rename it so it does not collide with the reserved lifecycle name"],"exampleFix":"// before\nexport const setup = { token: 'abc' };\n\n// after\nexport const config = { token: 'abc' };\nexport function setup() { return config; }","handlingStrategy":"type-guard","validationCode":"if (typeof setup !== 'undefined' && typeof setup !== 'function') { throw new Error('setup export must be a function'); }","typeGuard":"const isValidSetup = (v) => v === undefined || typeof v === 'function';","tryCatchPattern":null,"preventionTips":["Keep reserved lifecycle names (setup, teardown, default, options, handleSummary) out of data exports","Lint scripts in CI for `export const setup` / `export const teardown` patterns","Run `k6 inspect script.js` locally — export-shape errors surface at load time before any traffic"],"tags":["javascript","script-structure","lifecycle","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}