Getting Started

In this tutorial we will:

  1. Write our own custom filter for Envoy
  2. build a WASM module from our filter and store it as an OCI image/
  3. push the module to the WebAssembly Hub
  4. deploy the image to a running instance of Envoy.
  5. curl the instance to see our filter act on a request.

For in-depth guides, please refer to:

Creating a new WASM module

Install the wasme CLI

In order to create and deploy a WASM filter, we’ll use the wasme command line tool. wasme makes building and managing Envoy WASM filters similar to how docker builds and manages Linux containers.

To install wasme:

curl -sL | sh
export PATH=$HOME/.wasme/bin:$PATH

Verify that wasme installed correctly:

wasme --version
wasme version 0.0.25

Initialize a new filter project

Let’s create a new project called new-filter:

$  wasme init ./new-filter

You’ll be asked with an interactive prompt which language platform you are building for. At time of writing, the AssemblyScript Filter base is compatible with Istio 1.5.x, 1.6.x and Gloo 1.3.x:

? What language do you wish to use for the filter:
  ▸ assemblyscript
? With which platforms do you wish to use the filter?:
  ▸ gloo:1.3.x, istio:1.5.x, istio:1.6.x
INFO[0014] extracting 1973 bytes to /Users/ilackarms/go/src/

The init command will place our base filter into the new-filter directory:

cd new-filter
tree .
├── assembly
│   ├── index.ts
│   └── tsconfig.json
├── package-lock.json
├── package.json
└── runtime-config.json

Open this project in your favorite IDE. The source code is AssemblyScript (a subset of Typescript) and we’ll make some changes to customize our new filter.

Making changes to the base filter

The new directory contains all files necessary to build and deploy a WASM filter with wasme.

Open assembly/index.ts in your favorite text editor. The source code is AssemblyScript and we’ll make some changes to customize our new filter.

Navigate to the onResponseHeaders method defined near the top of the file. Let’s add a new header that we can use to verify our module was executed correctly (later down in the tutorial). Let’s add a new response header hello: world!:

      stream_context.headers.response.add("hello", "world!");

Your method should look like this:

  onResponseHeaders(a: u32): FilterHeadersStatusValues {
    // add the hello: world! response header
    stream_context.headers.response.add("hello", "world!");
    // continue execution of the filter chain
    return FilterHeadersStatusValues.Continue;

Now, let’s build a WASM image from our filter with wasme. The filter will be tagged and stored in a local registry, similar to how Docker stores images.

Build and tag our image like so:

wasme build assemblyscript -t$YOUR_USERNAME/add-header:v0.1 .
INFO[0010] adding image to cache...                      filter file=/tmp/wasme653155634/filter.wasm tag=""
INFO[0010] tagged image                                  digest="sha256:8b74e9b0bbc5ff674c49cde904669a775a939b4d8f7f72aba88c184d527dfc30" image=""

The module will take less than a minute to build. In the background, wasme has launched a Docker container to run the necessary build steps.

When the build has finished, you’ll be able to see the image with wasme list:

wasme list
NAME                                   TAG  SHA      UPDATED             SIZE v0.1 bbfdf674 26 Jan 20 10:45 EST 1.0 MB

Pushing your new WASM module

In order to push the module to a registry, we’ll need to log in. If you’ve already logged in, you can skip this step.

Create a User on

Pushing images with wasme requires a compatible OCI registry. In this tutorial, we’ll use as our remote registry.

Let’s open in the browser to create an account.

  1. Click Log In in the top right:

  2. Choose Sign up now under the login form:

  3. Fill out the sign-up form and click Sign Up:

  4. You should now be logged in as a new user:

Now we can log in on the command-line.

Log In from the wasme command line

In order to push images under our new username, we’ll need to store our credentials where wasme can access them.

Let’s do that now with wasme login:

 wasme login -u $YOUR_USERNAME -p $YOUR_PASSWORD
INFO[0000] Successfully logged in as ilackarms (Scott Weiss)
INFO[0000] stored credentials in /Users/ilackarms/.wasme/credentials.json

Great! We’re logged in and ready to push our image.

Push the image

Pushing the image is done with a single command:

wasme push$YOUR_USERNAME/add-header:v0.1
INFO[0000] Pushing image
INFO[0006] Pushed
INFO[0006] Digest: sha256:9d4b4660f71f2714cc71e2b844e9b8460def21f6d76259140e70d447ccc7c702

Awesome! Our image should be pushed and ready to deploy.

View our published image

We can verify the image was pushed via the command-line:

wasme list --search $YOUR_USERNAME
NAME                                   TAG  SIZE    SHA      UPDATED v0.1 13.8 kB 9d4b4660 05 Mar 20 01:12 UTC

Deploy our new module

In this section we’ll be deploying our filter to Envoy which is serving as an edge proxy for the Gloo API Gateway.

Prepare environment

To get started, let’s deploy a sample service that we can call through Envoy. We’ll deploy the sample petstore API:

kubectl apply -f

You should now have the petstore running:

$  kubectl get po

NAME                        READY   STATUS    RESTARTS   AGE
petstore-5dcf5d6b66-n8tjt   1/1     Running   0          2m20s

Deploying Gloo/Envoy

In this tutorial, we’ll use Gloo Edge Enterprise, an API Gateway based on Envoy that has built-in wasm support. But these steps should also work for base Envoy.

First, install Gloo Edge using one of the following installation options:

helm repo add glooe
helm repo update
kubectl create ns gloo-system
helm install gloo-gateway glooe/gloo-ee --namespace gloo-system --set-string license_key=$GLOO_KEY

glooctl install gateway enterprise -n gloo-system --license-key $GLOO_KEY

Gloo Edge Enterprise version 1.6.2 or greater is required. Check your installed version of Gloo with glooctl version. You may obtain a license key with a free trial of the Enterprise edition, available here.

Verify set up

Lastly, we’ll set up our routing rules to be able to call our petstore service. Let’s add a route to the routing table:

Create a Gloo VirtualService to route to the petstore:

cat <<EOF | kubectl apply -f-
kind: VirtualService
  name: default
  namespace: gloo-system
    - '*'
    - matchers:
      - prefix: /
            name: default-petstore-8080
            namespace: gloo-system

To get Gloo’s external IP, run the following:

URL=$(kubectl get svc -n gloo-system gateway-proxy -o jsonpath='{.status.loadBalancer.ingress[*].ip}')

Now let’s curl that URL:

curl -v $URL/api/pets
*   Trying
* Connected to ( port 80 (#0)
> GET /api/pets HTTP/1.1
> Host:
> User-Agent: curl/7.54.0
> Accept: */*
< HTTP/1.1 200 OK
< content-type: application/xml
< date: Thu, 05 Mar 2020 01:14:01 GMT
< content-length: 86
< x-envoy-upstream-service-time: 3
< server: envoy
* Connection #0 to host left intact

If you’re able to get to this point, we have a working Envoy proxy and we’re able to call it externally.

To deploy the module to Envoy via Gloo:

wasme deploy gloo$YOUR_USERNAME/add-header:v0.1 --id=add-header

It will take a few moments for the image to be pulled by the server-side cache. The deployment should have added our filter to the Gloo Edge Gateway. Let’s confirm this with kubectl:

kubectl get gateway -n gloo-system '-ojsonpath={.items[0].spec.httpGateway.options.wasm}'

Verify behavior

To verify the behavior, let’s use the curl command from above:

curl -v $URL/api/pets

We expect to see our new headers in the response:

*   Trying
* Connected to ( port 80 (#0)
> GET /api/pets HTTP/1.1
> Host:
> User-Agent: curl/7.54.0
> Accept: */*
< HTTP/1.1 200 OK
< content-type: application/xml
< date: Thu, 05 Mar 2020 01:19:07 GMT
< content-length: 86
< x-envoy-upstream-service-time: 0
< hello: world!
< server: envoy
* Connection #0 to host left intact

If we can see the new header hello: world! in our response, that means everything worked!

You’ve now gone through the process step-by-step to build and deploy Envoy filters from scratch.

For guides on performing specific tasks with wasme, please refer to:

For more information and support using wasme and the Web Assembly Hub, visit the slack channel at