{"record":{"id":"3345bd17819a651b","repo":"grafana/k6","slug":"invalid-value-for-metric-tag-s-only-string-bo","errorCode":null,"errorMessage":"invalid value for metric tag '%s': only String, Boolean and Number types are accepted as a metric tag values","messagePattern":"invalid value for metric tag '(.+?)': only String, Boolean and Number types are accepted as a metric tag values","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/execution/execution.go","lineNumber":374,"sourceCode":"\tstate   *lib.State\n}\n\n// Get a property value for the key. May return nil if the property does not exist.\nfunc (o *tagsDynamicObject) Get(key string) sobek.Value {\n\ttcv := o.state.Tags.GetCurrentValues()\n\tif tag, ok := tcv.Tags.Get(key); ok {\n\t\treturn o.runtime.ToValue(tag)\n\t}\n\treturn nil\n}\n\n// Set a property value for the key. It returns true if succeed. String, Boolean\n// and Number types are implicitly converted to the Sobek's relative string\n// representation. An exception is raised in case a denied type is provided.\nfunc (o *tagsDynamicObject) Set(key string, val sobek.Value) bool {\n\to.state.Tags.Modify(func(tagsAndMeta *metrics.TagsAndMeta) {\n\t\tif err := common.ApplyCustomUserTag(tagsAndMeta, key, val); err != nil {\n\t\t\tpanic(o.runtime.NewTypeError(err.Error()))\n\t\t}\n\t})\n\treturn true\n}\n\n// Has returns true if the property exists.\nfunc (o *tagsDynamicObject) Has(key string) bool {\n\tctv := o.state.Tags.GetCurrentValues()\n\tif _, ok := ctv.Tags.Get(key); ok {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Delete deletes the property for the key. It returns true on success (note,\n// that includes missing property).\nfunc (o *tagsDynamicObject) Delete(key string) bool {\n\to.state.Tags.Modify(func(tagsAndMeta *metrics.TagsAndMeta) {","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/execution/execution.go#L356-L392","documentation":"The execution module exposes vu.tags (and scenario-level tags) as a dynamic object whose Set trap pipes through ApplyCustomUserTag. That helper only accepts strings, booleans, and numbers, because metric tags must serialize into the samples every output receives; assigning an object, array, null, or undefined raises the TypeError shown, naming the offending tag key.","triggerScenarios":"vu.tags['user'] = { id: 1 } or vu.tags.envs = ['a','b']; also assigning the result of a function that returns undefined, or null from a lookup. Reads (Get) and existence checks (Has) are unaffected — only writes with non-primitive values fail, and the failure names the key in '%s'.","commonSituations":"Tagging with structured data (session objects, JWT payload claims parsed to an object) instead of a scalar field; spreading a config object into vu.tags where some values are nested; porting from k6 iterations where tags were set on individual HTTP calls with pre-stringified values; null flowing in after a failed JSON parse.","solutions":["Assign a primitive: vu.tags['user'] = String(obj.id) or JSON.stringify(obj) when you need the whole structure as one tag","Beware tag cardinality: prefer concise IDs over stringified JSON blobs, which bloat every sample","Whitelist-spread only scalar keys: for (const k of ['name','env']) vu.tags[k] = cfg[k]","Guard values: vu.tags[k] = v ?? 'unknown' to avoid null/undefined"],"exampleFix":"// before\nvu.tags['claims'] = jwtPayload; // object -> TypeError: invalid value for metric tag 'claims'\n\n// after\nvu.tags['claims'] = JSON.stringify(jwtPayload);\n// better (low cardinality):\nvu.tags['tenant'] = String(jwtPayload.tenant_id);","handlingStrategy":"validation","validationCode":"function setTag(tags, key, value) {\n  if (value == null) return; // skip null/undefined\n  const t = typeof value;\n  tags[key] = (t === 'string' || t === 'number' || t === 'boolean') ? value : JSON.stringify(value);\n}\nsetTag(vu.tags, 'tenant', payload.tenant);","typeGuard":"const isPrimitiveTagValue = v => v != null && ['string','number','boolean'].includes(typeof v);","tryCatchPattern":"try { vu.tags[key] = value; } catch (e) { if (/metric tag/.test(e.message)) vu.tags[key] = JSON.stringify(value); else throw e; }","preventionTips":["Route all dynamic tag writes through one primitive-checking helper","Stringify structured values once, at assignment time — and prefer short IDs over JSON blobs to limit cardinality","Null-check values from parses/fetches before tagging","Remember the same rule applies to tags passed to HTTP/WebSocket options"],"tags":["tags","metrics","execution","type-error"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}