grafana/k6 · error · TypeError
invalid value for metric tag '%s': only String, Boolean and
Error message
invalid value for metric tag '%s': only String, Boolean and Number types are accepted as a metric tag values
What it means
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.
Source
Thrown at internal/js/modules/k6/execution/execution.go:374
state *lib.State
}
// Get a property value for the key. May return nil if the property does not exist.
func (o *tagsDynamicObject) Get(key string) sobek.Value {
tcv := o.state.Tags.GetCurrentValues()
if tag, ok := tcv.Tags.Get(key); ok {
return o.runtime.ToValue(tag)
}
return nil
}
// Set a property value for the key. It returns true if succeed. String, Boolean
// and Number types are implicitly converted to the Sobek's relative string
// representation. An exception is raised in case a denied type is provided.
func (o *tagsDynamicObject) Set(key string, val sobek.Value) bool {
o.state.Tags.Modify(func(tagsAndMeta *metrics.TagsAndMeta) {
if err := common.ApplyCustomUserTag(tagsAndMeta, key, val); err != nil {
panic(o.runtime.NewTypeError(err.Error()))
}
})
return true
}
// Has returns true if the property exists.
func (o *tagsDynamicObject) Has(key string) bool {
ctv := o.state.Tags.GetCurrentValues()
if _, ok := ctv.Tags.Get(key); ok {
return true
}
return false
}
// Delete deletes the property for the key. It returns true on success (note,
// that includes missing property).
func (o *tagsDynamicObject) Delete(key string) bool {
o.state.Tags.Modify(func(tagsAndMeta *metrics.TagsAndMeta) {View on GitHub (pinned to 93accf6570)
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
Example fix
// before vu.tags['claims'] = jwtPayload; // object -> TypeError: invalid value for metric tag 'claims' // after vu.tags['claims'] = JSON.stringify(jwtPayload); // better (low cardinality): vu.tags['tenant'] = String(jwtPayload.tenant_id);
Defensive patterns
Strategy: validation
Validate before calling
function setTag(tags, key, value) {
if (value == null) return; // skip null/undefined
const t = typeof value;
tags[key] = (t === 'string' || t === 'number' || t === 'boolean') ? value : JSON.stringify(value);
}
setTag(vu.tags, 'tenant', payload.tenant); Type guard
const isPrimitiveTagValue = v => v != null && ['string','number','boolean'].includes(typeof v);
Try / catch
try { vu.tags[key] = value; } catch (e) { if (/metric tag/.test(e.message)) vu.tags[key] = JSON.stringify(value); else throw e; } Prevention
- 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
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- metric tags: %w
- Unexpected end of selector while parsing selector `${selecto
- cannot add tags for the '%s' custom metric: %w
- invalid WebSocket tags option: %w
- Error while parsing selector `${selector}`: ${e.message}
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/3345bd17819a651b.
Report an issue: GitHub.