For developers
Frameworks
Where the two lines go in Next.js, Nuxt, React, Vue, Angular, WordPress, Tilda and Bitrix.
On this page
Every stack uses the same two lines (Install on a site). Add the script once and
the element once, in the layout all pages share. Replace your-project with the project's id.
Plain HTML
Add the lines before </body> on every page, or in the template that builds them:
<script type="module" src="https://api.tolkly.io/widget.js"></script>
<tolkly-chat endpoint="https://api.tolkly.io" project="your-project"></tolkly-chat>
</body>Other server templates work the same way: Laravel's Blade layout, Django's base.html, Rails'
application.html.erb. Put the lines before </body> of the base layout.
Next.js (App Router)
The root layout stays mounted when the visitor navigates. Put the chat there:
// app/layout.tsx
import Script from 'next/script'
import type { ReactNode } from 'react'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
{children}
<tolkly-chat endpoint="https://api.tolkly.io" project="your-project"></tolkly-chat>
<Script type="module" src="https://api.tolkly.io/widget.js" strategy="afterInteractive" />
</body>
</html>
)
}next/scriptpassestype="module"on to the script it adds.- With
afterInteractive, the widget runs after React hydrates the page. - React 19 renders the custom element with its attributes as written.
- Add the type declaration below, or TypeScript rejects
<tolkly-chat>.
Next.js (Pages Router)
_app wraps every page and stays mounted on navigation:
// pages/_app.tsx
import type { AppProps } from 'next/app'
import Script from 'next/script'
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<tolkly-chat endpoint="https://api.tolkly.io" project="your-project"></tolkly-chat>
<Script type="module" src="https://api.tolkly.io/widget.js" strategy="afterInteractive" />
</>
)
}TypeScript and JSX
React 19 types read intrinsic elements from React.JSX. Declare the tag once in any .d.ts file
that your tsconfig.json includes, for example tolkly.d.ts:
import type { DetailedHTMLProps, HTMLAttributes } from 'react'
type TolklyChatProps = DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & {
endpoint?: string
project?: string
name?: string
greeting?: string
handoff?: string
teaser?: string
position?: 'left' | 'right'
theme?: 'site' | 'system' | 'light' | 'dark'
'send-icon'?: 'arrow' | 'plane' | 'forward'
collapse?: string
'data-identity'?: string
}
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'tolkly-chat': TolklyChatProps
}
}
}The import makes the file a module. Thanks to it, declare module 'react' adds to React's
types instead of replacing them. HTMLAttributes already covers lang, nonce, style and
className.
Do not pass open and inline in JSX. If the element has a DOM property with the same name,
React 19 sets the prop as that property. <tolkly-chat> has an open() method and a read-only
inline property. Write these attributes in index.html instead, or call open({ focus: false })
on the element from an effect.
Nuxt 3
Load the script from nuxt.config.ts and tell Vue the tag is a custom element:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [{ src: 'https://api.tolkly.io/widget.js', type: 'module' }],
},
},
vue: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('tolkly-'),
},
},
})Then put the element in app.vue, outside <NuxtPage>:
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<tolkly-chat endpoint="https://api.tolkly.io" project="your-project"></tolkly-chat>
</template>React with Vite
Put both lines in index.html, before </body>, outside the #root element that React renders
into. You do not need to change the React code. If you prefer to render <tolkly-chat> from JSX,
render it once in the root component and add the type declaration.
Vue 3 with Vite
Put both lines in index.html, before </body>, outside the #app element. If you prefer to use
the tag in a Vue template, tell the compiler it is a custom element:
// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: { isCustomElement: (tag) => tag.startsWith('tolkly-') },
},
}),
],
})Angular
Put both lines in src/index.html, before </body>, outside the app's root element. Do not add
the script to scripts in angular.json. That bundles it as a classic script, and it will not
run.
If you prefer to use the tag in a component template, allow custom elements there:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppComponent {}WordPress
Add the lines just before </body> in your theme's footer.php. Use a child theme, so a theme
update does not remove them. Or add them from the theme's functions.php:
add_action('wp_footer', function () {
echo '<script type="module" src="https://api.tolkly.io/widget.js"></script>';
echo '<tolkly-chat endpoint="https://api.tolkly.io" project="your-project"></tolkly-chat>';
});No access to theme files? Any plugin that adds custom code before </body> on every page will
do. Then clear the page cache, if the site has one.
Tilda
Add both lines to an HTML code block (T123). Put the block in a footer that every page uses, or on each page where the chat should show. Then publish the pages again.
1C-Bitrix
Add the lines just before </body> in the site template's footer.php. It is at
/local/templates/<template>/footer.php or /bitrix/templates/<template>/footer.php. If the
template is cached, clear the site cache afterwards.