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.ink/api/embed" data-site-id="YOUR_SITE_ID" data-api-url="https://threadline-comments-api.onrender.com" 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://threadline-comments-api.onrender.com). 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.ink/api/embed" data-site-id="YOUR_SITE_ID" data-api-url="https://threadline-comments-api.onrender.com" async></script>
WordPress
A WordPress plugin is available in the repository at integrations/wordpress. It adds a settings screen, automatic placement, and shortcode support. Follow the plugin README for installation; the manual snippet below is an alternative.
<?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.ink/api/embed"
data-site-id="YOUR_SITE_ID"
data-api-url="https://threadline-comments-api.onrender.com"
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.ink/api/embed"
data-site-id="YOUR_SITE_ID"
data-api-url="https://threadline-comments-api.onrender.com"
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.ink/api/embed';
script.async = true;
script.dataset.siteId = 'YOUR_SITE_ID';
script.dataset.apiUrl = 'https://threadline-comments-api.onrender.com';
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.ink/api/embed"
data-site-id="YOUR_SITE_ID"
data-api-url="https://threadline-comments-api.onrender.com"
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://threadline-comments-api.onrender.com
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.ink/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.ink/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.ink/api/embed';
script.async = true;
script.dataset.tlEmbed = '1';
script.dataset.siteId = 'YOUR_SITE_ID';
script.dataset.apiUrl = 'https://threadline-comments-api.onrender.com';
document.body.appendChild(script);
}, []);
return <div id="threadline-comments" />;
}Comment counts outside the widget
Show a live count beside article links in your homepage, category pages, cards, or navigation without loading the full comment widget. Add the count attribute to each link and load the count script once per page.
<a href="https://your-site.example/article" data-threadline-count> <span class="threadline-count-value">0</span> Comments </a> <script src="https://threadline.ink/api/count.js" data-site-id="YOUR_SITE_ID" data-api-url="https://threadline-comments-api.onrender.com" async></script>
The link's href identifies the article URL. Use data-threadline-url if the comment thread uses a canonical URL different from the link destination. Wrap the number in threadline-count-value to preserve the surrounding label and icons.
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