Amazon Game Tech Blog

Build a Twitch Extension With an AWS Serverless Backend

Whether you are live streaming your band’s latest single or live coding a web app, the broadcaster community is creating and sharing content that viewers around the world can consume. The developer community is revolutionizing that experience by building interactive capabilities that allow viewers to directly engage with their favorite broadcasters through Twitch Extensions. Extensions enable you to create live apps that interact with the stream as an overlay on the video player, a panel on a channel page, or with chat; such as heat maps, real-time data-driven overlays, mini-games, music requests, and leader boards.

Just how developers changed how we interact with our phones by creating apps that enhance the experience and functionality, Twitch Extensions are changing how we interact and engage with video experiences. There have been over 1.8 billion interactions with Extensions in the first few months of 2019 alone and over 67 percent of partner and affiliate streamers on Twitch have an Extension installed.

Creating your first Twitch Extension may seem daunting, but we’re here to help you along the journey. To add to the Twitch Dev resources, Nicki Klein, Senior Technical Evangelist at AWS, and Matt Auerbach, Developer Advocate at Twitch, have created an 8-part live coding series to walk the community through how to build a Twitch Extension.

This blog will cover the steps Matt and Nicki used to build their Twitch Extension end-to-end using AWS for all developer provided services. We will walk through how to build a serverless EBS using AWS Lambda, Amazon DynamoDb, and Amazon API Gateway as well as outline how to make sure your backend and frontend are communicating securely and properly in order to submit your Extension for approval by Twitch.

Twitch Extensions are comprised of two parts: the actual frontend overlay or panel, and an optional Extension Backend Service (EBS). Since the backend of the Twitch Extension we set out to build did not need to be always running, due to the fact that streams aren’t live all the time, the Extension isn’t being used, or the frontend is doing all the work, the best option for an EBS in our case was serverless. This might not be the case for all extensions.


Requires an AWS account, a Twitch Developer account, knowledge of JavaScript frameworks, and frontend web development as well as knowledge of Node.js.

Extension planning begins

First thing’s first——time for Extension design. Episode # 1 started, like any app should, with feature requirement definitions, UX mocks, and a project plan.

Starting with the problem, as we all know, Twitch Chat can get very busy, which is ideal for a fun stream but makes it very difficult to keep track of viewer questions, especially when broadcasting a Just Chatting show or a technical stream such as live coding.

Figma is a cloud-based prototyping tool, which makes it easier to quickly mock up application User Interfaces. On stream, the team sketched out the two required views: Live Config and Panel. It looked something like this:

Let’s break down the functionality by each component.

The Panel component (right above) allows the viewer to:

  • Submit questions for the streamer
  • View their previously asked questions with answers, if provided by the streamer

The Live Config (left above) allows the broadcaster to:

  • View a list of all questions
  • Reply to questions directly

Now that we have all the components, let’s walk through an architecture diagram.

This chart is broken down into three pillars:

  1. Twitch-owned Services
  2. The Frontend
  3. The Backend, hosted on AWS

The services shown in purple are managed by Twitch and the services shown in blue are the developer’s responsibility.

Let’s start on the Twitch side. Twitch provides developers with additional features that make it easier to build and scale their application without having to worry about additional technical burdens. PubSub allows you to send messages from the backend directly to all Extension frontend instances. Additionally, the Twitch Extension helper is bundled into the Extension frontend and provides developers with critical services such as providing the channel ID, user ID, or even if a given user is a subscriber of the current broadcaster.

Next, the frontend. This consists of JavaScript, HTML, and CSS and is shown to all viewers of the broadcaster’s channel. When a developer’s frontend code is ready for production, this bundle is hosted on the Twitch CDN, managed by Twitch, to ensure consistent and low latency experiences for viewers.

An Extension backend, while optional, will allow for deeper integration into other services and allow the app to process business logic. In our case, the backend will be written in JavaScript using Node as a backend framework. The backend will act as a layer in between the viewers, broadcasters, and DynamoDB. It will provide the frontend with question data given a specific channel, as well as process newly submitted questions. As mentioned above, Lambda will process this business logic after the data is routed via an API Gateway Endpoint and use DynamoDB to persist data.

Building The Backend

First, we created a DynamoDb table with a name of ‘Twitch_Questions’ and a primary key of id, an arbitrary guide that we would create upon insertion of a row. We added two Global Secondary Indexes for two of our queries: a user_id-index and a channel_id-index so that we could get all questions by user or channel.

Next, we needed to create the scaffolding for our Node.js Express application, and we used our own AWS package called aws-serverless-express/middleware available on Github and through install with npm to create a Node.js application using express to create our backend project.

Once we had a project created, we defined the required endpoints. We decided that we would need the following endpoints to : fetch all questions for the broadcaster, as well as post to ask and answer a question.

GET /questions/?user_id={insert a twitch user id}
GET /channelquestions/?channel_id={insert a twitch channel user id to pull questions for the broadcaster}
POST /question body = {user_id, channel_id, question, postedToForum, displayName}
PUT /answer body = {id (of the question), answer (text of the answer)}

Once we outlined our endpoints, we had to write JavaScript functions to actually query DynamoDb, or insert into the table, delete, etc. We installed the aws-sdk and instantiated the DynamoDb client in JavaScript with const dynamodb = new AWS.DynamoDB.DocumentClient({region: 'us-west-2'}) and then we were able to use the client to make calls to our database table.

We used a DynamoDb query and our Global secondary index to get all questions by user with the code below:

const getQuestionsByUser = async (userid) => {
    var params = {
    ExpressionAttributeValues: { ":userId": userid},
    KeyConditionExpression: "user_id = :userId",
    IndexName: "user_id-index",
    TableName: tableName
    let data = await dynamodb.query(params).promise();
    return data.Items;

To see the rest of our calls to the database, take a look at our Node.js express app here.

To test our serverless app, we used SAM, the Serverless Application Model, to run our Lambda function locally. SAM is both a YAML template that was included for us when we created the app using the aws-serverless-express/middleware package and a CLI tool. If you have both the SAM CLI tool and Docker installed, running sam local start-api will tell SAM to find your SAM template named template.yaml. SAM will then spin up a Docker container for you mimicking the Lambda environment and run your application on that container so you can debug before the code even gets to the cloud.

Once we created our endpoints and thoroughly tested them using sam local, we could deploy our backend for use with our Twitch Extension! The aws-serverless-express/middleware package also gave us some easy to use npm scripts to package up our application and deploy it. There were three that were relevant to our deployment:

"package": "aws cloudformation package --template ./template.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region", "deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region", "package-deploy": "npm run package && npm run deploy"

So either we could run package and then deploy or we could just run npm run package-deploy and it would package up our code by zipping it up and putting it in an S3 bucket and then update our template.yaml file to tell Lambda where to find our code once we deployed and then deploy it. When we run npm run deploy, our template.yaml is then sent to CloudFormation where all the resources we needed for our app like this Lambda function, and an API Gateway endpoint and possibly some IAM roles, are created for us in the cloud. Once we deployed our app and again testing using the production URL given to us through one of the outputs of the CloudFormation Stack, we are able to go back to our frontend and make calls to it like any regular old API!

Building The Frontend

Although, we have JavaScript experience, we weren’t sure if we were ready to build the app in Vue.js, because we haven’t used the framework before. If you aren’t familiar, Vue.js is a framework that makes it easy to build user interfaces quickly and single page apps similar to React. We gave it a shot as it’s less complex than React and would allow us to get started quickly.

In Vue, a good starting point is using Vue CLI to generate boilerplate code. From here, we will add our two components: Panel.vue and LiveConfig.vue. These components require very similar logical, and only differ in the data we display to the user. After the Vue component renders, The Twitch Extension helper provides us with contextual information. We use the Vue life cycle method mounted(), which gives you full access to the components, templates and rendered DOM. In the code snippet below, we pull the userID, channelID, clientID and token (constants declared at the top of our Vue file) from the Extension helper.

async mounted() {
    await twitch.onAuthorized((auth) => {  
    userID = auth.userId;
    channelID = auth.channelId; 
    clientID = auth.clientId
    token = auth.token;

Lastly, we call pullQuestions() to load any questions this user might have previously asked.

              method: 'GET',
              headers: new Headers({
               'Content-Type': 'application/json',
               'Authorization': `Bearer ${token}`})
          .then(data => data.json())
          .then(result =>
              this.questions = result;

Here, we make a GET request to /pullQuestions() and pass in the userID. We decided that viewers would only be able to see previous questions they asked. This response provides the Vue component with the question data to display to the user.

What about submitting a question? After a user types a question in the input box, the submit button is tied to a onClick handler. In Vue, we can use a fancy shorthand to map a function to a button:

<button class="askQuestionBtn" @click="askAQuestion" variant="primary">Submit</button>

Then, in askAQuestion(), we make a POST request to /question

          fetch(`${ROOT_URL}question`, {
              method: 'POST',
              headers: new Headers({
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${token}`}),
              body: JSON.stringify({
                    user_id: `${userID}`,
                    channel_id: `${channelID}`,
                    question: this.questionText,
                    postedToForum: false,
                    displayName: displayName

The body contains the data that the backend requires to process the question submission. We send the user_id, channel_d and question. It’s also important to note the Bearer token we send in the header. This allows us to authenticate API requests on our backend.

Using in the Developer Rig

The Twitch Developer rig makes developing Extensions even easier. It allows you to run the frontend and backend locally while mimicking the native Extension environment. The developer can setup multiple Extension views which correlate to the different possible views (Live View, Configuration, and Viewer Panel) and the different user states (logged out, logged in). In our case, we would want to run the Broadcaster view (Live View) using our LiveView.vue component. On the right, we pull in the Panel.vue and simulate a logged in viewer. The Rig also helps simulate other situations using the “Edit Context” tab – to name a few, configurations like muting or pausing a stream and light vs dark mode.

Extension Testing

Now that we’ve verified that the Extension runs locally via the Rig, we should complete a Hosted Test — meaning we need to upload the Extension frontend assets to the Twitch CDN and test within the context of an actual channel on It’s recommended you test this on your own channel. To do this, we build our production bundle using npm run build which bundles our assets into a dist/ directory. From here, we zip the files which look like:

-- Dist
— live_config.html
— panel.html
— /static
-- /css
-- /js

and upload the root to Twitch via the Developer Dashboard. Then we flip the switch from Local Test to Hosted Test. Lastly, we need to click “View and Install on Twitch” which activates the Extension as a panel on your channel. We can then verify that everything is working as expected on our channel and if so we are ready to submit our Extension for approval!

Steps to build

View the project code on GitHub and build AskACaster locally with the Developer Rig. Feel free to create Issues, Pull Requests or reach out for help.

GitHub repo:

Twitch Extensions Show

Want to learn more about the process? Follow along with past recordings of our Build a Twitch Extension Show


This blog was co-authored by:

Matthew Auerbach – Developer Advocate, Twitch
Twitter: @mauerbac

Nicki Klein – Senior Technical Evangelist, AWS
Twitter: @kneekey23