Embed Script Options
All configuration through data-* attributes. The script loads with async so it does not block page render.
<div id="threadline-comments"></div> <script src="https://threadline.io/api/embed" data-site-id="YOUR_SITE_ID" data-api-url="https://api.threadline.io" async></script>
Attributes Reference
| Attribute | Type | Description |
|---|---|---|
data-site-idRequired | string | Your 64-character Site ID (API key) from the dashboard. Required. |
data-api-urlRequired | string | Comments API base URL (e.g. https://api.threadline.io). Required when the embed script is loaded from a different origin than the API. |
data-page-url | string | Stable page URL for the thread. Defaults to the current page URL without query/hash. |
data-page-title | string | Override the page title shown in the dashboard. Defaults to document.title. |
data-theme | "light" | "dark" | "auto" | Force a color scheme. Defaults to "auto" (respects prefers-color-scheme). |
data-language | string | ISO 639-1 language code. Defaults to browser language. Supports: en, es, fr, de, pt, ja. |
data-sort | "latest" | "newest" | "oldest" | Default comment sort. Latest ranks best comments first. Visitors can change this. Defaults to latest. |
data-app-url | string | App base for config fetches. Use a same-origin path (e.g. /threadline) for CSP-proof first-party installs. |
data-auth-url | string | Threadline app origin for login popups. Keep the real Threadline URL even when using first-party rewrites. |
data-max-depth | number | Maximum nesting depth for reply threads. Defaults to 6. Set to 1 for flat comments. |
Plain HTML
Paste near the bottom of the page. Works on desktop and mobile browsers.
<div id="threadline-comments"></div> <script src="https://threadline.io/api/embed" data-site-id="YOUR_SITE_ID" data-api-url="https://api.threadline.io" async></script>
WordPress
Add the footer hook in functions.php and place the comments container in your post template. A dedicated plugin is planned.
<?php
// Add to your theme's functions.php — loads the async embed on posts/pages.
add_action('wp_footer', function () {
if (is_single() || is_page()) {
echo '<script src="https://threadline.io/api/embed"
data-site-id="YOUR_SITE_ID"
data-api-url="https://api.threadline.io"
async></script>';
}
});
// In single.php / page.php (or comments.php), place the mount BEFORE the footer script:
echo '<div id="threadline-comments"></div>';Ghost
Inject the script via Settings → Code Injection → Site Footer, and put the container in post.hbs.
{{!-- Mount in post template; script can live in Site Footer --}}
<div id="threadline-comments"></div>
<script
src="https://threadline.io/api/embed"
data-site-id="YOUR_SITE_ID"
data-api-url="https://api.threadline.io"
async></script>Astro
Use a client-side .astro component so the loader runs after HTML is sent.
---
// src/components/ThreadlineComments.astro
---
<div id="threadline-comments"></div>
<script>
// Runs client-side only — async so it does not block page render
const script = document.createElement('script');
script.src = 'https://threadline.io/api/embed';
script.async = true;
script.dataset.siteId = 'YOUR_SITE_ID';
script.dataset.apiUrl = 'https://api.threadline.io';
document.body.appendChild(script);
</script>
<!-- In your layout or post page: -->
<!-- import ThreadlineComments from '../components/ThreadlineComments.astro'; -->
<!-- <ThreadlineComments /> -->Next.js Example
'use client';
import Script from 'next/script';
export default function ThreadlineComments() {
return (
<>
<div id="threadline-comments" />
<Script
src="https://threadline.io/api/embed"
data-site-id="YOUR_SITE_ID"
data-api-url="https://api.threadline.io"
strategy="afterInteractive"
/>
</>
);
}React & Next.js integration guide
Prefer a small client component that injects the embed script once. Use env vars for site ID and API URL.
Environment variables
# .env.local NEXT_PUBLIC_THREADLINE_SITE_ID=your_64_char_site_id NEXT_PUBLIC_THREADLINE_API_URL=https://api.threadline.io
React (createElement + cleanup)
Creates the script with data-site-id / data-api-url, and clears window.__threadline__ on unmount so remounts can re-init.
'use client';
import { useEffect, useRef } from 'react';
export default function ThreadlineComments() {
const mounted = useRef(false);
useEffect(() => {
if (mounted.current) return; // prevent double-mount in Strict Mode
mounted.current = true;
const script = document.createElement('script');
script.src = 'https://threadline.io/api/embed';
script.async = true;
script.dataset.siteId = process.env.NEXT_PUBLIC_THREADLINE_SITE_ID!;
script.dataset.apiUrl = process.env.NEXT_PUBLIC_THREADLINE_API_URL!;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
// Allow widget re-init on remount
delete (window as any).__threadline__;
};
}, []);
return <div id="threadline-comments" />;
}Next.js (next/script)
Use strategy="afterInteractive" so the widget loads after hydration without blocking first paint.
'use client';
import Script from 'next/script';
export default function ThreadlineComments() {
return (
<>
<Script
src="https://threadline.io/api/embed"
data-site-id={process.env.NEXT_PUBLIC_THREADLINE_SITE_ID}
data-api-url={process.env.NEXT_PUBLIC_THREADLINE_API_URL}
strategy="afterInteractive"
/>
<div id="threadline-comments" />
</>
);
}Tips
- Strict Mode double-mount: React 18 Strict Mode runs effects twice in development. Guard with a
useRef(as in the React example) so you do not inject two scripts. - Put
<div id="threadline-comments">exactly once per page - the embed looks up that host element. - On cleanup, delete
window.__threadline__so client-side navigations can remount cleanly.
Static React snippet (hardcoded IDs)
'use client';
import { useEffect } from 'react';
export default function ThreadlineComments() {
useEffect(() => {
// Idempotent: don't inject a second copy if the bundle is already on the page.
if (document.querySelector('script[data-tl-embed="1"]')) {
document.dispatchEvent(new Event('threadline:remount'));
return;
}
const script = document.createElement('script');
script.src = 'https://threadline.io/api/embed';
script.async = true;
script.dataset.tlEmbed = '1';
script.dataset.siteId = 'YOUR_SITE_ID';
script.dataset.apiUrl = 'https://api.threadline.io';
document.body.appendChild(script);
}, []);
return <div id="threadline-comments" />;
}Caching & CORS
/api/embed—Cache-Control: public, max-age=300+ CORS*- Site config — cached 60s; comments proxy — short cache (5s) with stale-while-revalidate
- Script tags use
async/ Next.jsafterInteractiveso HTML parsing is not blocked