Contents

Get started with Twitch Extensions

What is a Twitch Extension?

A Twitch Extension is a webpage that sits inside Twitch and communicates with Twitch to provide extra functionality. For example, the Hearthstone Deck Tracker extension adds an interactive overlay allowing viewers to browse through the cards the streamer is using and their effects.

Twitch Extensions can appear in one of three views:

How do Extensions work?

A Twitch Extension sits in a specially sandboxed iframe inside Twitch and communicates with its parent via postMessage. We handle all the nitty-gritty of forming these messages, and provide a Javascript API to the extension called the Extension Helper. This is a high level API allowing the extension to request things like user identity and chat.

%% Extensions Overview Diagram
graph LR
    Twitch --> | - postMessage - | Extension
    Extension --> | - postMessage - | Twitch
    Extension --> | - HTTP - | APIs
    Extension --> | - HTTP - | EBS[Extension Backend]
    Identity --> Twitch
    Chat --> Twitch
    Memes --> Twitch

What can I do with an Extension?

Being a webpage, an extension can do almost anything you can already do on the web, such as play sounds and show video. We provide some special functionality for you including:

User Stream
[Posting in Chat] [Read Information about the Game and Video]
[Twitch.ext.onContext]
[Follow Channels][Twitch.ext.actions.followChannel] [Get the user’s Language]
[Client Query Parameters]
[Get Subscription Status][isSubscriptionStatusAvailable]  
[Microtransactions]  

Build your first Extension

It’s easier than you might think to make a simple Twitch Extension. Extensions are simply webpages, and the only basic requirement is to import the Extension Helper.

Here’s an extension that just says ‘Hello, world!’:

<!DOCTYPE HTML>
<title>Hello World!</title>
<p>Hello, world!</p>
<script src="https://extension-files.twitch.tv/helper/v1/twitch-ext.min.js"></script>

<!-- index.html -->

Configure the Extension

Configuring an Extension requires a few steps:

Console > Start Extension
Choose Extension type
Optional fields

Extensions needs to know what the starting point is for your panel. Set the Panel Viewer Path to index.html (the name of our panel HTML file).

Panel Viewer path

Package your Extension

When you send Twitch an Extension, you need to bundle it into a zip file.

To upload your zip file to Twitch, choose Files > Upload Version in Assets > Choose File. Then click the purple Upload button at the bottom.

Now we’re ready to move to Hosted Test. Click Next Step in the top right, click Move To Hosted Test, and confirm your choice.

If you go to your Twitch channel now, you’ll see your Extension!

Hello world!

Unless you’re in dark mode, in which case the black text won’t show up against the black background. To fix that, repeat the previous steps, this time changing the text color.

Hello world! in dark mode

Develop your Extension

The easiest way to develop your extension is probably to use the Local Test function. This points Twitch Extensions to your local computer, letting you develop and update Extensions on the fly, while seeing the results in Twitch.

By default, your base URI, where we look for the extensions files, is set to https://localhost:8080/. Note the https in this URL. Because Twitch is served over HTTPS, your browser will reject the extension unless it’s also served over HTTPS. Depending on your operating system, convincing your browser your extension is safe can be a bit tricky.

HTTPS on localhost: the easy way

Technically speaking, HTTPS doesn’t make a lot of sense when it comes to loading your local extension. HTTPS encrypts information in transit as it goes over the internet and makes it secure, but if you’re just communicating from your local machine to your local machine there’s not really anyone who could intercept and decrypt it. As a result, a few browsers either accept locally hosted content as though it were HTTPS, or have a flag to do so.

We recommend using Google Chrome. In the browser, enable the flag allow insecure localhost. To enable this flag, navigate to: chrome://flags/#allow-insecure-localhost and restarting your browser.

Once you have restarted your browser, open an HTTP server on localhost:8080, and change the base URI.

An easy way to launch an HTTP server in the folder your index.html is in is to use Python:

python3 -m http.server     # python 3
python -m SimpleHTTPServer # python 2

Then, change your base URI in the Asset Hosting page of your control panel from https://localhost:8080 to http://localhost:8080.

Now you can skip over the hard way and congratulate yourself on a job well done.

HTTPS on localhost: the hard way

We recommend using a tool called mkcert, which will create and install an HTTPS certificate. After you install mkcert, generate a certificate for HTTPS:

# if you have the Go programming language installed, you
# can replace 'mkcert' in the following line with go run github.com/FiloSottile/mkcert
# to automatically download, install and run it.
mkcert -install localhost
# > The certificate is at "./localhost.pem" and the key at "./localhost-key.pem"

Use this certificate to start a server. With Python installed, create a server.py in the same folder as your index.html:

#!/usr/bin/env/python
import BaseHTTPServer, SimpleHTTPServer
import ssl

cert_file = "./localhost.pem"
key_file = "./localhost-key.pem"

class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
        self.send_header("Pragma", "no-cache")
        self.send_header("Expires", "0")
        SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self)

httpd = BaseHTTPServer.HTTPServer(
    ('', 8080),
    RequestHandler
)

httpd.socket = ssl.wrap_socket(
    httpd.socket,
    server_side=True,
    keyfile=key_file,
    certfile=cert_file
)

httpd.serve_forever()

Now run:

python server.py

Congratulate yourself

Whether you did it the easy way or the hard way, you got your server started. Now, move back to local test to have the development version show up:
Move to local test

When you load your page you should see what you saw before:
Hello world!

To be sure, edit the page and then refresh. You can also click the little arrow icon to pop it out to just focus on the extension.
Hello Twitch!

Stories

Build an Extension with Create React-App

Since extensions are such front-end heavy applications, it’s common to use modern frontend libraries like React. While these instructions are specific to React, they should extrapolate to other similar frameworks. For this section, please already have extension set to Local Test.

All you need to start is a working yarn installation, then you can run create-react-app:

yarn create react-app example-extension

This should take a little while as React gets your development environment set up. 

If you’re unfamiliar with create-react-app, it might be worth running yarn start in the directory yarn created. This will launch a server based on the react code in src/. Any code changes in src/ will cause live changes to the page create-react-app just opened. You’ll want to close it before continuing to the next steps.

There are two things we need to do to make this work with Extensions. First, we need to add the Extension Helper, then we need to add HTTPS support to our create-react-app install.

To add the Extension Helper, just add the extension helper script to public/index.html between <body> tags:

<!-- ... -->
  <body>
    <script src="https://extension-files.twitch.tv/helper/v1/twitch-ext.min.js"></script>
<!-- ... -->
  </body>
<!-- ... -->

Next, set up HTTPS. You can avoid this step in Google Chrome by setting a flag as described previously. If you do that, you can run yarn start now.

You’ll find install instructions for mkcert on its Github page.

Save the following script as install_cert.sh:

#!/usr/bin/env bash
mkcert -install \
    -cert-file cert.pem \
    -key-file key.pem \
    localhost
cat cert.pem key.pem > node_modules/webpack-dev-server/ssl/server.pem
rm cert.pem key.pem

Next, make some modifications to package.json to:

Modify the scripts part of your package.json to modify start and add postinstall as shown:

// ...
  "scripts": {
    "start": "HTTPS=true PORT=8080 react-scripts start",
    "build": "react-scripts build",
    "postinstall": "sh install_cert.sh"
// ...

Note that PORT=8080 changes the server to be served at https://localhost:8080, the default is https://localhost:3000. You can omit this, but you’ll need to change your Base URI.

Finally, run yarn postinstall to install your certificates, and yarn start to start the server.

With your server started, check out your cool new react extension!
new react Extension

Google Analytics

Use of Google Analytics is allowed in a Twitch Extension. However, due to restrictions on content, it must be included as a javascript file.

Typically, your Google Analytics snippet will look like this:

(function(i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function() {
        (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o), m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA12345678', 'auto');
ga('set', 'anonymizeIp', true);
Note: If you attempt to store or process any of this data, you will need to be compliant with European GDPR law about capturing IP address information.

With an Extension, you just need to add this to a file and import it. For example, if you had this saved as analytics.js, you might import it into your page with <script src="analytics.js"></script>.

If you’re using a more elaborate Javascript setup with a bundler, you can add it to any of your Javascript, such as src/App.js.

The Google Analytics script just inserts a <script async='https://www.google-analytics.com/analytics.js'></script>. You might wish to add this to your HTML directly, and instead call the ga() functions in your own javascript bundle.

Google WebFonts

Typically, an extension cannot load fonts outside of a Twitch Extension. This helps to prevent sneaky font attacks.

However Google WebFonts is allowed. Both of the forms allowed by Google WebFonts will work:

<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');

Pushing announcements to Extensions

Twitch Extensions can use PubSub to push out real time notifications to all extensions at once. This is a robust service based on the same technologies as Twitch Chat.

Architectural Reference

Configuration

Bits Support

The Bits Support config allows this extension to use APIs to request bits from the user via the Twitch.ext.bits Javascript APIs.

Base URI

The Base URI is the location of the folder containing your extension used in local testing. When an extension in testing loads, the Base URI is combined with the Panel Viewer Path, or Config Path, to work out where to load your web content from. Unlike a web origin it must end with /.

For example, if loading a Panel Extension in testing with Base URI https://localhost:8080/ and Panel Viewer Path index.html?view=panel, the Extension will load https://localhost:8080/index.html?view=panel.

Panel Viewer Path

The Panel Viewer Path is the path that will be loaded when a Panel Extension is loaded. It is combined with the Base URI to decide what location to load an extension from when an extension is in Local Test.

Panel Height

The Panel Height is the height in pixels of this extension when displayed as a Panel Extension on a broadcaster’s channel.

Config Path

The Config Path is the path that will be loaded when the config for an extension is shown to a broadcaster. It is combined with the Base URI to decide what location to load when an extension is in Local Test.

Live Config Path

The Live Config Path is the path that will be loaded when the extension is viewed from the Live module of the Twitch Dashboard. The streamer can then take actions via this page without leaving their dashboard. It is combined with the Base URI to get the full URL to load when an extension is in Local Test.

Type of Extension

The Type of Extension configuration field allows you to select how you want your Extension to be displayed. If an Extension type is not picked here, a user of your Extension will not be able to use it in that way. For example, if the ‘Panel’ Extension type is not picked, a user of this extension will not be able to activate it as a Panel Extension.

Identity

Opaque Identifier

Initially, an extension does not have access to the identity of the user that is using it. Twitch.ext.onAuthorized will return a special kind of userId called an Opaque Identifier. Opaque Identifiers come in two types:

Type State Persistent across sessions?
Anonymous User is logged out of Twitch No
Unauthorized User is logged into Twitch Yes

If a user is logged into Twitch, you will get an opaque userId starting with U-. This allows you to uniquely identify the user across sessions even before knowing who they are, but does not disclose their identity.

If the user is not logged into Twitch, you will get a userId starting with A-. This identifier will change between sessions.

Extension Types

Panel Extension

A Panel Extension sits with the rest of the user profile content at the bottom of a Twitch channel.
Panel Extension example

Overlay Extension

An Overlay Extension displays on top of the whole video as a transparent overlay.Overlay Extension example

Component Extension

A Component Extension displays as part of the video, taking up part of the screen. Component Extensions can be hidden by viewers.

Component Extension

Extension Helper

The Extension Helper is a Javascript library that runs inside an extension. Without it, an extension will not work. It exposes a Javascript API that can perform actions not normally accessible through our web APIs.

Security Model

The Extensions system deploys heavy sandboxing and Content Security Policy constraints to make it extremely hard to abuse an Extension. These elements make up the extensions Security Model.

%% Security Model Diagram
graph LR
    Twitch[Twitch.tv] --> | embeds | Supervisor[Extension Supervisor]
    Supervisor -->  | embeds | Extension[Twitch Extension]

ext-twitch.tv

%% ext-twitch.tv
graph TD
    ext-twitch.tv --> my-extension.ext-twitch.tv
    ext-twitch.tv --> your-extension.ext-twitch.tv

ext-twitch.tv is the host for all Extensions. For historical reasons, subdomains of a domain like myextension.twitch.tv are given certain access rights over twitch.tv. For example, due to historical Cookie Security policies, myextension.twitch.tv can add cookies to twitch.tv, potentially overwriting a user’s existing session.

For this reason, Twitch Extensions are all based on ext-twitch.tv. This causes the web browser to treat twitch Extensions as entirely separate to twitch.tv itself.

ext-twitch.tv is also the host of the Extension Supervisor, which embeds the extensions themselves.

Extension Supervisor

The Extension Supervisor is located at supervisor.ext-twitch.tv. When an Extension loads in Twitch, Twitch embeds the Extension Supervisor and sends a message to the supervisor indicating what extension to load.

The supervisor generates a Content Security Policy frame-src directive that prevents the Extension from loading anything other than the Extension, and then embeds the Extension itself as an iframe.

This prevents bad actors from making an Extension load content which isn’t vetted by the Extensions Review Process.

Restrictions on content

Content Type Policy Name Allowed
Default default-src Only the extension’s own files and the Extension Helper
Images img-src ✅ All
Sound and Video media-src ✅ All
Javascript-loaded remote content connect-src ✅ All
CSS style-src ✅ In <style> tags and style= attributes, and the extension’s own files
Fonts font-src Google WebFonts and the extension’s own files
Scripts (Javascript) script-src Google Analytics and the extension’s own files
Embedded iframe content frame-src
Insecure content block-all-mixed-content

Content Security Policy is a relatively new set of protocols that help to prevent hackers and other bad actors from taking over web content. Twitch Extensions’ policy specifies what kind of content can be loaded and from where. Since attackers goal is usually to inject their own code, the policy prevents nearly all cross-site scripting and redress attacks.

For reference, here’s Twitch Extensions’ current Content Security Policy:

Content-Security-Policy: connect-src https: wss: https://www.google-analytics.com https://stats.g.doubleclick.net; default-src 'self'; block-all-mixed-content; img-src * data: blob:; media-src * data: blob:; frame-ancestors https://supervisor.ext-twitch.tv https://extension-files.twitch.tv https://*.twitch.tv https://*.twitch.tech https://localhost.twitch.tv:* https://localhost.twitch.tech:* http://localhost.rig.twitch.tv:*; font-src https://fonts.googleapis.com https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self'https://extension-files.twitch.tv https://www.google-analytics.com;

Default restrictions on content

default-src is a Content Security Policy directive that is used as a fallback for all content that doesn’t have a specific policy associated with it.

For a Twitch Extension, this is the Extension’s own origin. That is, where there is no other specific policy, an Extension may only load its own files.

default-src 'self'

Restrictions on fonts

font-src is a Content Security Policy that restricts where fonts can be loaded from. In the case of extensions, this is set to 'self, https://fonts.googleapis.com, and https://fonts.gstatic.com. This allows use of Google WebFonts while helping to prevent sneaky font attacks.

font-src https://fonts.googleapis.com https://fonts.gstatic.com

Restrictions on CSS

style-src is a Content Security Policy directive that restricts where style information can be loaded from. In the case of extensions, this is set to 'self' 'unsafe-inline' and https://fonts.google.com. This means that stylesheets such as CSS can be loaded from the extension’s own files, Google WebFonts, and injected directly into the HTML.

This means that without proper cross-site scripting protection, an attacker can change the styles of a Twitch Extension.

style-src 'self' 'unsafe-inline' https://fonts.googleapis.com

Restrictions on scripts

script-src is a Content Security Policy directive that restricts where scripts such as Javascript can be loaded from. In the case of Extensions, this is set to 'self', https://extension-files.twitch.tv, and https://www.google-analytics.com. This means an extension can only load scripts from itself, https://extension-files.twitch.tv (where the Extension Helper is located), and from Google Analytics.

script-src 'self' https://extension-files.twitch.tv https://www.google-analytics.com;

Restrictions on Javascript HTTP requests

connect-src is a Content Security Policy directive that restricts what URLs Javascript can load data from.

For extensions, this is any https: or wss: (secure websocket) URL. This means that Javascript in an extension is allowed to connect to any secure websocket, or load any secure web content via the [XHR] or [fetch API]s.

connect-src https: wss:

Restrictions on images

img-src is a Content Security Policy directive that restricts where a webpage can load images. For a Twitch Extension, this policy allows *, data:, and blob:. This allows images to be loaded from anywhere on the internet, as well as from Javascript memory and inline [data URI]s. This means that potentially without proper [Cross-Site Scripting] protection, an attacker will be able to load images from anywhere on the internet into an extension.

img-src * data: blob:

Restrictions on media

media-src is a Content Security Policy directive that restricts where a webpage can load video and sound from. For a Twitch Extension, this policy allows *, data:, and blob:. This allows media to be loaded from anywhere on the internet, as well as from Javascript memory and inline data URIs. This means that potentially without proper cross-site scripting protection, an attacker will be able to load video and audio from anywhere on the internet into an extension.

media-src * data: blob:

Restrictions on Iframes

frame-src is a Content Security Policy directive that restricts what pages can be embedded in a webpage. In the context of a Twitch Extension, the Extension Supervisor sets a frame-src that prevents anything but the Extension itself being embedded.

Restrictions on browser APIs

Action Policy Name Allowed
HTML Forms allow-forms
Popup windows (of webpages) allow-popups
Unsandboxed popups allow-popups-to-escape-sandbox
Run Scripts allow-scripts
Have own web origin† allow-same-origin
Downloads without user interaction allow-downloads-without-user-activation
alert(), prompt(), print(), confirm(), other modals allow-modals
Lock the orientation of the screen allow-orientation-lock
Lock the pointer to the extension allow-pointer-lock
Start a presentation session allow-presentation-session
Access Twitch cookies with user confirmation allow-storage-access-by-user-activation
Navigate Twitch to somewhere else allow-top-navigation
Navigate Twitch to somewhere else, if the user clicks a link allow-top-navigation-by-user-activation

† allows the Extension to load its own content, use CORS, and set and read cookies amongst other things.

Extensions use iframe sandboxing to prevent Extensions messing with Twitch without permission. This includes opening popups that might appear to be from Twitch, locking the cursor to the Extension amongst other things. The current sandbox configuration is:

allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox