Docs
Get from zero to an embedded, on-brand audio player. Most setups take under five minutes.
On this page+
Getting started
Create a free account at app.butterreader.blog, then create your first player — a reusable, styled audio widget. You'll embed it on your site and it generates audio for the page it's on.
Every account starts free with managed AI voices (Chirp 3 HD + Gemini Flash). No API key required.
Two ways to use a player:
- Generate — paste text (or let the widget read the page) and we synthesize it.
- Upload — bring your own audio file and wrap it in your branded player.
Embedding the widget
Copy the snippet from your player and paste it where you want the player to appear. It works on any site that lets you add an HTML/embed/code block.
<script src="https://butter-reader.web.app/widget/AudioPlayerWidget.bundle.js"
data-player-id="YOUR_PLAYER_ID"></script>
Where the player renders: by default the widget injects itself right where the script tag is. To control placement precisely, add an empty container and the widget mounts into it:
<div data-butter-player-id="YOUR_PLAYER_ID"></div>
<script src="https://butter-reader.web.app/widget/AudioPlayerWidget.bundle.js"
data-player-id="YOUR_PLAYER_ID"></script>
Use one player per page. The widget reads the current page's content, so the same player ID can live on every post and each one converts its own page.
How content detection works
For CMS players, the widget reads the article off the page automatically — no hardcoded theme selectors. Here's exactly what it does, so you can structure your pages to convert cleanly:
- Title — it uses the first
<h1>on the page. - Content container — it looks for the nearest
<article>,<main>,[role="main"],.entry-content, or.post-contentaround that<h1>. If none exists, it falls back to the whole page<body>. - Body text — it reads text only from
<p>,<h2>–<h6>,<li>, and<blockquote>. Text inside bare<div>or<span>is ignored. - Noise removal — it skips
<nav>,<header>,<footer>, and anything whose class or id contains: nav, menu, sidebar, widget, ad, comment, share, social, related, tag, category, author, meta, breadcrumb, pagination, search. - Length — up to ~4,000 characters per conversion; longer posts are trimmed at a sentence boundary.
The single most important rule: put your real article text in semantic tags (paragraphs and headings), not in styled
<div>s.
Content rules & best practices
Follow these and auto-detection "just works" on almost any platform:
- Exactly one
<h1>— it's treated as the title. Extra<h1>s confuse it; use<h2>–<h6>for sub-sections. - Real text in
<p>,<h2>–<h6>,<li>,<blockquote>— not<div>/<span>. - Wrap the article in
<article>,<main>,[role="main"],.entry-content, or.post-content, with the<h1>inside it. - Put nav/footer in
<nav>/<footer>(or classes containing those words) so they're stripped from the audio. - Content must be in the page at load — text hidden in tabs/accordions or loaded later by JavaScript may be missed.
- Don't name your content wrapper with an excluded word (e.g.
post-meta,content-sidebar) — it'll be filtered out.
WordPress
Works out of the box — WordPress themes already wrap posts in <article> and
.entry-content / .post-content, which the detector recognizes.
- Edit your post and add a Custom HTML block where you want the player.
- Paste your embed snippet. No plugin, no FTP, no theme files.
- Publish.
To put it on every post automatically, add the snippet to your theme's single-post template (or a reusable block / pattern).
Webflow
Webflow pages are <div>-heavy, but its Rich Text element outputs proper
<p> / <h2> tags (wrapped in the class .w-richtext), so detection still works.
- Add an Embed element where you want the player and paste the snippet.
- Make sure your post title is an actual H1 element (Webflow Heading set to H1).
- Keep your body copy inside a Rich Text element.
- (Recommended for precision) set your player's Content selector to
.w-richtext(or add a custom class to your content wrapper and use that).
Webflow's navbar (.w-nav) and footer are filtered automatically by their class
names.
Squarespace
- Add a Code Block to your post/template and paste the snippet.
- Use the post's native title (Squarespace renders it as an
<h1>). - (Recommended) set your player's Content selector to
[data-content-field="main-content"](or.sqs-block-content) so only the article body is read, not block chrome.
Squarespace wraps text blocks in .sqs-block-content; body copy is in real
<p>/heading tags, so it converts well.
Ghost
Ghost's default (Casper/Source) theme wraps post content in .gh-content inside
an <article> — both recognized automatically.
- Add an HTML card to your post and paste the snippet.
- (If detection is off) set your player's Content selector to
.gh-content— or your theme's content class (some themes use.post-contentor.c-content).
To add it site-wide, drop the snippet into your theme's post.hbs template.
Shopify (blog)
Shopify blog articles render body copy with the rich-text class .rte.
- Add the snippet to your blog post template (
article.liquid/main-article.liquid) or via a Custom Liquid section. - (Recommended) set your player's Content selector to
.rte(or.article__content).
Plain HTML / custom sites
You have full control, so just structure the page semantically:
<article>
<h1>Your post title</h1>
<p>Your opening paragraph…</p>
<h2>A section</h2>
<p>More text…</p>
<ul><li>A point</li></ul>
</article>
<!-- player renders here -->
<script src="https://butter-reader.web.app/widget/AudioPlayerWidget.bundle.js"
data-player-id="YOUR_PLAYER_ID"></script>
One <h1> inside the <article>, body text in <p>/headings/<li>, and put
your menu/footer in <nav>/<footer>. That's it.
Other platforms
- Wix / Framer / Super (Notion): add the snippet via their custom-code / embed
feature. These are
<div>-heavy, so set a Content selector for your content wrapper, or ensure your text uses real paragraph/heading elements. - Medium & Substack: these don't allow custom
<script>embeds, so the widget can't run there. Host the canonical version of the post on a site you control and embed the player there.
Advanced: content selector
If auto-detection grabs the wrong text (common on heavily-templated builders), set a Content selector in your player settings — a CSS selector pointing at your article container. It takes priority over auto-detection.
Examples: .w-richtext (Webflow), .gh-content (Ghost), .rte (Shopify),
#article-body, .post-body.
Tip: open your page, right-click the article text → Inspect, and find the closest wrapper element that contains only the article — use its class as the selector.
Voices & models
Managed plans include two tiers:
- Natural (Chirp 3 HD) — clean, warm narrator. The default.
- Expressive (Gemini Flash) — steerable tone for stories, announcements, opinion.
Set a default voice per player so new audio is consistent.
Limits & billing
- Free: 10,000 characters / month.
- Monthly Pro / Yearly Pro: 100,000 characters / month.
Each conversion is capped at ~4,000 characters per request; long posts are chunked automatically. See Pricing for full plan details.
Troubleshooting
The player doesn't appear.
Check the script actually loaded (no Content-Security-Policy blocking
butter-reader.web.app), that the player isn't hidden for billing/quota reasons,
and that your site's domain is allowed for the player.
It reads the wrong text (menus, footer, the wrong section).
Make sure body copy is in <p>/heading tags, nav/footer use <nav>/<footer>,
and set a Content selector for your article container.
The title is wrong.
Ensure there's exactly one <h1>, and it's inside your content container.
Only part of the post is read. There's a ~4,000-character cap per conversion; longer posts are trimmed. Also make sure the full text is present in the page at load (not lazy-loaded or in a tab).
Still stuck?
Email app.butterreader@gmail.com or browse the blog.