Skip to main content

Tags and Groups

Tags and groups help you organize, filter, and analyze your k6 test metrics. They allow you to segment results by request type, scenario, user flow, or any custom dimension.

Tags

Tags are key-value pairs attached to metrics. They enable filtering and aggregation of test results.

Why Use Tags?

  • Filter metrics - Create thresholds for specific requests or scenarios
  • Organize results - Group related requests together
  • Track metadata - Add context like environment, version, or team
  • Analyze patterns - Identify performance issues by tag dimensions

System Tags

k6 automatically adds system tags to all metrics:
TagDescriptionExample
protoProtocol usedhttp/1.1, http/2, ws
statusHTTP status code200, 404, 500
methodHTTP methodGET, POST, PUT
urlFull URLhttps://quickpizza.grafana.com/api/menu
nameRequest name/URLhttps://quickpizza.grafana.com
groupGroup name::API Calls::Get Menu
checkCheck namestatus is 200
errorError message(if request failed)
error_codeError code1000, 1101
tls_versionTLS versiontls1.2, tls1.3
scenarioScenario nameapi_test, user_flow
serviceService nameapi, web

Controlling System Tags

Enable or disable system tags:
export let options = {
  systemTags: [
    'status',
    'method',
    'url',
    'name',
    'group',
    'check',
    'scenario',
  ],
};
Disabling system tags reduces memory usage and metrics cardinality, but limits filtering options.

Custom Tags

Global Tags

Apply tags to all metrics in the test:
export let options = {
  tags: {
    environment: 'staging',
    team: 'backend',
    version: 'v1.2.3',
    region: 'us-east-1',
  },
};

Request Tags

Add tags to specific HTTP requests:
import http from 'k6/http';
import { Trend } from 'k6/metrics';
import { check } from 'k6';

let myTrend = new Trend('my_trend');

export default function() {
  // Add tag to request metric data
  let res = http.get('http://httpbin.org/', { 
    tags: { 
      my_tag: "I'm a tag",
      endpoint: 'homepage',
      api_version: 'v2',
    } 
  });

  // Add tag to check
  check(res, { 
    'status is 200': (r) => r.status === 200 
  }, { 
    my_tag: "I'm a tag",
    check_type: 'status',
  });

  // Add tag to custom metric
  myTrend.add(res.timings.connecting, { 
    my_tag: "I'm a tag",
    phase: 'connection',
  });
}

Scenario Tags

Tag all metrics from a scenario:
export let options = {
  scenarios: {
    api_test: {
      executor: 'constant-vus',
      vus: 10,
      duration: '30s',
      tags: {
        test_type: 'api',
        flow: 'user_registration',
      },
    },
    browser_test: {
      executor: 'constant-vus',
      vus: 5,
      duration: '30s',
      tags: {
        test_type: 'browser',
        flow: 'checkout',
      },
    },
  },
};

Using Tags in Thresholds

Filter metrics by tags when defining thresholds:
import http from 'k6/http';

export let options = {
  thresholds: {
    // All HTTP requests
    'http_req_duration': ['p(95)<500'],
    
    // Only POST requests
    'http_req_duration{method:POST}': ['p(95)<800'],
    
    // Specific URL
    'http_req_duration{name:http://httpbin.org/post}': ['max<1000'],
    
    // Multiple tag filters
    'http_req_duration{method:GET,status:200}': ['p(99)<600'],
    
    // Custom tags
    'http_req_duration{endpoint:api}': ['p(95)<400'],
    
    // Scenario-specific
    'http_req_duration{scenario:api_test}': ['p(95)<500'],
  },
};

export default function() {
  http.get('http://httpbin.org/');
  http.post('http://httpbin.org/post', { data: 'some data' });
}
Tag values in threshold expressions are case-sensitive and must match exactly.

Groups

Groups organize requests into logical sections, similar to folders. They help structure test logic and create hierarchical metrics.

Basic Groups

import http from 'k6/http';
import { group, check } from 'k6';

export default function() {
  group('API Calls', function() {
    let res = http.get('https://quickpizza.grafana.com/api/menu');
    check(res, { 'status is 200': (r) => r.status === 200 });
  });
  
  group('Static Assets', function() {
    http.get('https://quickpizza.grafana.com/logo.png');
    http.get('https://quickpizza.grafana.com/style.css');
  });
}

Nested Groups

Create hierarchical organization:
import http from 'k6/http';
import { group, check } from 'k6';

export default function() {
  group('User Flow', function() {
    
    group('Login', function() {
      let loginRes = http.post('https://quickpizza.grafana.com/api/login', {
        username: '[email protected]',
        password: 'password123',
      });
      check(loginRes, { 'login successful': (r) => r.status === 200 });
    });
    
    group('Browse', function() {
      http.get('https://quickpizza.grafana.com/api/menu');
      
      group('View Item', function() {
        http.get('https://quickpizza.grafana.com/api/items/42');
      });
    });
    
    group('Checkout', function() {
      group('Add to Cart', function() {
        http.post('https://quickpizza.grafana.com/api/cart', {
          itemId: 42,
          quantity: 1,
        });
      });
      
      group('Complete Order', function() {
        http.post('https://quickpizza.grafana.com/api/orders');
      });
    });
    
  });
}
This creates a hierarchy like:
::User Flow
::User Flow::Login
::User Flow::Browse
::User Flow::Browse::View Item
::User Flow::Checkout
::User Flow::Checkout::Add to Cart
::User Flow::Checkout::Complete Order

Group Duration Metrics

k6 automatically tracks group duration:
export let options = {
  thresholds: {
    // Threshold for entire Login group
    'group_duration{group:::User Flow::Login}': ['p(95)<1000'],
    
    // Threshold for nested group
    'group_duration{group:::User Flow::Checkout::Complete Order}': ['p(99)<2000'],
  },
};

Combining Tags and Groups

import http from "k6/http";
import { check, group } from "k6";

export default function() {
  // GET request
  group("GET", function() {
    let res = http.get("http://httpbin.org/get?verb=get");
    check(res, {
      "status is 200": (r) => r.status === 200,
      "is verb correct": (r) => r.json().args.verb === "get",
    });
  });

  // POST request
  group("POST", function() {
    let res = http.post("http://httpbin.org/post", { verb: "post" });
    check(res, {
      "status is 200": (r) => r.status === 200,
      "is verb correct": (r) => r.json().form.verb === "post",
    });
  });

  // PUT request
  group("PUT", function() {
    let res = http.put(
      "http://httpbin.org/put", 
      JSON.stringify({ verb: "put" }), 
      { headers: { "Content-Type": "application/json" }}
    );
    check(res, {
      "status is 200": (r) => r.status === 200,
      "is verb correct": (r) => r.json().json.verb === "put",
    });
  });

  // DELETE request
  group("DELETE", function() {
    let res = http.del("http://httpbin.org/delete?verb=delete");
    check(res, {
      "status is 200": (r) => r.status === 200,
      "is verb correct": (r) => r.json().args.verb === "delete",
    });
  });
}

Tag and Group Best Practices

  1. Use consistent naming - Establish naming conventions for tags and groups
  2. Avoid high cardinality - Don’t use unique values (like user IDs) as tags
  3. Tag strategically - Add tags that help with analysis and debugging
  4. Group logically - Organize by user flow, API endpoint type, or feature
  5. Limit nesting depth - Keep group hierarchies shallow (2-3 levels max)
  6. Use scenario tags - Tag entire scenarios for easy filtering

Real-World Example

Here’s a complete example combining tags and groups:
import http from 'k6/http';
import { group, check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

let errorRate = new Rate('errors');

export let options = {
  scenarios: {
    api_flow: {
      executor: 'constant-vus',
      vus: 10,
      duration: '1m',
      tags: {
        test_type: 'api',
        flow: 'ecommerce',
      },
    },
  },
  tags: {
    environment: 'staging',
    version: 'v2.0.0',
  },
  thresholds: {
    'http_req_duration{endpoint:api}': ['p(95)<500'],
    'http_req_duration{group:::Browse::Search}': ['p(99)<800'],
    'checks{flow:purchase}': ['rate>0.95'],
    'errors': ['rate<0.05'],
  },
};

export function setup() {
  let loginRes = http.post('https://quickpizza.grafana.com/api/login', {
    username: '[email protected]',
    password: 'password123',
  }, {
    tags: { endpoint: 'api', operation: 'auth' }
  });
  
  return { token: loginRes.json('token') };
}

export default function(data) {
  let params = {
    headers: { 'Authorization': `Bearer ${data.token}` },
    tags: { endpoint: 'api' },
  };
  
  group('Browse', function() {
    
    group('Search', function() {
      let searchRes = http.get(
        'https://quickpizza.grafana.com/api/search?q=pizza',
        { ...params, tags: { ...params.tags, operation: 'search' } }
      );
      
      let searchOk = check(searchRes, {
        'search status 200': (r) => r.status === 200,
        'has results': (r) => r.json('results').length > 0,
      }, { flow: 'browse' });
      
      errorRate.add(!searchOk);
    });
    
    sleep(1);
  });
  
  group('Purchase', function() {
    
    let orderRes = http.post(
      'https://quickpizza.grafana.com/api/orders',
      JSON.stringify({ items: [{ id: 42, quantity: 1 }] }),
      { 
        ...params, 
        tags: { ...params.tags, operation: 'create_order' },
        headers: { ...params.headers, 'Content-Type': 'application/json' },
      }
    );
    
    let orderOk = check(orderRes, {
      'order created': (r) => r.status === 201,
      'has order id': (r) => r.json('orderId') !== undefined,
    }, { flow: 'purchase' });
    
    errorRate.add(!orderOk);
    
    sleep(1);
  });
}

Metrics Explorer Integration

Tags enable powerful filtering in metrics backends:
// Grafana query examples:
// - http_req_duration{scenario="api_test"}
// - http_req_duration{method="POST",status="200"}
// - group_duration{group=~".*Checkout.*"}

export let options = {
  tags: {
    team: 'platform',
    service: 'api',
    environment: 'production',
  },
};

Next Steps

Build docs developers (and LLMs) love