Install PodSaid on any site
If your platform isn’t in the platform list, or you’re writing HTML
by hand, this is the doc for you. The widget is just two lines of
HTML — a <script> tag and a custom element — with no framework
requirements, no build step, and no runtime dependencies. If your
site can render <script> and a custom HTML element, you can host
the widget.
Time: less than a minute for the paste. Longer if you’re also learning the local-testing workflow.
What you need before you start:
- A PodSaid account with a feed set up (see Quickstart).
- The embed snippet from Console → Settings → Embed Code. Your
feed-idandpk_live_key are already substituted. - The domain your site is served from added to your Allowed Origins. See Allowed origins.
The snippet, explained
<script src="https://widget.podsaid.com/v1/widgets.iife.js" defer></script>
<ask-pod-widget
feed-id="yourfeedslug"
api-key="pk_live_xxxxxxxxxxxxxxxx">
</ask-pod-widget> Two lines, two jobs:
- The
<script>tag loads a self-contained JavaScript bundle from our CDN. The bundle usesdefer, so it downloads in parallel with the rest of the page and executes after the DOM is ready. Once it executes, it registers a custom element (ask-pod-widget) with the browser. - The
<ask-pod-widget>element is where the widget renders. Its attributes tell the widget which feed to answer questions for and which public API key to authenticate with. The element mounts a floating chat launcher; its own UI lives inside a Shadow DOM, so your page’s CSS can’t reach it and its CSS can’t leak out.
You can use the element with attributes only (as shown), or place
it anywhere in the page to control where the launcher mounts
relative to your layout. Most people use the second form — bare <ask-pod-widget> with attributes — and let the widget position
its floating launcher in the bottom-right by default.
Where to paste the snippet
For a floating chat launcher on every page, put the snippet just
before the closing </body> tag:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My site</title>
<!-- your page's head content -->
</head>
<body>
<!-- your page content -->
<script src="https://widget.podsaid.com/v1/widgets.iife.js" defer></script>
<ask-pod-widget
feed-id="yourfeedslug"
api-key="pk_live_xxxxxxxxxxxxxxxx">
</ask-pod-widget>
</body>
</html> Placing the snippet at the end of <body> (rather than inside <head>) means the browser parses your visible content first and
only then blocks on our widget script. The defer attribute
already means the script doesn’t block parsing regardless of
position, but end-of-body is the conventional spot for embed
snippets and avoids a class of layout-shift issues on slow
connections.
If your site has a single template file or a layout partial that wraps every page (e.g. a Hugo layouts/_default/baseof.html,
a Jekyll _includes/footer.html, an Eleventy _layouts/base.html,
or a hand-rolled include), paste the snippet there once and every
page inherits it.
Server-side templating languages — Rails ERB, Laravel Blade,
Django templates, PHP, Go’s html/template, Twig, anything else
that renders HTML on the server — behave the same as static HTML
from the browser’s perspective. Paste the snippet in the base
layout template.
Testing locally
There is one thing to know about local testing: you cannot test by opening the HTML file directly in your browser.
Why: browsers treat files opened as file:///Users/you/site.html as having no origin (or a null origin). Our API requires a real https:// or http:// origin so it can check your paste against
your Allowed Origins list. Requests from a file:// page will
fail with a CORS error.
What to do instead — spin up a tiny local server:
# Python 3, works on macOS and Linux out of the box
python3 -m http.server 8080
# Or, if you have Node
npx serve .
# Or, if you have a real dev server (Vite, Astro, Hugo, Jekyll,
# whatever), use that — its dev server already binds to a port Then open http://localhost:8080/your-page.html in the browser.
Add http://localhost:8080 to Console → Settings → Allowed
Origins (you can leave localhost in the allowlist permanently —
costs nothing).
Static-site generators — a quick note
If you’re on a static-site generator (Hugo, Jekyll, Eleventy,
Astro, Docusaurus, Zola, Pelican, mkdocs), the snippet goes in the
same place: in your base layout template, just before </body>.
Two extra things to know:
Templating braces conflict — if your generator’s template engine treats
{{as a variable interpolation (Hugo, Jekyll, Nunjucks, Liquid), it will try to parse the snippet. The current PodSaid snippet contains no{{or{%, so no wrapping is needed today. Future-proof it by using your engine’s raw block:- Hugo: wrap in
{{/* raw */}}...{{/* endraw */}}— Hugo doesn’t have a true raw block, but comments protect literal text. Alternatively, put the snippet in a partial file with no template directives so the template engine leaves it alone. - Jekyll / Eleventy (Liquid / Nunjucks): wrap in
{% raw %}...{% endraw %}.
- Hugo: wrap in
Asset pipelines — some SSGs (Astro, Eleventy with 11ty-image, Next.js in export mode) rewrite
<script src>URLs to hash them for cache-busting. Our widget URL is external (widget.podsaid.com) so it’s untouched by these pipelines. If you see the URL rewritten or 404-ing in your build output, tell us — that’s a config issue, not a widget issue.
Verifying it works
Open your live URL in a normal browser window.
Look for the floating launcher (default: bottom-right).
Click it. If a chat box slides in, you’re done.
Open DevTools console (⌥⌘I on Mac, F12 on Windows). Look for:
[podsaid-widget] v1.8.2 mounted (feed=yourfeedslug, api=https://api.podsaid.com)
If the widget appears but questions fail, jump to Widget errors — what each one means.
When the widget shows “still preparing your content”
If ingest hasn’t finished for your feed yet, the widget renders a friendly placeholder instead of an error. Your paste is correct — you just don’t have content to answer questions against yet. Watch progress under Console → Feed → Ingest status, or wait for the “your widget is live” email.
See How ingest works (and why it takes hours).
Match your brand
The widget renders inside a Shadow DOM, so your site’s CSS can’t reach the widget and the widget’s CSS can’t leak into your site — nothing in your theme will accidentally break the widget’s look. To make it match your church or podcast brand, the widget exposes a set of CSS custom properties and Shadow Parts you can target from your own stylesheet.
See Customization (CSS) for the full list, with copy-paste examples.
Still stuck?
- Errors index: Widget errors — what each one means
- Book a 15-minute call with Mark: /docs/support
- Email:
[email protected]— include the live URL and a screenshot of the browser console