Put Dium on your site
Mount the full app under your own path, link to your Flow, or embed the member map.
Pick the right option
Dium does not have a drop-in JavaScript widget. You have three real options.
| Option | What members see | Sign-in | Needs |
|---|---|---|---|
| Mount (recommended) | The full Dium app under your own path, such as yoursite.com/learning/dium/ | Works, first-party | A server that runs PHP, and a partner key |
| Link out | Your Flow on dium.io | Works, with the token flow | Nothing, or a key for autologin |
| Member map iframe | A map of Flow members who share a location | Not needed | Your origin on our allowlist |
How the mount works
The mount is a short PHP file from Dium that you place on your own server. When a member opens a page under your path, it:
- Reads the path and works out which Flow, Wave or Page to show.
- Writes a small config object,
window.__DIUM, with Dium's API address, yourbasePath, yourpartnerId, a back link and an app name. - Picks up a
dium_tokenfrom the URL after autologin, stores it inlocalStorageand removes it from the address bar. - Checks the session with
auth.phpvalidateusing a Bearer header. - Loads Dium's app script from
https://dium.io/api/js/app.js.
Before it works, we register your key, allow your origin and add your host to the redirect allowlist. See how to get a key.
Install by platform
Works on any host that runs PHP, such as Apache or nginx with PHP-FPM.
- Copy the mount template we send you to a folder on your site, such as
/learning/dium/index.php. - Put
dium_config.phpoutside the web root and fill inDIUM_PARTNER_ID,DIUM_PARTNER_KEYandDIUM_BASE_PATH. - Route every path under the mount to
index.php. - Link to
/learning/dium/from your navigation.
your-site/
learning/
dium/
index.php <- copy of Dium's partner mount template
.htaccess <- sends every sub-path to index.php
private/
dium_config.php <- DIUM_PARTNER_ID, DIUM_PARTNER_KEY, DIUM_BASE_PATH (outside the web root)# learning/dium/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]React runs in the browser, so it cannot host the PHP mount. Serve the mount path from your web server and link to it.
// The mount path is served by your web server, not by React Router.
// Use a normal link so the browser does a full page load.
export function CommunityLink() {
return <a href="/learning/dium/">Open the community</a>;
}
// If you run a single-page app, make sure your host sends /learning/dium/*
// to the PHP mount before it falls back to index.html.On Vercel, Netlify or a CDN, you can instead run the mount on a subdomain such as community.yoursite.com, on a small server you control.
WordPress runs PHP, so the mount can live next to it. WordPress's own rewrite rules pass real folders through untouched.
1. Upload the mount template to /learning/dium/index.php in your web root
(the folder that holds wp-admin and wp-content).
2. Add the .htaccess below to that folder.
3. Put dium_config.php outside the web root and set its path in index.php.
4. Visit https://yoursite.com/learning/dium/ and sign in.# learning/dium/.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]Do not paste the template into a page or a shortcode. It must run as its own PHP file so it can control the whole page.
Webflow hosting does not run PHP, so the mount cannot live on a Webflow path. You have two options:
- Subdomain. Run the mount on a small PHP server at
community.yoursite.comand point a DNS record at it. - Link. Link straight to your Flow on dium.io.
Add the link with an Embed element or a normal button.
<!-- Paste into a code block, embed element or custom HTML section -->
<a href="https://community.yoursite.com/" class="community-button">
Join the community
</a>Squarespace does not run your server code, so the mount needs a subdomain you control, or a plain link to your Flow on dium.io.
<!-- Paste into a code block, embed element or custom HTML section -->
<a href="https://community.yoursite.com/" class="community-button">
Join the community
</a>Squarespace code blocks work for the link. They cannot host the full app.
Shopify themes cannot run PHP. Run the mount on a subdomain such as community.yourstore.com, or link to your Flow on dium.io from your theme.
<!-- Paste into a code block, embed element or custom HTML section -->
<a href="https://community.yoursite.com/" class="community-button">
Join the community
</a>If you want a store customer to arrive signed in, send the link through your own server first, which mints a one-time token as shown in the SSO guide.
Theming
Each Flow has its own logo, banner, primary colour and accent colour, set by the Flow owner in settings. The mounted app uses them. There is no custom CSS hook or theme API for partners today.
The member map iframe
The member map shows Flow members who have a location on their Moat profile. It is the only Dium surface built to sit in an iframe. It sends a Content-Security-Policy: frame-ancestors header built from Dium's allowed origins, so it only renders on sites we have approved.
<iframe
src="MAP-URL-WE-SEND-YOU"
title="Community member map"
width="100%" height="480"
loading="lazy"
style="border:0;border-radius:16px"></iframe>Sizing. The map does not resize itself. Give the iframe a fixed height, as above.
Security
- No framing of the app. Dium's app pages send
X-Frame-Options: DENY. That blocks clickjacking and is why the mount runs on your domain instead of in a frame. - Allowlists, not wildcards. Framing is controlled with CSP
frame-ancestorslisting exact origins. The oldALLOW-FROMoption no longer works in browsers. - postMessage. Dium does not send or listen for
postMessagetoday. If you add your own messaging to a frame, checkevent.originwith an exact match, never post to"*", and never put message data intoinnerHTML. - Keys stay on the server.
dium_config.phpholds your partner key. Keep it outside the web root.
// Only if YOU add messaging between your page and a frame you control.
// Dium itself does not send postMessage events today.
const TRUSTED = "https://dium.io";
window.addEventListener("message", (event) => {
if (event.origin !== TRUSTED) return; // exact match, not includes()
const msg = event.data;
if (!msg || typeof msg.type !== "string") return;
statusEl.textContent = String(msg.text ?? ""); // textContent, never innerHTML
});
frame.contentWindow.postMessage({ type: "hello" }, TRUSTED); // never "*"