nuxt-twitch-ext
nuxt-twitch-ext
Build your Twitch Extension and Extension Backend Service (EBS) in a single Nuxt project.
Features
- Manage only one project for the extension and its backend
- Preview extension pages in Local Test with
.html - Include the Twitch Extension Helper and type definitions
- EBS API endpoints with CORS support
- Client-side utility for EBS calls and public asset paths
- Server utilities for Twitch JWT verification
- Package extension environment assets as a ZIP
- Only extension pages are pre-rendered in the extension build
- Extension-only pages and assets are excluded from the app build
Quick Setup
- Add
nuxt-twitch-extdependency to your project
pnpm add nuxt-twitch-ext
- Add the module in your
nuxt.config.ts
export default defineNuxtConfig({
modules: ["nuxt-twitch-ext"],
});
Project Structure
A project structure with default directories might look like this:
app/
├── app.vue # Application root
├── pages/
│ ├── index.vue # Nuxt main page (Not an extension page)
│ ├── extension/ # Twitch extension pages
│ │ ├── config.vue
│ │ ├── panel.vue
│ │ ├── mobile.vue
│ │ ├── video_overlay.vue
│ │ └── video_component.vue
│ ├── ...
├── assets/ # Application assets
│ └── ...
server/
└── api/
├── ebs/ # EBS API endpoints
│ └── ...
└── ...
Configuration
| Option | Default | Description |
|---|---|---|
helperScript |
Twitch's Extension Helper | Script included in extension pages |
filename |
'twitch-ext.zip' |
Name of the generated archive |
pages.dirname |
'extension' |
Directory name for extension pages |
pages.prerender |
['config', 'panel', 'mobile', 'video_overlay', 'video_component'] |
Pages to pre-render for the extension |
ebs.enabled |
true |
Enable the EBS utilities and CORS handling |
ebs.dirname |
'ebs' |
Directory name for EBS API endpoints |
ebs.baseURL |
Production URL used by extFetch |
|
ebs.preflight.allowMethods |
['GET'] |
Methods allowed for EBS CORS preflight requests |
ebs.preflight.allowHeaders |
['Content-Type', 'Authorization'] |
Headers allowed for EBS CORS preflight requests |
For example, customize the archive name and CORS settings:
export default defineNuxtConfig({
modules: ["nuxt-twitch-ext"],
twitchExt: {
filename: "my-extension.zip",
ebs: {
baseURL: "https://your-app.example.com",
preflight: {
allowMethods: ["GET", "POST"],
allowHeaders: ["Content-Type", "Authorization", "X-Custom-Header"],
},
},
},
});
[!TIP] When the EBS is enabled,
twitchExt.ebs.baseURLmust be set to the production URL of the Nuxt server, so thatextFetchcan correctly target the production EBS endpoints.
Environment Variables
Provide the Twitch Extension Client ID and Extension Secret Key as server-only runtime configuration values.
NUXT_TWITCH_EXT_CLIENT_ID=your-extension-client-id
NUXT_TWITCH_EXT_SECRET_KEY=your-extension-secret-key
Extension Pages
The default directory for extension pages is app/pages/extension/. The directory name for
extension pages can be changed with twitchExt.pages.dirname.
When navigating between extension pages, use relative paths (./<page>) instead of root-relative
paths (/<page>).
<template>
<NuxtLink to="./another-page">Another Page</NuxtLink>
</template>
I recommend placing extension assets in the app/assets/ folder and referencing them from your
extension pages as ~/assets/<file>. For example:
<template>
<img src="https://raw.githubusercontent.com/Yizack/nuxt-twitch-ext/HEAD/~/assets/images/logo.png" alt="My Logo" />
</template>
Nuxt processes referenced assets for the extension build; unreferenced files in app/assets/ are
not included.
For assets that should be available in all environments, place them in public/. Nuxt serves these
files as-is in development and production, and the extension build includes them too.
Use the auto-imported extAsset helper for public assets referenced dynamically in an extension
page. It keeps root-relative paths in development and resolves them against the extension assets in
a Twitch build:
<template>
<img :src="extAsset('/images/logo.png')" alt="Logo" />
</template>
Development Environment
In development, extension pages are accessible at root level with .html for easy preview during
Twitch Extension Local Test status.
For example, app/pages/extension/<page>.vue can be accessed as /<page>.html
Twitch Extension Build
The module CLI runs Nuxt in twitchExt mode (nuxt-twitch-ext). In this mode, the build only
includes pages from app/pages/extension/ (or the directory configured by
twitchExt.pages.dirname).
For each extension page:
- The
extension/directory segment is removed from the route - If the page name matches one of the
twitchExt.pages.prerendervalues, it is pre-rendered as a root-level HTML file. - Nuxt's inline state is extracted into a sibling JavaScript file
For example:
app/pages/extension/panel.vue
is generated as:
panel.html
panel-nuxt-state.js
Regular application pages, such as app/pages/index.vue, are not included in the extension build.
The build also:
- Includes the Twitch Extension Helper
- Uses relative URLs for generated assets
- Ignores Nuxt's default
200.htmland404.htmlpages - Uses
twitchExt.ebs.baseURLas the base URL for theextFetchutility
Production App Environment
When Nuxt runs in production (nuxt build), pages under twitchExt.pages.dirname are excluded from
the application routes. Unlike development or the twitchExt build, this does not generate
extension pages or package an extension ZIP; regular application pages and endpoints remain
available.
When EBS is enabled, the module register a middleware for routes under
/api/<twitchExt.ebs.dirname>/** that:
- Adds
https://<client-id>.ext-twitch.tvas the allowed origin in CORS responses. - Answers
OPTIONSpreflight requests with the configured allowed methods and headers.
Call verifyTwitchExtension or verifyTwitchTransaction server utilities in EBS route handlers as
appropriate.
Auto Imports
Twitch Extension Client:
extAsset(path)returns the path for an asset within the Twitch extension, based on the current navigation URL and origin.extFetch(request, options?)instance of$fetchthat fetches data from the EBS endpoints, uses the current origin in development andtwitchExt.ebs.baseURLin the generated Twitch extension as the base URL for EBS requests.
Server:
verifyTwitchExtension(event)verifies the Twitch JWT token for your extension, returning the verified payload ornullif the verification fails.verifyTwitchTransaction(event, receipt)verifies a Bits transaction receipt for your extension, returning the verified payload ornullif the verification fails.
Working with the EBS
After Twitch authorizes the extension, extFetch automatically adds the current token as a Bearer
token in the Authorization header for requests to the EBS origin. Calls made before authorization
do not include the token.
[!IMPORTANT] Add the domain of
twitchExt.ebs.baseURLto the Allowlist for URL Fetching Domains in your Twitch Extension Capabilities settings. Without it, requests from the extension to the EBS may be blocked.
<script setup lang="ts">
onMounted(() => {
Twitch.ext.onAuthorized(async () => {
const data = await extFetch("/api/ebs/data");
});
});
</script>
Verify the token in the server route before processing the request:
export default defineEventHandler(async (event) => {
const payload = await verifyTwitchExtension(event);
if (!payload) {
throw createError({
status: 401,
message: "Invalid authorization",
});
}
return { channelId: payload.channel_id };
});
[!NOTE] You can set
twitchExt.ebstofalseto disable the EBS entirely.
Build the Extension
Add the following script to your package.json to build the Twitch extension:
"scripts": {
"build:twitch-ext": "nuxt-twitch-ext"
}
This runs nuxt generate --envName twitchExt and writes .output/twitch-ext.zip by default. Upload
that ZIP in your Twitch Extension files tab in the Twitch developer console.
Caveats
- An
index.vueat the root of the extension pages directory and defined in thetwitchExt.pages.prerenderarray does not generateindex.html. With the defaultpages.dirname: "extension",app/pages/extension/index.vuegenerates/extension.html. - Twitch's Extension Content Security Policy (CSP) blocks inline JavaScript, including inline
<script>elements. The extension build moves Nuxt's inline state script to a sibling*-nuxt-state.jsfile, but custom inline scripts are not rewritten. Put custom JavaScript in Vue component scripts or imported.js/.tsfiles so Nuxt can bundle it. - In the
twitchExtbuild, this module disables unsupported modules such as@nuxtjs/color-mode,@nuxthub/core, and@nuxtjs/sitemap. - Referencing assets from the
publicfolder without usingextAssetmay result in incorrect paths within the Twitch extension.
Related Packages
One utility function for module authors to define compiler constants for all bui...
Version History
| Version | Released | License |
|---|---|---|
| 0.2.11 | MIT | |
| 0.2.10 | MIT | |
| 0.2.9 | MIT | |
| 0.2.8 | MIT | |
| 0.2.7 | MIT | |
| 0.2.6 | MIT | |
| 0.2.5 | MIT | |
| 0.2.4 | MIT | |
| 0.2.3 | MIT | |
| 0.2.2 | MIT | |
| 0.2.1 | MIT | |
| 0.2.0 | MIT | |
| 0.1.4 | MIT | |
| 0.1.3 | MIT | |
| 0.1.2 | MIT | |
| 0.1.1 | MIT | |
| 0.1.0 | MIT | |
| 0.0.1 | MIT |