<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Create a k6 extension on Grafana Labs</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/</link><description>Recent content in Create a k6 extension on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/k6/v2.3.x/extensions/create/index.xml" rel="self" type="application/rss+xml"/><item><title>k6 extension quick start guide</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/quickstart/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/quickstart/</guid><content><![CDATA[&lt;h1 id=&#34;k6-extension-quick-start-guide&#34;&gt;k6 extension quick start guide&lt;/h1&gt;
&lt;p&gt;This guide covers the first steps to developing k6 extensions. By following these step-by-step instructions, you will learn how to create a functional extension under your GitHub account, run a k6 test that uses it, and create a custom build of k6 using xk6.&lt;/p&gt;
&lt;h2 id=&#34;before-you-begin&#34;&gt;Before you begin&lt;/h2&gt;
&lt;p&gt;To follow along, you’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href=&#34;https://docs.github.com/en/get-started/start-your-journey/creating-an-account-on-github&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub account&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having a GitHub account simplifies the process of developing k6 extensions, which the guide will cover. &lt;a href=&#34;https://github.com/features/codespaces&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub Codespaces&lt;/a&gt; provides a streamlined development experience for k6 extensions, reducing the need for local setup.&lt;/p&gt;
&lt;h2 id=&#34;create-a-github-repository&#34;&gt;Create a GitHub repository&lt;/h2&gt;
&lt;p&gt;The first step is to create a GitHub repository using the &lt;a href=&#34;https://github.com/grafana/xk6-example&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;grafana/xk6-example&lt;/a&gt; template repository. This can be done interactively in a browser by clicking &lt;a href=&#34;https://github.com/new?template_name=xk6-example&amp;amp;template_owner=grafana&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;here&lt;/a&gt;. Name the repository &lt;code&gt;xk6-quickstart&lt;/code&gt;, and set the visibility to &lt;strong&gt;Public&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Alternatively, use the &lt;a href=&#34;https://cli.github.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub CLI&lt;/a&gt; to create the repository.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;gh repo create -p grafana/xk6-example -d &amp;#34;Experimental k6 extension&amp;#34; --public xk6-quickstart&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;create-a-codespace&#34;&gt;Create a codespace&lt;/h2&gt;
&lt;p&gt;GitHub Codespaces is a GitHub feature that lets you create and use a fully configured development environment in the cloud.&lt;/p&gt;
&lt;p&gt;To create a GitHub Codespace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to the &lt;code&gt;xk6-quickstart&lt;/code&gt; repository you created in the previous step&lt;/li&gt;
&lt;li&gt;On the repository page, click the green &lt;code&gt;Code&lt;/code&gt; button and then select &lt;code&gt;Codespaces&lt;/code&gt; from the dropdown menu.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Create new codespace&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the codespace is ready, it will open in your browser as a Visual Studio Code-like environment, letting you begin working on your project with the repository code already checked out.&lt;/p&gt;
&lt;p&gt;Alternatively, use the &lt;a href=&#34;https://cli.github.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub CLI&lt;/a&gt; to create the codespace, replacing &lt;code&gt;USER&lt;/code&gt; with your GitHub username:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;gh codespace create --repo USER/xk6-quickstart --web&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;run-the-example-script&#34;&gt;Run the example script&lt;/h2&gt;
&lt;p&gt;The repository&amp;rsquo;s root directory includes a &lt;code&gt;script.js&lt;/code&gt; file. When developing k6 extensions, use the &lt;code&gt;xk6 run&lt;/code&gt; command instead of &lt;code&gt;k6 run&lt;/code&gt; to execute your scripts.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;xk6 run script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;xk6 will run &lt;code&gt;script.js&lt;/code&gt; without creating an executable k6 file in the current directory.&lt;/p&gt;
&lt;h2 id=&#34;build-a-custom-k6&#34;&gt;Build a custom k6&lt;/h2&gt;
&lt;p&gt;To use a custom k6 extension, such as &lt;code&gt;xk6-quickstart&lt;/code&gt;, a custom k6 binary must be built using the &lt;code&gt;xk6 build&lt;/code&gt; subcommand.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;xk6 build --with github.com/USER/xk6-quickstart=.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Replace &lt;code&gt;USER&lt;/code&gt; with your GitHub username. This command builds a custom k6 executable in the current directory, including the &lt;code&gt;xk6-quickstart&lt;/code&gt; extension, using the current directory as the extension&amp;rsquo;s source.&lt;/p&gt;
&lt;p&gt;To execute the example k6 script, use the newly built k6 executable by running the following command.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 run script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;customize-the-extension&#34;&gt;Customize the extension&lt;/h2&gt;
&lt;p&gt;You can modify the Go source code to change the extension&amp;rsquo;s functionality, and use new functionality from JavaScript.&lt;/p&gt;
&lt;p&gt;The repository also includes a smoke test, Makefile, and Go test files that you can inspect and update as you change the extension’s code.&lt;/p&gt;
&lt;h2 id=&#34;set-up-a-local-development-environment&#34;&gt;Set up a local development environment&lt;/h2&gt;
&lt;p&gt;While using a GitHub codespace in the browser is a good starting point, you can also set up a local development environment for a better developer experience.&lt;/p&gt;
&lt;h4 id=&#34;before-you-begin-1&#34;&gt;Before you begin&lt;/h4&gt;
&lt;p&gt;To create a local development environment, you’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://code.visualstudio.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Visual Studio Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Visual Studio Code Dev Containers extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;clone-and-open-the-example-repository&#34;&gt;Clone and open the example repository&lt;/h3&gt;
&lt;p&gt;First, clone the &lt;code&gt;xk6-quickstart&lt;/code&gt; repository to your machine and open it in Visual Studio Code. Make sure to replace &lt;code&gt;USER&lt;/code&gt; with your GitHub username:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;git clone https://github.com/USER/xk6-quickstart.git
code xk6-quickstart&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Visual Studio Code will detect the &lt;a href=&#34;https://containers.dev/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;development container&lt;/a&gt; configuration and show a pop-up to open the project in a dev container. Accept the prompt and the project opens in the dev container, and the container image is rebuilt if necessary.&lt;/p&gt;
&lt;p&gt;After that, you can follow the same instructions as &lt;a href=&#34;#run-the-example-script&#34;&gt;Run the example script&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Refer to &lt;a href=&#34;https://code.visualstudio.com/docs/devcontainers/containers&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Developing inside a Container&lt;/a&gt; for more details about dev containers.&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;You have created a &lt;code&gt;xk6-quickstart&lt;/code&gt; extension under your own GitHub account using the extension template, and learned how to build a custom k6 binary with the extension and run a test.&lt;/p&gt;
&lt;p&gt;Next, explore the &lt;a href=&#34;/docs/k6/latest/extensions/explore/&#34;&gt;extensions catalog&lt;/a&gt; to see which extensions are available, and to learn more about how to build your custom extension.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/grafana/xk6-example&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;grafana/xk6-example&lt;/a&gt; GitHub template streamlines the creation of k6 extensions, letting you easily expand k6&amp;rsquo;s JavaScript features. If you&amp;rsquo;re looking to extend k6&amp;rsquo;s output functionalities instead, the &lt;a href=&#34;https://github.com/grafana/xk6-output-example&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;grafana/xk6-output-example&lt;/a&gt; GitHub template provides a similar, easy-to-use starting point.&lt;/p&gt;
]]></content><description>&lt;h1 id="k6-extension-quick-start-guide">k6 extension quick start guide&lt;/h1>
&lt;p>This guide covers the first steps to developing k6 extensions. By following these step-by-step instructions, you will learn how to create a functional extension under your GitHub account, run a k6 test that uses it, and create a custom build of k6 using xk6.&lt;/p></description></item><item><title>Create an extension with a template</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/create-extension-with-template/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/create-extension-with-template/</guid><content><![CDATA[&lt;h1 id=&#34;create-an-extension-with-a-template&#34;&gt;Create an extension with a template&lt;/h1&gt;
&lt;p&gt;This guide explains a step-by-step process for creating a k6 extension using the GitHub k6 extension template repository.&lt;/p&gt;
&lt;p&gt;In this guide, you’ll learn how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a GitHub repository using the k6 extension template repository.&lt;/li&gt;
&lt;li&gt;Create a TypeScript declaration file to document your API.&lt;/li&gt;
&lt;li&gt;Create &lt;a href=&#34;https://en.wikipedia.org/wiki/Ascii85&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;ascii85&lt;/a&gt; encoding and decoding implementation.&lt;/li&gt;
&lt;li&gt;Build a k6 binary with the extension.&lt;/li&gt;
&lt;li&gt;Use the custom k6 binary to run a test.&lt;/li&gt;
&lt;li&gt;Best practices for creating tests, checking for security vulnerabilities, and static analysis for your extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this guide, you’ll implement two functions that handle ascii85 encoding, which is a feature that’s not natively supported by k6. This will be implemented using Go.&lt;/p&gt;
&lt;h2 id=&#34;before-you-begin&#34;&gt;Before you begin&lt;/h2&gt;
&lt;p&gt;To follow along, you’ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;a href=&#34;https://docs.github.com/en/get-started/start-your-journey/creating-an-account-on-github&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub account&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having a GitHub account simplifies the process of developing k6 extensions, which the guide will cover. &lt;a href=&#34;https://github.com/features/codespaces&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub Codespaces&lt;/a&gt; provides a streamlined development experience for k6 extensions, reducing the need for local setup.&lt;/p&gt;
&lt;h2 id=&#34;create-a-github-repository&#34;&gt;Create a GitHub repository&lt;/h2&gt;
&lt;p&gt;The first step is to create a GitHub repository using the &lt;a href=&#34;https://github.com/grafana/xk6-example&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;grafana/xk6-example&lt;/a&gt; template repository. This can be done interactively in a browser by clicking &lt;a href=&#34;https://github.com/new?template_name=xk6-example&amp;amp;template_owner=grafana&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;here&lt;/a&gt;. Name the repository &amp;ldquo;xk6-example-ascii85&amp;rdquo;, and set the visibility to &lt;strong&gt;Public&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Alternatively, use the &lt;a href=&#34;https://cli.github.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub CLI&lt;/a&gt; to create the repository.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;gh repo create -p grafana/xk6-example -d &amp;#34;Experimental k6 extension&amp;#34; --public xk6-example-ascii85&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;create-a-codespace&#34;&gt;Create a codespace&lt;/h2&gt;
&lt;p&gt;GitHub Codespaces is a GitHub feature that lets you create and use a fully configured development environment in the cloud.&lt;/p&gt;
&lt;p&gt;To create a GitHub codespace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to the xk6-example-ascii85 repository you created in the previous step.&lt;/li&gt;
&lt;li&gt;On the repository page, click the green &lt;strong&gt;Code&lt;/strong&gt; button and then select &lt;strong&gt;Codespaces&lt;/strong&gt; from the dropdown menu.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create new codespace&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the codespace is ready, it will open in your browser as a Visual Studio Code-like environment, letting you begin working on your project with the repository code already checked out.&lt;/p&gt;
&lt;p&gt;Alternatively, use the &lt;a href=&#34;https://cli.github.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub CLI&lt;/a&gt; to create the codespace, replacing &lt;code&gt;USER&lt;/code&gt; with your GitHub username:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;gh codespace create --repo USER/xk6-example-ascii85 --web&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;api-declaration&#34;&gt;API declaration&lt;/h2&gt;
&lt;p&gt;This step is optional but recommended. It is a good practice to document the API of the k6 extension before implementing it.&lt;/p&gt;
&lt;p&gt;Create a TypeScript declaration file named &lt;code&gt;index.d.ts&lt;/code&gt; and add the following code:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;typescript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-typescript&#34;&gt;/**
 * **Example ascii85 encoding for k6**
 *
 * @module example_ascii85
 */

export as namespace example_ascii85;

/**
 * ascii85encode returns the ASCII85 encoding of src.
 *
 * @param src The input to encode.
 */
export declare function encode(src: ArrayBuffer): string;

/**
 * ascii85decode returns the decoded bytes represented by the string str.
 *
 * @param str The string to decode.
 */
export declare function decode(str: string): ArrayBuffer;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;add-encoding-and-decoding-functions&#34;&gt;Add encoding and decoding functions&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;encode()&lt;/code&gt; function&amp;rsquo;s implementation is straightforward, as the k6 runtime handles all type conversions. The Go standard &lt;code&gt;ascii85&lt;/code&gt; package provides the ASCII85 encoding implementation, requiring only a parameter for its use.&lt;/p&gt;
&lt;p&gt;Add the following function to the &lt;code&gt;module.go&lt;/code&gt; file. The &lt;code&gt;ascii85&lt;/code&gt; package import will be added automatically by the IDE.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;func (*module) encode(data []byte) string {
    dst := make([]byte, ascii85.MaxEncodedLen(len(data)))
    n := ascii85.Encode(dst, data)

    return string(dst[:n])
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;decode()&lt;/code&gt; function should return an &lt;code&gt;ArrayBuffer&lt;/code&gt;, which requires type conversion by the JavaScript runtime. The &lt;code&gt;sobek.ArrayBuffer&lt;/code&gt; go struct corresponds to the JavaScript &lt;code&gt;ArrayBuffer&lt;/code&gt;, so an instance of it must be returned. Refer to the &lt;a href=&#34;https://pkg.go.dev/github.com/grafana/sobek#Runtime.ExportTo&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;sobek.Runtime#ExportTo()&lt;/a&gt; documentation for mapping details.&lt;/p&gt;
&lt;p&gt;Add the following function to the &lt;code&gt;module.go&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;func (m *module) decode(str string) (sobek.ArrayBuffer, error) {
    dst := make([]byte, len(str))

    n, _, err := ascii85.Decode(dst, []byte(str), true)
    if err != nil {
        return sobek.ArrayBuffer{}, err
    }

    return m.vu.Runtime().NewArrayBuffer(dst[:n]), nil
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To make the &lt;code&gt;encode()&lt;/code&gt; and &lt;code&gt;decode()&lt;/code&gt; functions usable within the JavaScript runtime, you have to export them. Add them to the exported symbols in the &lt;code&gt;module.go&lt;/code&gt; file.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;func (m *module) Exports() modules.Exports {
    return modules.Exports{
        Named: map[string]any{
            &amp;#34;encode&amp;#34;: m.encode,
            &amp;#34;decode&amp;#34;: m.decode,
        },
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The Go implementation of the extension is complete.&lt;/p&gt;
&lt;h2 id=&#34;build-a-custom-k6-binary&#34;&gt;Build a custom k6 binary&lt;/h2&gt;
&lt;p&gt;To use the &lt;code&gt;xk6-example-ascii85&lt;/code&gt; extension, a custom k6 build must be created using the &lt;code&gt;xk6 build&lt;/code&gt; subcommand.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;xk6 build --with github.com/USER/xk6-example-ascii85=.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Replace &lt;code&gt;USER&lt;/code&gt; with your GitHub username.&lt;/p&gt;
&lt;p&gt;This command creates a custom k6 executable in the current folder.&lt;/p&gt;
&lt;h2 id=&#34;run-a-test-with-the-custom-k6-binary&#34;&gt;Run a test with the custom k6 binary&lt;/h2&gt;
&lt;p&gt;To showcase the extension&amp;rsquo;s functionality, create a JavaScript file named &lt;code&gt;script.js&lt;/code&gt; and add the following code to it:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;js&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-js&#34;&gt;import { encode } from &amp;#39;k6/x/example_ascii85&amp;#39;;

export default function () {
  console.log(encode(new Uint8Array([72, 101, 108, 108, 111, 33]).buffer)); // 87cURD]o
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And then run the script using the custom k6 binary:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 run script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The script outputs &lt;code&gt;87cURD]o&lt;/code&gt; to the console. This string is the ascii85 encoded representation of &lt;code&gt;Hello!&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;best-practices&#34;&gt;Best practices&lt;/h2&gt;
&lt;h3 id=&#34;create-a-smoke-test&#34;&gt;Create a smoke test&lt;/h3&gt;
&lt;p&gt;For initial verification before writing comprehensive integration tests, you can create a basic smoke test in &lt;code&gt;test/smoke.test.js&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;js&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-js&#34;&gt;import { encode, decode } from &amp;#39;k6/x/example_ascii85&amp;#39;;
import { check } from &amp;#39;k6&amp;#39;;

export const options = {
  thresholds: {
    checks: [&amp;#39;rate==1&amp;#39;],
  },
};

export default function () {
  const bytes = new Uint8Array([72, 101, 108, 108, 111, 33]).buffer;

  check(encode(bytes), {
    encoded: (str) =&amp;gt; str == &amp;#39;87cURD]o&amp;#39;,
    reverse: (str) =&amp;gt; equal(bytes, decode(str)),
  });
}

const equal = (a, b) =&amp;gt; new Uint8Array(a).toString() === new Uint8Array(b).toString();&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This test ensures the correctness of ascii85 encoding and decoding. It uses a fixed &lt;code&gt;Hello!&lt;/code&gt; string as a test case for both encoding and decoding processes.&lt;/p&gt;
&lt;h3 id=&#34;create-go-module-tests&#34;&gt;Create Go module tests&lt;/h3&gt;
&lt;p&gt;Go tests offer the quickest method for verifying extension implementations. Standard unit testing practices apply. For a module-level integration test example, refer to the module_test.go file. This setup facilitates comprehensive integration testing between the Go implementation and the JavaScript runtime.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package example_ascii85

import (
    _ &amp;#34;embed&amp;#34;
    &amp;#34;testing&amp;#34;

    &amp;#34;github.com/stretchr/testify/require&amp;#34;
    &amp;#34;go.k6.io/k6/v2/js/modulestest&amp;#34;
)

func Test_module(t *testing.T) { //nolint:tparallel
    t.Parallel()

    runtime := modulestest.NewRuntime(t)
    err := runtime.SetupModuleSystem(map[string]any{importPath: new(rootModule)}, nil, nil)
    require.NoError(t, err)

    _, err = runtime.RunOnEventLoop(`let mod = require(&amp;#34;` &amp;#43; importPath &amp;#43; `&amp;#34;)`)
    require.NoError(t, err)

    tests := []struct {
        name  string
        check string
    }{
        // Add your test cases here
        // Example: {name: &amp;#34;myFunc()&amp;#34;, check: `mod.myFunc() == expectedValue`},
        {
            name:  &amp;#34;encode()&amp;#34;,
            check: `mod.encode(new Uint8Array([72, 101, 108, 108, 111, 33]).buffer) == &amp;#34;87cURD]o&amp;#34;`,
        },
    }
    for _, tt := range tests { //nolint:paralleltest
        t.Run(tt.name, func(t *testing.T) {
            got, err := runtime.RunOnEventLoop(tt.check)

            require.NoError(t, err)
            require.True(t, got.ToBoolean())
        })
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The provided test code creates an extension instance and integrates it into the JavaScript runtime, accessible as &lt;code&gt;mod&lt;/code&gt;. The JavaScript code defining the test is then executed within the JavaScript runtime&amp;rsquo;s event loop.&lt;/p&gt;
&lt;h3 id=&#34;generate-api-documentation&#34;&gt;Generate API documentation&lt;/h3&gt;
&lt;p&gt;You can generate HTML API documentation from the &lt;code&gt;index.d.ts&lt;/code&gt; API declaration file using &lt;a href=&#34;https://typedoc.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;TypeDoc&lt;/a&gt;. To do this, run the following command that creates the extension API documentation from the &lt;code&gt;index.d.ts&lt;/code&gt; file and saves it in the &lt;code&gt;build/docs&lt;/code&gt; directory.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;bun x typedoc --out build/docs&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;security-and-vulnerability&#34;&gt;Security and vulnerability&lt;/h3&gt;
&lt;p&gt;Ensure the Go source code of your k6 extension is checked for security vulnerabilities using the &lt;a href=&#34;https://github.com/securego/gosec&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;gosec&lt;/a&gt; tool. Like any Go project, security scanning is crucial for your extension&amp;rsquo;s codebase.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;gosec -quiet ./...&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Generally, extensions rely on external Go module dependencies. It is advisable to use the &lt;a href=&#34;https://github.com/golang/vuln&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;govulncheck&lt;/a&gt; tool to identify potential vulnerabilities within these dependencies.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;govulncheck ./...&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Security and vulnerability checks are a requirement for registering the extension in the &lt;a href=&#34;https://registry.k6.io&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 Extension Registry&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;static-analysis&#34;&gt;Static analysis&lt;/h3&gt;
&lt;p&gt;Analyzing the Go source code of your k6 extension statically can proactively identify subtle bugs. &lt;a href=&#34;https://golangci-lint.run/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;golangci-lint&lt;/a&gt; is a popular static code analysis tool that even can be used without configuration.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;golangci-lint run ./...&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;
&lt;p&gt;The complete Go source code (&lt;code&gt;module.go&lt;/code&gt;) for the extension implementation is provided for reference.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;// Package example_ascii85 contains the xk6-example-ascii85 extension.
package example_ascii85

import (
    &amp;#34;encoding/ascii85&amp;#34;

    &amp;#34;github.com/grafana/sobek&amp;#34;
    &amp;#34;go.k6.io/k6/v2/js/modules&amp;#34;
)

type rootModule struct{}

func (*rootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &amp;amp;module{vu}
}

type module struct {
    vu modules.VU
}

func (m *module) Exports() modules.Exports {
    return modules.Exports{
        Named: map[string]any{
            &amp;#34;encode&amp;#34;: m.encode,
            &amp;#34;decode&amp;#34;: m.decode,
        },
    }
}

func (*module) encode(data []byte) string {
    dst := make([]byte, ascii85.MaxEncodedLen(len(data)))
    n := ascii85.Encode(dst, data)

    return string(dst[:n])
}

func (m *module) decode(str string) (sobek.ArrayBuffer, error) {
    dst := make([]byte, len(str))

    n, _, err := ascii85.Decode(dst, []byte(str), true)
    if err != nil {
        return sobek.ArrayBuffer{}, err
    }

    return m.vu.Runtime().NewArrayBuffer(dst[:n]), nil
}

var _ modules.Module = (*rootModule)(nil)&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In addition, &lt;code&gt;register.go&lt;/code&gt; contains the registration of the extension with the k6 runtime.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package example_ascii85

import &amp;#34;go.k6.io/k6/v2/js/modules&amp;#34;

const importPath = &amp;#34;k6/x/example_ascii85&amp;#34;

func init() {
    modules.Register(importPath, new(rootModule))
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;reference-to-the-javascript-runtime&#34;&gt;Reference to the JavaScript runtime&lt;/h3&gt;
&lt;p&gt;In the k6 runtime, each VU (data structure representing a virtual user) has a dedicated JavaScript runtime instance, which can be accessed with the &lt;code&gt;Runtime()&lt;/code&gt; function.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;m.vu.Runtime()&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;additional-resources&#34;&gt;Additional resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 go API documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/github.com/grafana/sobek&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 JavaScript engine documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/grafana/xk6&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;xk6 - k6 extension development toolbox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="create-an-extension-with-a-template">Create an extension with a template&lt;/h1>
&lt;p>This guide explains a step-by-step process for creating a k6 extension using the GitHub k6 extension template repository.&lt;/p>
&lt;p>In this guide, you’ll learn how to:&lt;/p></description></item><item><title>JavaScript extensions</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/javascript-extensions/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/javascript-extensions/</guid><content><![CDATA[&lt;h1 id=&#34;javascript-extensions&#34;&gt;JavaScript extensions&lt;/h1&gt;
&lt;p&gt;Take advantage of Go&amp;rsquo;s speed, power, and efficiency while providing the flexibility of using JavaScript APIs
within your test scripts.&lt;/p&gt;
&lt;p&gt;By implementing k6 interfaces, you can close various gaps in your testing setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New network protocols&lt;/li&gt;
&lt;li&gt;Improved performance&lt;/li&gt;
&lt;li&gt;Features not supported by k6 core&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;before-you-start&#34;&gt;Before you start&lt;/h2&gt;
&lt;p&gt;To run this tutorial, you&amp;rsquo;ll need the following applications installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also need to install xk6:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;go install go.k6.io/xk6/cmd/xk6@latest&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;write-a-simple-extension&#34;&gt;Write a simple extension&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;First, set up a directory to work in:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir xk6-compare; cd xk6-compare; go mod init xk6-compare&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;In the directory, make a Go file for your JavaScript extension.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A simple JavaScript extension requires a struct that exposes methods called by the test script.&lt;/p&gt;
  &lt;!-- TODO: A better trivial example? A coin flip perhaps? --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package compare

import &amp;#34;fmt&amp;#34;

// Compare is the type for our custom API.
type Compare struct{
    ComparisonResult string // textual description of the most recent comparison
}

// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
func (c *Compare) IsGreater(a, b int) bool {
    if a &amp;gt; b {
        c.ComparisonResult = fmt.Sprintf(&amp;#34;%d is greater than %d&amp;#34;, a, b)
        return true
    } else {
        c.ComparisonResult = fmt.Sprintf(&amp;#34;%d is NOT greater than %d&amp;#34;, a, b)
        return false
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Register the module to use these from k6 test scripts.&lt;/li&gt;
&lt;/ol&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;k6 extensions must have the &lt;code&gt;k6/x/&lt;/code&gt; prefix,
and the short name must be unique among all extensions built in the same k6 binary.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;


&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;import &amp;#34;go.k6.io/k6/v2/js/modules&amp;#34;

// init is called by the Go runtime at application startup.
func init() {
    modules.Register(&amp;#34;k6/x/compare&amp;#34;, new(Compare))
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Save the file as something like &lt;code&gt;compare.go&lt;/code&gt;. The final code looks like this:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package compare

import (
    &amp;#34;fmt&amp;#34;
    &amp;#34;go.k6.io/k6/v2/js/modules&amp;#34;
)

// init is called by the Go runtime at application startup.
func init() {
    modules.Register(&amp;#34;k6/x/compare&amp;#34;, new(Compare))
}

// Compare is the type for our custom API.
type Compare struct{
    ComparisonResult string // textual description of the most recent comparison
}

// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
func (c *Compare) IsGreater(a, b int) bool {
    if a &amp;gt; b {
        c.ComparisonResult = fmt.Sprintf(&amp;#34;%d is greater than %d&amp;#34;, a, b)
        return true
    } else {
        c.ComparisonResult = fmt.Sprintf(&amp;#34;%d is NOT greater than %d&amp;#34;, a, b)
        return false
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;compile-your-extended-k6&#34;&gt;Compile your extended k6&lt;/h2&gt;
&lt;p&gt;To build a k6 binary with this extension, run this command:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;xk6 build --with xk6-compare=.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;When building from source code, &lt;code&gt;xk6-compare&lt;/code&gt; is the Go module name passed to &lt;code&gt;go mod init&lt;/code&gt;.
Usually, this would be a URL similar to &lt;code&gt;github.com/grafana/xk6-compare&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;use-your-extension&#34;&gt;Use your extension&lt;/h2&gt;
&lt;p&gt;Now, use the extension in a test script!&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Make a file with a name like &lt;code&gt;test.js&lt;/code&gt; then add this code:&lt;/li&gt;
&lt;/ol&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import compare from &amp;#39;k6/x/compare&amp;#39;;

export default function () {
  console.log(`${compare.isGreater(2, 1)}, ${compare.comparison_result}`);
  console.log(`${compare.isGreater(1, 3)}, ${compare.comparison_result}`);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Run the test with &lt;code&gt;./k6 run test.js&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It should output the following:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;INFO[0000] true, 2 is greater than 1                     source=console
INFO[0000] false, 1 is NOT greater than 3                source=console&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;use-the-advanced-module-api&#34;&gt;Use the advanced module API&lt;/h2&gt;


&lt;div class=&#34;admonition admonition-caution&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Caution&lt;/p&gt;&lt;p&gt;The advanced module API adds significant complexity to your extension. Consider whether you actually need access to internal
k6 objects before implementing this pattern. For simple modules that don&amp;rsquo;t need VU state or runtime access, the basic approach
shown above is sufficient.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Suppose your extension needs access to internal k6 objects to, for example, inspect the state of the test during execution.
We will need to make slightly more complicated changes to the above example.&lt;/p&gt;
&lt;p&gt;Our main &lt;code&gt;Compare&lt;/code&gt; struct should implement the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/js/modules#Instance&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;modules.Instance&lt;/code&gt;&lt;/a&gt; interface
to access the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/js/modules#VU&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;modules.VU&lt;/code&gt;&lt;/a&gt; to inspect internal k6 objects such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/lib#State&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;lib.State&lt;/code&gt;&lt;/a&gt;, the VU state with values like the VU ID and iteration number&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/github.com/grafana/sobek#Runtime&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;sobek.Runtime&lt;/code&gt;&lt;/a&gt;, the JavaScript runtime used by the VU&lt;/li&gt;
&lt;li&gt;a global &lt;code&gt;context.Context&lt;/code&gt; containing objects like the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/lib#ExecutionState&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;lib.ExecutionState&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, there should be a root module implementation of the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/js/modules#Module&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;modules.Module&lt;/code&gt;&lt;/a&gt;
interface to serve as a factory of &lt;code&gt;Compare&lt;/code&gt; instances for each VU.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what that would look like:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package compare

import (
    &amp;#34;fmt&amp;#34;
    &amp;#34;go.k6.io/k6/v2/js/modules&amp;#34;
)

// init is called by the Go runtime at application startup.
func init() {
    modules.Register(&amp;#34;k6/x/compare&amp;#34;, New())
}

type (
    // RootModule is the global module instance that will create module
    // instances for each VU.
    RootModule struct{}

    // ModuleInstance represents an instance of the JS module.
    ModuleInstance struct {
        // vu provides methods for accessing internal k6 objects for a VU
        vu modules.VU
        // comparator is the exported type
        comparator *Compare
    }
)

// Ensure the interfaces are implemented correctly.
var (
    _ modules.Instance = &amp;amp;ModuleInstance{}
    _ modules.Module   = &amp;amp;RootModule{}
)

// New returns a pointer to a new RootModule instance.
func New() *RootModule {
    return &amp;amp;RootModule{}
}

// NewModuleInstance implements the modules.Module interface returning a new instance for each VU.
func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &amp;amp;ModuleInstance{
        vu: vu,
        comparator: &amp;amp;Compare{vu: vu},
    }
}

// Compare is the type for our custom API.
type Compare struct{
    vu modules.VU           // provides methods for accessing internal k6 objects
    ComparisonResult string // textual description of the most recent comparison
}

// IsGreater returns true if a is greater than b, or false otherwise, setting textual result message.
func (c *Compare) IsGreater(a, b int) bool {
    if a &amp;gt; b {
        c.ComparisonResult = fmt.Sprintf(&amp;#34;%d is greater than %d&amp;#34;, a, b)
        return true
    } else {
        c.ComparisonResult = fmt.Sprintf(&amp;#34;%d is NOT greater than %d&amp;#34;, a, b)
    return false
    }
}

// Exports implements the modules.Instance interface and returns the exported types for the JS module.
func (mi *ModuleInstance) Exports() modules.Exports {
    return modules.Exports{
        Default: mi.comparator,
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Notice that we implemented the Module API and now &lt;code&gt;modules.Register&lt;/code&gt; the &lt;em&gt;root module&lt;/em&gt; rather than our &lt;em&gt;Compare&lt;/em&gt; object!&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;accessing-runtime-state&#34;&gt;Accessing runtime state&lt;/h2&gt;
&lt;p&gt;At this time, we&amp;rsquo;ve provided access to the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/js/modules#VU&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;modules.VU&lt;/code&gt;&lt;/a&gt; from the &lt;code&gt;Compare&lt;/code&gt;
type; however, we aren&amp;rsquo;t taking advantage of the methods provided. Here is a contrived example of how we can utilize the
runtime state:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;// InternalState holds basic metadata from the runtime state.
type InternalState struct {
	ActiveVUs       int64       `js:&amp;#34;activeVUs&amp;#34;`
	Iteration       int64
	VUID            uint64      `js:&amp;#34;vuID&amp;#34;`
	VUIDFromRuntime sobek.Value `js:&amp;#34;vuIDFromRuntime&amp;#34;`
}

// GetInternalState interrogates the current virtual user for state information.
func (c *Compare) GetInternalState() *InternalState {
	state := c.vu.State()
	ctx := c.vu.Context()
	es := lib.GetExecutionState(ctx)
	rt := c.vu.Runtime()

	return &amp;amp;InternalState{
		VUID:            state.VUID,
		VUIDFromRuntime: rt.Get(&amp;#34;__VU&amp;#34;),
		Iteration:       state.Iteration,
		ActiveVUs:       es.GetCurrentlyActiveVUsCount(),
	}
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Create a test script to utilize the new &lt;code&gt;getInternalState()&lt;/code&gt; function as in the following:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import compare from &amp;#39;k6/x/compare&amp;#39;;

export default function () {
  const state = compare.getInternalState();
  console.log(
    `Active VUs: ${state.activeVUs}, Iteration: ${state.iteration}, VU ID: ${state.vuID}, VU ID from runtime: ${state.vuIDFromRuntime}`
  );
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Executing the script as &lt;code&gt;./k6 run test-state.js --vus 2 --iterations 5&lt;/code&gt; will produce output similar to the following:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;INFO[0000] Active VUs: 2, Iteration: 0, VU ID: 2, VU ID from runtime: 2  source=console
INFO[0000] Active VUs: 2, Iteration: 0, VU ID: 1, VU ID from runtime: 1  source=console
INFO[0000] Active VUs: 2, Iteration: 1, VU ID: 2, VU ID from runtime: 2  source=console
INFO[0000] Active VUs: 2, Iteration: 1, VU ID: 1, VU ID from runtime: 1  source=console
INFO[0000] Active VUs: 2, Iteration: 2, VU ID: 2, VU ID from runtime: 2  source=console&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;things-to-keep-in-mind&#34;&gt;Things to keep in mind&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The code in the &lt;code&gt;default&lt;/code&gt; function (or another function specified by

    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/#options&#34;&gt;&lt;code&gt;exec&lt;/code&gt;&lt;/a&gt;) will be executed many
times during a test run and possibly in parallel by thousands of VUs.
Any operation of your extension should therefore be performant
and &lt;a href=&#34;https://en.wikipedia.org/wiki/Thread_safety&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;thread-safe&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Any &lt;em&gt;heavy&lt;/em&gt; initialization should be done in the 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/init-context/&#34;&gt;init context&lt;/a&gt;,
if possible, and not as part of the &lt;code&gt;default&lt;/code&gt; function execution.&lt;/li&gt;
&lt;li&gt;Use the registry&amp;rsquo;s &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/metrics#Registry.NewMetric&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;NewMetric&lt;/code&gt;&lt;/a&gt; method to create
custom metrics; to emit them, use &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/metrics#PushIfNotDone&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;metrics.PushIfNotDone()&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Questions? Feel free to join the discussion on extensions in the &lt;a href=&#34;https://community.grafana.com/c/grafana-k6/extensions/82&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 Community Forum&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Next, create an 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/create/output-extensions/&#34;&gt;Output extension&lt;/a&gt; to publish test metrics
to a destination not already supported by k6.&lt;/p&gt;
]]></content><description>&lt;h1 id="javascript-extensions">JavaScript extensions&lt;/h1>
&lt;p>Take advantage of Go&amp;rsquo;s speed, power, and efficiency while providing the flexibility of using JavaScript APIs
within your test scripts.&lt;/p>
&lt;p>By implementing k6 interfaces, you can close various gaps in your testing setup:&lt;/p></description></item><item><title>Output extensions</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/output-extensions/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/output-extensions/</guid><content><![CDATA[&lt;h1 id=&#34;output-extensions&#34;&gt;Output extensions&lt;/h1&gt;
&lt;p&gt;k6 provides many 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/metrics/&#34;&gt;metrics&lt;/a&gt; and 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/&#34;&gt;output formats&lt;/a&gt;, but it cannot directly support all possibilities.
To store or alter metrics captured during an active k6 test,
you can create a custom output extension.&lt;/p&gt;
&lt;p&gt;Output extension binaries can use the &lt;code&gt;--out&lt;/code&gt; flag to send metrics to a custom place.
Some potential reasons for a custom extension could include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To support a time-series database not already supported&lt;/li&gt;
&lt;li&gt;To add derived metrics data for storage&lt;/li&gt;
&lt;li&gt;To filter metrics to only the data you care about&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Like 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/create/javascript-extensions/&#34;&gt;JavaScript extensions&lt;/a&gt;,
output extensions rely on the extension author to implement specific APIs.&lt;/p&gt;
&lt;h2 id=&#34;before-you-start&#34;&gt;Before you start:&lt;/h2&gt;
&lt;p&gt;To run this tutorial, you&amp;rsquo;ll need the following applications installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also need to install xk6:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;go install go.k6.io/xk6/cmd/xk6@latest&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;write-a-simple-extension&#34;&gt;Write a simple extension&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Set up a directory to work in.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir xk6-output-logger; cd xk6-output-logger; go mod init xk6-output-logger&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;The core of an Output extension is a struct that implements the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#Output&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;output.Output&lt;/code&gt;&lt;/a&gt;
interface.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a simple example that outputs each set of metrics to the console as received by the &lt;code&gt;AddMetricSamples(samples []metrics.SampleContainer)&lt;/code&gt;
method of the output interface.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package log

import (
    &amp;#34;fmt&amp;#34;
    &amp;#34;strings&amp;#34;
    &amp;#34;time&amp;#34;

    &amp;#34;go.k6.io/k6/v2/metrics&amp;#34;
    &amp;#34;go.k6.io/k6/v2/output&amp;#34;
)

// AddMetricSamples receives metric samples from the k6 Engine as they&amp;#39;re emitted.
func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
    for _, sample := range samples {
        all := sample.GetSamples()
        fmt.Fprintf(l.out, &amp;#34;%s [%s]\n&amp;#34;, all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
    }
}

// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
func metricKeyValues(samples []metrics.Sample) string {
    names := make([]string, 0, len(samples))
    for _, sample := range samples {
        names = append(names, fmt.Sprintf(&amp;#34;%s=%v&amp;#34;, sample.Metric.Name, sample.Value))
    }
    return strings.Join(names, &amp;#34;, &amp;#34;)
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Register the module to use these from k6 test scripts.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;import &amp;#34;go.k6.io/k6/v2/output&amp;#34;

// init is called by the Go runtime at application startup.
func init() {
    output.RegisterExtension(&amp;#34;logger&amp;#34;, New)
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;You must use the registered with the &lt;code&gt;-o&lt;/code&gt;, or &lt;code&gt;--out&lt;/code&gt; flag when running k6!&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;The final extension code looks like this:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package log

import (
    &amp;#34;fmt&amp;#34;
    &amp;#34;io&amp;#34;
    &amp;#34;strings&amp;#34;
    &amp;#34;time&amp;#34;

    &amp;#34;go.k6.io/k6/v2/metrics&amp;#34;
    &amp;#34;go.k6.io/k6/v2/output&amp;#34;
)

// init is called by the Go runtime at application startup.
func init() {
    output.RegisterExtension(&amp;#34;logger&amp;#34;, New)
}

// Logger writes k6 metric samples to stdout.
type Logger struct {
    out io.Writer
}

// New returns a new instance of Logger.
func New(params output.Params) (output.Output, error) {
    return &amp;amp;Logger{params.StdOut}, nil
}

// Description returns a short human-readable description of the output.
func (*Logger) Description() string {
    return &amp;#34;logger&amp;#34;
}

// Start initializes any state needed for the output, establishes network
// connections, etc.
func (*Logger) Start() error {
    return nil
}

// AddMetricSamples receives metric samples from the k6 Engine as they&amp;#39;re emitted.
func (l *Logger) AddMetricSamples(samples []metrics.SampleContainer) {
    for _, sample := range samples {
        all := sample.GetSamples()
        fmt.Fprintf(l.out, &amp;#34;%s [%s]\n&amp;#34;, all[0].GetTime().Format(time.RFC3339Nano), metricKeyValues(all))
    }
}

// metricKeyValues returns a string of key-value pairs for all metrics in the sample.
func metricKeyValues(samples []metrics.Sample) string {
    names := make([]string, 0, len(samples))
    for _, sample := range samples {
        names = append(names, fmt.Sprintf(&amp;#34;%s=%v&amp;#34;, sample.Metric.Name, sample.Value))
    }
    return strings.Join(names, &amp;#34;, &amp;#34;)
}

// Stop finalizes any tasks in progress, closes network connections, etc.
func (*Logger) Stop() error {
    return nil
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice a couple of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The module initializer &lt;code&gt;New()&lt;/code&gt; receives an instance of
&lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#Params&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;output.Params&lt;/code&gt;&lt;/a&gt;.
With this object, the extension can access the output-specific configuration,
interfaces to the filesystem, synchronized stdout and stderr, and more.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;AddMetricSamples&lt;/code&gt; in this example writes to stdout. This output might have
to be buffered and flushed periodically in a real-world scenario to avoid memory
leaks. Below we&amp;rsquo;ll discuss some helpers you can use for that.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;compile-your-extended-k6&#34;&gt;Compile your extended k6&lt;/h2&gt;
&lt;p&gt;To build a k6 binary with this extension, run:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;xk6 build --with xk6-output-logger=.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;&lt;code&gt;xk6-output-logger&lt;/code&gt; is the Go module name passed to &lt;code&gt;go mod init&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Usually, this would be a URL similar to &lt;code&gt;github.com/grafana/xk6-output-logger&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;use-your-extension&#34;&gt;Use your extension&lt;/h2&gt;
&lt;p&gt;Now we can use the extension with a test script.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In new JavaScript file, make some simple test logic.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import http from &amp;#39;k6/http&amp;#39;;
import { sleep } from &amp;#39;k6&amp;#39;;

export default function () {
  http.get(&amp;#39;https://quickpizza.grafana.com&amp;#39;);
  sleep(0.5);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Now, run the test.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 run test.js --out logger --quiet --summary-mode=disabled --iterations 2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The &lt;code&gt;--out logger&lt;/code&gt; argument tells k6 to use your custom output. The flag
&lt;code&gt;--quiet --summary-mode=disabled&lt;/code&gt; configures k6 to show only custom output.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Your output should look something like this:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;2022-07-01T08:55:09.59272-05:00 [http_reqs=1, http_req_duration=117.003, http_req_blocked=558.983, http_req_connecting=54.135, http_req_tls_handshaking=477.198, http_req_sending=0.102, http_req_waiting=116.544, http_req_receiving=0.357, http_req_failed=0]
2022-07-01T08:55:09.917036-05:00 [vus=1, vus_max=1]
2022-07-01T08:55:10.094196-05:00 [data_sent=446, data_received=21364, iteration_duration=1177.505083, iterations=1]
2022-07-01T08:55:10.213926-05:00 [http_reqs=1, http_req_duration=119.122, http_req_blocked=0.015, http_req_connecting=0, http_req_tls_handshaking=0, http_req_sending=0.103, http_req_waiting=118.726, http_req_receiving=0.293, http_req_failed=0]
2022-07-01T08:55:10.715323-05:00 [data_sent=102, data_received=15904, iteration_duration=620.862459, iterations=1]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;things-to-keep-in-mind&#34;&gt;Things to keep in mind&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Output structs can optionally implement additional interfaces that allow them to
receive &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#WithThresholds&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;thresholds&lt;/a&gt; or
&lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#WithRunStatusUpdates&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;run-status updates&lt;/a&gt;
and even &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#WithTestRunStop&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;interrupt a test&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Consider using &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#SampleBuffer&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;output.SampleBuffer&lt;/code&gt;&lt;/a&gt;
and &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/output#PeriodicFlusher&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;output.PeriodicFlusher&lt;/code&gt;&lt;/a&gt;
to improve performance given the large amounts of data produced by k6. Refer to
&lt;a href=&#34;https://github.com/grafana/k6/blob/master/output/statsd/output.go&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;statsd&lt;/code&gt; output&lt;/a&gt; for an example.&lt;/li&gt;
&lt;li&gt;Use the &lt;a href=&#34;https://github.com/grafana/xk6-output-template&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;project template&lt;/a&gt; as a starting point
for your output extension.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Questions? Feel free to join the discussion on extensions in the &lt;a href=&#34;https://community.grafana.com/c/grafana-k6/extensions/82&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 Community Forum&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
]]></content><description>&lt;h1 id="output-extensions">Output extensions&lt;/h1>
&lt;p>k6 provides many
&lt;a href="/docs/k6/v2.3.x/using-k6/metrics/">metrics&lt;/a> and
&lt;a href="/docs/k6/v2.3.x/results-output/">output formats&lt;/a>, but it cannot directly support all possibilities.
To store or alter metrics captured during an active k6 test,
you can create a custom output extension.&lt;/p></description></item><item><title>Secret source extensions</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/secret-source_extensions/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/secret-source_extensions/</guid><content><![CDATA[&lt;h1 id=&#34;secret-source-extensions&#34;&gt;Secret Source extensions&lt;/h1&gt;
&lt;p&gt;Secret source extension allow users to write or use written by other users extensions in order to get secrets to be used within k6 tests.&lt;/p&gt;
&lt;p&gt;Like 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/create/javascript-extensions/&#34;&gt;JavaScript extensions&lt;/a&gt;,
secret source extensions rely on the extension author to implement specific APIs.&lt;/p&gt;
&lt;h2 id=&#34;before-you-start&#34;&gt;Before you start:&lt;/h2&gt;
&lt;p&gt;To run this tutorial, you&amp;rsquo;ll need the following applications installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also need to install xk6:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;$ go install go.k6.io/xk6/cmd/xk6@latest&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;write-a-simple-extension&#34;&gt;Write a simple extension&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Set up a directory to work in.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;$ mkdir xk6-secret-source-cli; cd xk6-secret-source-cli; go mod init xk6-secret-source-cli&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;The core of an Output extension is a struct that implements the &lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/secretsource#Source&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;secrets.Source&lt;/code&gt;&lt;/a&gt;
interface.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Create a simple example secret source that just reads the secrets from the cli flag - similar to the in built &lt;code&gt;mock&lt;/code&gt; one.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package cli

import (
	&amp;#34;errors&amp;#34;
	&amp;#34;fmt&amp;#34;
	&amp;#34;strings&amp;#34;

	&amp;#34;go.k6.io/k6/v2/secretsource&amp;#34;
)

func newCLISecretSourceFromParams(params secretsource.Params) (secretsource.Source, error) {
	list := strings.Split(params.ConfigArgument, &amp;#34;,&amp;#34;)
	secrets := make(map[string]string, len(list))
	for _, kv := range list {
		k, v, ok := strings.Cut(kv, &amp;#34;=&amp;#34;)
		if !ok {
			return nil, fmt.Errorf(&amp;#34;parsing %q, needs =&amp;#34;, kv)
		}

		secrets[k] = v
	}
	return &amp;amp;cliSecretSource{internal: secrets}
}

type cliSecretSource struct {
	internal map[string]string
}

func (*cliSecretSource) Description() string {
	return &amp;#34;this is an example secret source&amp;#34;
}

func (css *cliSecretSource) Get(key string) (string, error) {
	v, ok := css.internal[key]
	if !ok {
		return &amp;#34;&amp;#34;, errors.New(&amp;#34;no value&amp;#34;)
	}
	return v, nil
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Register the module to use these from k6 test scripts.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package cli

import &amp;#34;go.k6.io/k6/v2/secretsource&amp;#34;

// init is called by the Go runtime at application startup.
func init() {
	secretsource.RegisterExtension(&amp;#34;cli&amp;#34;, newCLISecretSourceFromParams)
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;You must use the registered with the &lt;code&gt;--secret-source&lt;/code&gt; flag when running k6!&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;The final extension code looks like this:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package cli

import (
	&amp;#34;errors&amp;#34;
	&amp;#34;fmt&amp;#34;
	&amp;#34;strings&amp;#34;

	&amp;#34;go.k6.io/k6/v2/secretsource&amp;#34;
)

// init is called by the Go runtime at application startup.
func init() {
	secretsource.RegisterExtension(&amp;#34;cli&amp;#34;, newCLISecretSourceFromParams)
}

func newCLISecretSourceFromParams(params secretsource.Params) (secretsource.Source, error) {
	list := strings.Split(params.ConfigArgument, &amp;#34;,&amp;#34;)
	secrets := make(map[string]string, len(list))
	for _, kv := range list {
		k, v, ok := strings.Cut(kv, &amp;#34;=&amp;#34;)
		if !ok {
			return nil, fmt.Errorf(&amp;#34;parsing %q, needs =&amp;#34;, kv)
		}

		secrets[k] = v
	}
	return &amp;amp;cliSecretSource{internal: secrets}, nil
}

type cliSecretSource struct {
	internal map[string]string
}

func (*cliSecretSource) Description() string {
	return &amp;#34;this is an example secret source&amp;#34;
}

func (css *cliSecretSource) Get(key string) (string, error) {
	v, ok := css.internal[key]
	if !ok {
		return &amp;#34;&amp;#34;, errors.New(&amp;#34;no value&amp;#34;)
	}
	return v, nil
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice a couple of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The module initializer &lt;code&gt;newCLISecretSourceFromParams()&lt;/code&gt; receives an instance of
&lt;a href=&#34;https://pkg.go.dev/go.k6.io/k6/v2/secretsource#Params&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;secretsource.Params&lt;/code&gt;&lt;/a&gt;.
With this object, the extension can access the secret source specific configuration,
interfaces to the filesystem, it cli args, logger, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Get&lt;/code&gt; in this example just returns from an internal array, but it also can do network requests or anything else it needs to get secrets.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;compile-your-extended-k6&#34;&gt;Compile your extended k6&lt;/h2&gt;
&lt;p&gt;To build a k6 binary with this extension, run:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;$ go mod tidy # this is needed by the go toolchain
$ xk6 build --with xk6-secret-source-cli=.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;&lt;code&gt;xk6-secret-source-cli&lt;/code&gt; is the Go module name passed to &lt;code&gt;go mod init&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Usually, this would be a URL similar to &lt;code&gt;github.com/grafana/xk6-secret-source-cli&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;use-your-extension&#34;&gt;Use your extension&lt;/h2&gt;
&lt;p&gt;Now we can use the extension with a test script.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In new JavaScript file, make some simple test logic.&lt;/li&gt;
&lt;/ol&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import secrets from &amp;#39;k6/secrets&amp;#39;;

export default async () =&amp;gt; {
  const my_secret = await secrets.get(&amp;#39;cool&amp;#39;); // get secret from a source with the provided identifier
  console.log(my_secret);
  await secrets.get(&amp;#39;else&amp;#39;); // get secret from a source with the provided identifier
  console.log(my_secret); // print the same old secret to see that the secret from above will also be redacted
  console.log(&amp;#39;some&amp;#39;); // log the original secret but using its constant value
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Now, run the test.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 run test.js --secret-source=cli=cool=some,else=console --quiet --summary-mode=disabled&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The &lt;code&gt;--secret-source=cli=cool=some,else=console&lt;/code&gt; argument tells k6 to use your custom secret source and gives its configuration.
The flag &lt;code&gt;--quiet --summary-mode=disabled&lt;/code&gt; configures k6 to show only custom output.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Your output should look something like this:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;INFO[0000] ***SECRET_REDACTED***                         source=console
INFO[0000] ***SECRET_REDACTED***                         source=&amp;#34;***SECRET_REDACTED***&amp;#34;
INFO[0000] ***SECRET_REDACTED***                         source=&amp;#34;***SECRET_REDACTED***&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As you can see there is quite a lot of redacted secrets. And also that &lt;code&gt;console&lt;/code&gt; is only redacted after it is accessed as before that k6 can not know it is actually a secret.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Real world secrets are not expected to be common strings that will be found naturally in the k6 output as is in this example.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;things-to-keep-in-mind&#34;&gt;Things to keep in mind&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Questions? Feel free to join the discussion on extensions in the &lt;a href=&#34;https://community.grafana.com/c/grafana-k6/extensions/82&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 Community Forum&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;
]]></content><description>&lt;h1 id="secret-source-extensions">Secret Source extensions&lt;/h1>
&lt;p>Secret source extension allow users to write or use written by other users extensions in order to get secrets to be used within k6 tests.&lt;/p></description></item><item><title>Subcommand extensions</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/subcommand-extensions/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/subcommand-extensions/</guid><content><![CDATA[&lt;h1 id=&#34;subcommand-extensions&#34;&gt;Subcommand extensions&lt;/h1&gt;
&lt;p&gt;k6 provides a rich set of built-in commands, but some use cases require custom CLI tools that integrate with k6&amp;rsquo;s runtime and state. Subcommand extensions allow you to register custom commands under the &lt;code&gt;k6 x&lt;/code&gt; namespace, providing a standardized way to extend k6&amp;rsquo;s CLI functionality.&lt;/p&gt;
&lt;p&gt;Subcommand extensions are useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup and configuration tools (for example, verifying system requirements)&lt;/li&gt;
&lt;li&gt;Custom validation and testing utilities&lt;/li&gt;
&lt;li&gt;Integration tools that interact with k6&amp;rsquo;s runtime state&lt;/li&gt;
&lt;li&gt;Helper commands specific to your testing infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use registered subcommand extensions directly without building a custom binary, or build a custom k6 binary for extensions you&amp;rsquo;re developing or that aren&amp;rsquo;t available in the registry.&lt;/p&gt;
&lt;h2 id=&#34;before-you-begin&#34;&gt;Before you begin&lt;/h2&gt;
&lt;p&gt;To run this tutorial, you&amp;rsquo;ll need the following applications installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://go.dev/doc/install&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/install/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You also need to install xk6:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;go install go.k6.io/xk6/cmd/xk6@latest&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;write-a-simple-extension&#34;&gt;Write a simple extension&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set up a directory to work in.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir xk6-subcommand-mytool; cd xk6-subcommand-mytool; go mod init xk6-subcommand-mytool&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The core of a subcommand extension is a constructor function that creates a Cobra command. The constructor receives k6&amp;rsquo;s &lt;code&gt;GlobalState&lt;/code&gt; for read-only access to runtime configuration.&lt;/p&gt;
&lt;p&gt;Create an example command named &lt;code&gt;mytool&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package mytool

import (
    &amp;#34;github.com/spf13/cobra&amp;#34;
    &amp;#34;go.k6.io/k6/v2/cmd/state&amp;#34;
    &amp;#34;go.k6.io/k6/v2/subcommand&amp;#34;
)

func init() {
    subcommand.RegisterExtension(&amp;#34;mytool&amp;#34;, newCommand)
}

func newCommand(gs *state.GlobalState) *cobra.Command {
    return &amp;amp;cobra.Command{
        Use:   &amp;#34;mytool&amp;#34;,
        Short: &amp;#34;My custom tool&amp;#34;,
        Long:  &amp;#34;A custom tool that integrates with k6&amp;#34;,
        Run: func(cmd *cobra.Command, args []string) {
            gs.Logger.Info(&amp;#34;Running mytool&amp;#34;)
            // Custom logic here
        },
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The extension uses the &lt;code&gt;subcommand.RegisterExtension&lt;/code&gt; function to register itself during initialization. The first argument is the command name (which must match the command&amp;rsquo;s &lt;code&gt;Use&lt;/code&gt; field), and the second is the constructor function.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;div class=&#34;admonition admonition-caution&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Caution&lt;/p&gt;&lt;p&gt;The &lt;code&gt;GlobalState&lt;/code&gt; provided to your command is read-only. Do not modify it, as this can cause core k6 instability.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;use-automatic-extension-resolution&#34;&gt;Use automatic extension resolution&lt;/h2&gt;
&lt;p&gt;If your subcommand extension is registered in the 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/explore/&#34;&gt;k6 extension catalog&lt;/a&gt;, you can use it directly without building a custom binary. k6 automatically detects, builds, and loads the extension when you invoke it:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;k6 x mytool&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This works for any registered subcommand extension. k6 provisions the required extension transparently and executes your command.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;To use community extensions you must have &lt;code&gt;K6_ENABLE_COMMUNITY_EXTENSIONS&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;K6_ENABLE_COMMUNITY_EXTENSIONS=true k6 x mytool&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;For real-world examples of official subcommand extensions that ship through this exact path, see 
    &lt;a href=&#34;/docs/k6/v2.3.x/set-up/configure-ai-assistant/&#34;&gt;the k6 MCP server&lt;/a&gt; (exposed as &lt;code&gt;k6 x mcp&lt;/code&gt;) and 
    &lt;a href=&#34;/docs/k6/v2.3.x/set-up/configure-ai-assistant/bootstrap-with-k6-x-agent/&#34;&gt;&lt;code&gt;k6 x agent&lt;/code&gt;&lt;/a&gt;, which scaffolds AI editor integrations on top of it.&lt;/p&gt;
&lt;h3 id=&#34;disable-automatic-extension-resolution&#34;&gt;Disable automatic extension resolution&lt;/h3&gt;
&lt;p&gt;You can disable this feature by setting the environment variable &lt;code&gt;K6_AUTO_EXTENSION_RESOLUTION&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;K6_AUTO_EXTENSION_RESOLUTION=false k6 x mytool&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;discover-available-subcommands&#34;&gt;Discover available subcommands&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;k6 x&lt;/code&gt; without arguments lists the available subcommands, both the ones built into the binary and those advertised by the 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/explore/&#34;&gt;extension catalog&lt;/a&gt; (official and community):&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;text&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-text&#34;&gt;$ k6 x
...
Available Commands:
  agent       Bootstrap an AI-assisted k6 testing workflow in any editor
  docs        CLI k6 docs for AI agents and users
  explore     Explore k6 extensions for Automatic Resolution
  mcp         An MCP server for k6 for AI agents&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Shell tab-completion surfaces the same set once the catalog has been cached locally by a prior &lt;code&gt;k6 x&lt;/code&gt; run, so completion never blocks on the network. This makes a k6 binary self-describing, which is particularly useful for AI agents driving k6.&lt;/p&gt;
&lt;h2 id=&#34;build-a-custom-k6-binary&#34;&gt;Build a custom k6 binary&lt;/h2&gt;
&lt;p&gt;To use subcommand extensions you&amp;rsquo;re developing or that aren&amp;rsquo;t available in the registry, build a custom k6 binary with &lt;a href=&#34;https://github.com/grafana/xk6&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;xk6&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;xk6 build --with xk6-subcommand-mytool=.&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This creates a &lt;code&gt;k6&lt;/code&gt; binary in your current directory that includes your extension.&lt;/p&gt;
&lt;p&gt;After building, your subcommand is available under the &lt;code&gt;k6 x&lt;/code&gt; namespace:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 x mytool&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To see all available extension subcommands:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 x help&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;constructor-requirements&#34;&gt;Constructor requirements&lt;/h2&gt;
&lt;p&gt;The constructor function passed to &lt;code&gt;RegisterExtension&lt;/code&gt; must:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Accept a single &lt;code&gt;*state.GlobalState&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;Return a &lt;code&gt;*cobra.Command&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create a command whose &lt;code&gt;Use&lt;/code&gt; field matches the registered extension name&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Violating these requirements causes the extension to panic at startup, ensuring configuration errors are caught early.&lt;/p&gt;
&lt;h2 id=&#34;example-complete-validation-tool&#34;&gt;Example: Complete validation tool&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a more complete example that checks system requirements:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;package validate

import (
    &amp;#34;fmt&amp;#34;
    &amp;#34;os&amp;#34;
    &amp;#34;runtime&amp;#34;

    &amp;#34;github.com/spf13/cobra&amp;#34;
    &amp;#34;go.k6.io/k6/v2/cmd/state&amp;#34;
    &amp;#34;go.k6.io/k6/v2/subcommand&amp;#34;
)

func init() {
    subcommand.RegisterExtension(&amp;#34;validate&amp;#34;, newValidateCommand)
}

func newValidateCommand(gs *state.GlobalState) *cobra.Command {
    cmd := &amp;amp;cobra.Command{
        Use:   &amp;#34;validate&amp;#34;,
        Short: &amp;#34;Verify system requirements&amp;#34;,
        Long:  &amp;#34;Check if the system meets requirements for running tests&amp;#34;,
        RunE: func(cmd *cobra.Command, args []string) error {
            gs.Logger.Info(&amp;#34;Checking system requirements...&amp;#34;)
            
            // Check Go version
            gs.Logger.Infof(&amp;#34;Go version: %s&amp;#34;, runtime.Version())
            
            // Check available memory
            var m runtime.MemStats
            runtime.ReadMemStats(&amp;amp;m)
            gs.Logger.Infof(&amp;#34;Available memory: %d MB&amp;#34;, m.Sys/1024/1024)
            
            // Check environment variables
            if broker := os.Getenv(&amp;#34;MQTT_BROKER&amp;#34;); broker != &amp;#34;&amp;#34; {
                gs.Logger.Infof(&amp;#34;MQTT broker configured: %s&amp;#34;, broker)
            } else {
                gs.Logger.Warn(&amp;#34;MQTT_BROKER not set&amp;#34;)
            }
            
            gs.Logger.Info(&amp;#34;Validation complete&amp;#34;)
            return nil
        },
    }
    
    return cmd
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Usage:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 x validate&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;access-k6-runtime-state&#34;&gt;Access k6 runtime state&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;GlobalState&lt;/code&gt; provides read-only access to k6&amp;rsquo;s configuration:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;func newCommand(gs *state.GlobalState) *cobra.Command {
    return &amp;amp;cobra.Command{
        Use:   &amp;#34;validate&amp;#34;,
        Short: &amp;#34;Validate k6 configuration&amp;#34;,
        Run: func(cmd *cobra.Command, args []string) {
            // Access logger
            gs.Logger.Info(&amp;#34;Validating k6 configuration...&amp;#34;)
            
            // Access flags and options
            if gs.Flags.Verbose {
                gs.Logger.Debug(&amp;#34;Verbose mode enabled&amp;#34;)
            }
            
            // Access environment variables
            gs.Logger.Infof(&amp;#34;Working directory: %s&amp;#34;, gs.Getwd)
        },
    }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;add-command-flags&#34;&gt;Add command flags&lt;/h2&gt;
&lt;p&gt;Use Cobra&amp;rsquo;s flag system to add options to your subcommand:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;func newCommand(gs *state.GlobalState) *cobra.Command {
    var target string
    var verbose bool
    
    cmd := &amp;amp;cobra.Command{
        Use:   &amp;#34;validate&amp;#34;,
        Short: &amp;#34;Validate configuration&amp;#34;,
        Run: func(cmd *cobra.Command, args []string) {
            if verbose {
                gs.Logger.Info(&amp;#34;Verbose mode enabled&amp;#34;)
            }
            gs.Logger.Infof(&amp;#34;Validating target: %s&amp;#34;, target)
            // Validation logic here
        },
    }
    
    cmd.Flags().StringVarP(&amp;amp;target, &amp;#34;target&amp;#34;, &amp;#34;t&amp;#34;, &amp;#34;localhost&amp;#34;, &amp;#34;Target to validate&amp;#34;)
    cmd.Flags().BoolVarP(&amp;amp;verbose, &amp;#34;verbose&amp;#34;, &amp;#34;v&amp;#34;, false, &amp;#34;Enable verbose output&amp;#34;)
    
    return cmd
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Usage:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;./k6 x validate --target example.com --verbose&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;best-practices&#34;&gt;Best practices&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read-only state&lt;/strong&gt;: Never modify the &lt;code&gt;GlobalState&lt;/code&gt; passed to your constructor&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Naming&lt;/strong&gt;: Use descriptive, kebab-case names for your commands&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Provide clear &lt;code&gt;Short&lt;/code&gt; and &lt;code&gt;Long&lt;/code&gt; descriptions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: Return errors from &lt;code&gt;RunE&lt;/code&gt; rather than panicking in command execution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging&lt;/strong&gt;: Use &lt;code&gt;gs.Logger&lt;/code&gt; for consistent output with k6&amp;rsquo;s logging system&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="subcommand-extensions">Subcommand extensions&lt;/h1>
&lt;p>k6 provides a rich set of built-in commands, but some use cases require custom CLI tools that integrate with k6&amp;rsquo;s runtime and state. Subcommand extensions allow you to register custom commands under the &lt;code>k6 x&lt;/code> namespace, providing a standardized way to extend k6&amp;rsquo;s CLI functionality.&lt;/p></description></item><item><title>About the Extensions Registry</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/extensions-registry/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/extensions-registry/</guid><content><![CDATA[&lt;h1 id=&#34;about-the-extensions-registry&#34;&gt;About the Extensions Registry&lt;/h1&gt;
&lt;p&gt;Did you create an extension and want to share it with your fellow k6 users?
We&amp;rsquo;d love to spread word of this new feature adding to our 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/explore/&#34;&gt;registry&lt;/a&gt; of available extensions.
However, before an extension is added to the registry, we must ensure that it is maintained to the registry standard.&lt;/p&gt;
&lt;p&gt;Our desire is to provide the best developer experience when using k6.
This extends to the extensions ecosystem as well.
The adaptability provided by k6 extensions opens a wide array of potential use cases.&lt;/p&gt;
&lt;p&gt;To ensure quality, we need a well-maintained, curated listing of extensions.
Our pledge to the community is to make our best attempt to ensure the listed projects meet certain standards.
While we cannot guarantee the quality of community-provided extensions, we &lt;em&gt;can&lt;/em&gt; aid the evaluation by requiring certain consistencies.&lt;/p&gt;
&lt;h2 id=&#34;registry-requirements&#34;&gt;Registry requirements&lt;/h2&gt;
&lt;p&gt;At minimum, each source code repository must have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;a README file&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;README&lt;/code&gt; must contain documentation such as the project description, build and usage instructions, as well as k6 version compatibility.
The goal is to provide enough information to quickly and easily evaluate the extension.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;the &lt;code&gt;xk6&lt;/code&gt; topic set&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;GitHub allows setting &lt;em&gt;topics&lt;/em&gt; for a repository.
This supports querying all public repositories based upon a keyword for better discoverability, i.e. &lt;a href=&#34;https://github.com/topics/xk6&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&amp;ldquo;xk6&amp;rdquo;&lt;/a&gt;.
See the &lt;a href=&#34;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub documentation&lt;/a&gt; to add topics.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;a non-restrictive license&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Any open source software (OSS) license will suffice, but &lt;a href=&#34;https://www.apache.org/licenses/LICENSE-2.0&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Apache2&lt;/a&gt; is preferred.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;an &lt;code&gt;examples&lt;/code&gt; folder with examples&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Provide at least one script to show proper usage of your API.
If a &lt;a href=&#34;https://docs.docker.com/compose/compose-file/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Docker Compose&lt;/a&gt; specification is provided, these could be used as integration tests to validate the extension works as intended.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;at least one versioned release&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As features or fixes are ready to be consumed, create a &lt;a href=&#34;https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;release&lt;/a&gt;.
This promotes stability by allowing a user to utilize a particular version.
Use &lt;a href=&#34;https://semver.org/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;semantic versioning&lt;/a&gt; to communicate changes as the extension evolves.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;builds with a recent k6 version&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ideally, the extension should build with the &lt;a href=&#34;https://github.com/grafana/k6/releases/latest&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;latest release&lt;/a&gt;.
But, it must build with a version of k6 that is no more than three releases old.
For example, if latest version of k6 is &lt;code&gt;v0.100&lt;/code&gt;, the extension must build with at least version &lt;code&gt;v0.98&lt;/code&gt;.
Be sure to also match the version of Go as determined by the version of k6.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;naming-conventions&#34;&gt;Naming conventions&lt;/h2&gt;
&lt;p&gt;Some extensions may be very specific, where others are more general.
Multiple extensions may even be created for the same product with different levels of support based upon version.
By adhering to typical naming conventions, your extension name can remove some doubts as to what is supported.&lt;/p&gt;
&lt;p&gt;For any extension, we recommend the &lt;code&gt;xk6-&lt;/code&gt; prefix as well as an optional &lt;code&gt;output-&lt;/code&gt; for 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/create/output-extensions/&#34;&gt;Output extensions&lt;/a&gt;.
Next, provide the product or protocol name; don&amp;rsquo;t be cryptic.
Ensure the usage is explicit by adopting only well-known acronyms or abbreviations if necessary.
If your extension supports only a specific version of a product, incorporate the version into the name, for example &lt;code&gt;v2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As an example, suppose an extension that outputs test metrics to the &lt;em&gt;AwesomeLog&lt;/em&gt; application, and it uses only the v2 API.
In this case, say the latest v3 API is not backward-compatible.
Applying our conventions, we&amp;rsquo;d recommend naming this repository as &lt;code&gt;xk6-output-awesomelog-v2&lt;/code&gt;.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Our goal is to quickly understand the intent of the extension.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;potential-for-de-listing&#34;&gt;Potential for de-listing&lt;/h2&gt;
&lt;p&gt;Given our desire to provide the best developer experience when using k6, we reserve the right to de-list any extension we deem is no longer maintaining standards.
Before any action takes place, the extension maintainers will be contacted to be given a chance to rectify the project and thus avoid de-listing.
Such contact may be in the form of GitHub issues or merge requests.&lt;/p&gt;
&lt;p&gt;Should any extension be de-listed, this does not constitute a permanent removal.
Any extension that has been de-listed may be reinstated once the reasons for the initial removal have been remediated.&lt;/p&gt;
]]></content><description>&lt;h1 id="about-the-extensions-registry">About the Extensions Registry&lt;/h1>
&lt;p>Did you create an extension and want to share it with your fellow k6 users?
We&amp;rsquo;d love to spread word of this new feature adding to our
&lt;a href="/docs/k6/v2.3.x/extensions/explore/">registry&lt;/a> of available extensions.
However, before an extension is added to the registry, we must ensure that it is maintained to the registry standard.&lt;/p></description></item><item><title>About the Go-to-JS bridge</title><link>https://grafana.com/docs/k6/v2.3.x/extensions/create/go-js-bridge/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/extensions/create/go-js-bridge/</guid><content><![CDATA[&lt;h1 id=&#34;about-the-go-to-js-bridge&#34;&gt;About the Go-to-JS bridge&lt;/h1&gt;
&lt;p&gt;All k6 and xk6 binaries have an embedded JavaScript engine, &lt;a href=&#34;https://github.com/grafana/sobek&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;sobek&lt;/a&gt;,
which your test scripts run on.&lt;/p&gt;
&lt;p&gt;You will deepen your conceptual knowledge of how your k6 extension works if you understand the &lt;em&gt;bridge&lt;/em&gt; between Go internals and the JavaScript runtime.&lt;/p&gt;
&lt;h2 id=&#34;go-to-javascript-bridge-features&#34;&gt;Go-to-JavaScript bridge features&lt;/h2&gt;
&lt;p&gt;The bridge has a few features we should highlight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Go method names are converted from &lt;em&gt;Pascal&lt;/em&gt; to &lt;em&gt;Camel&lt;/em&gt; case when
accessed in JS. For example, &lt;code&gt;IsGreater&lt;/code&gt; becomes &lt;code&gt;isGreater&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go field names convert from &lt;em&gt;Pascal&lt;/em&gt; to &lt;em&gt;Snake&lt;/em&gt; case. For example, the struct field &lt;code&gt;ComparisonResult string&lt;/code&gt;
becomes &lt;code&gt;comparison_result&lt;/code&gt; in JS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Field names may be explicit using &lt;code&gt;js&lt;/code&gt; struct tags. For example, declaring the field as &lt;CodeInline&gt;ComparisonResult string `js:&amp;ldquo;result&amp;rdquo;`&lt;/CodeInline&gt;
or hiding from JS using &lt;CodeInline&gt;`js:&amp;quot;-&amp;quot;`&lt;/CodeInline&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;type-conversion-and-native-constructors&#34;&gt;Type conversion and native constructors&lt;/h2&gt;
&lt;p&gt;The JavaScript runtime transparently converts Go types like &lt;code&gt;int64&lt;/code&gt; to their JS equivalent.
For complex types where this is impossible, your script might fail with a &lt;code&gt;TypeError&lt;/code&gt;, requiring you to explicitly convert
your object to a &lt;a href=&#34;https://pkg.go.dev/github.com/grafana/sobek#Object&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;sobek.Object&lt;/code&gt;&lt;/a&gt; or &lt;a href=&#34;https://pkg.go.dev/github.com/grafana/sobek#Value&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;sobek.Value&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Go&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-go&#34;&gt;func (*Compare) XComparator(call sobek.ConstructorCall, rt *sobek.Runtime) *sobek.Object {
	return rt.ToValue(&amp;amp;Compare{}).ToObject(rt)
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The preceding snippet also demonstrates the &lt;em&gt;native constructors&lt;/em&gt; feature from sobek, where methods can become JS constructors.
Methods with this signature can create &lt;code&gt;Comparator&lt;/code&gt; instances in JS with &lt;code&gt;new compare.Comparator()&lt;/code&gt;.
While this is more idiomatic to JS, it still has the benefit of receiving the &lt;code&gt;sobek.Runtime&lt;/code&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="about-the-go-to-js-bridge">About the Go-to-JS bridge&lt;/h1>
&lt;p>All k6 and xk6 binaries have an embedded JavaScript engine, &lt;a href="https://github.com/grafana/sobek" target="_blank" rel="noopener noreferrer">sobek&lt;/a>,
which your test scripts run on.&lt;/p>
&lt;p>You will deepen your conceptual knowledge of how your k6 extension works if you understand the &lt;em>bridge&lt;/em> between Go internals and the JavaScript runtime.&lt;/p></description></item></channel></rss>