The Modern Embed system lets you embed your Higher Logic Vanilla (Vanilla) community directly inside your own website. Your visitors interact with the community without ever leaving your site - discussions, knowledge base articles, groups, and more all appear as part of your page.
- In this article, we'll walk you through enabling the Modern Embed, configuring it in your Dashboard, and adding the embed to your website.
- A Technical Reference section at the end covers implementation details for developers working on deeper integrations.
NOTE: The Modern Embed replaces the legacy/advance embed system for full community embedding. If you need embedded comments (for blog posts or articles) or WordPress-specific integrations, continue using the legacy embed system - the Modern Embed does not support those use cases.
What you'll need
- Admin access to your Vanilla Dashboard.
- Access to your website's HTML (or a developer who can add a small code snippet to your site).
- If using SSO, a configured SSO connection on your community. The
ssostring attribute supports jsConnect specifically (see Embedded SSO with jsConnect), but JWT, OAuth2, and SAML SSO also work within the embed - see SSO Authentication for details on each connector. For a general introduction to SSO in Vanilla, see Introduction: What is SSO?
Enable the Modern Embed
The Modern Embed needs to be turned on before you can configure it.
- In your Dashboard, navigate to Settings > Labs.
- Find the "New Embed System" option and toggle it on.
Once enabled, your community's embed settings page will switch to the Modern Embed configuration.
Troubleshooting: If you see "Event was not an embedEvent" errors in your browser console after setting up the embed, the community is still running the legacy embed script. Verify that the New Embed System toggle is enabled in Settings → Labs. In some cases, an additional backend feature flag (newEmbedSystem) may also need to be enabled - contact Vanilla Support if the Labs toggle alone does not resolve the error.
Configure Your Embed Settings
- In your Dashboard, go to Settings > Embed.(When the New Embed System is enabled in Labs, this redirects to the Modern Embed settings
- Enable Embed My Community.
- In the Remote URL field, enter the full URL of the page on your website where the community will appear (for example,
https://www.example.com/community). - Optionally, enable Force Embedding - when this is turned on, visitors who go directly to your community URL (for example, from an old bookmark or a search engine result) will be automatically redirected to your website's embedded version instead. This ensures everyone has a consistent experience on your site.
Add the embed to your website
Adding the embed requires two lines of code on your website. You (or your developer) will add a script tag and a custom HTML element to the page where you want the community to appear.
<!-- 1. Load the embed script -->
<script src="https://your-community.example.com/api/v2/embed/script"></script>
<!-- 2. Place the community -->
<vanilla-embed
remoteurl="https://your-community.example.com"
style="height: 100vh;"
></vanilla-embed>
NOTE: The above is a very simplified version of the HTML in the Embedding section of the Dashboard. You can copy-paste directly from the Dashboard.Replace your-community.example.com with your actual community URL.
That's it. The script handles the rest. It creates the embedded view, keeps your page URL in sync with the community, and manages scrolling and cleanup automatically.
NOTE: Setting height: 100vh is recommended. Without an explicit height, the embed may collapse or display with too little space.
Configuration options
You can customize the embed's behavior using attributes on the <vanilla-embed> element. Here's a summary of what's available:
remoteurl (required)
The base URL of your Vanilla community. For hub/node sites (see Hub and Node), you can include a specific node slug to restrict the embed to that node (for example, https://community.example.com/my-node), or omit the slug to allow navigation across all nodes.
initialpath
The page the community opens to when a visitor first arrives. Defaults to / (the homepage). If the parent page URL already has a hash in it (see URL Sync below), the hash takes precedence over this setting. Changing this updates the page that users land on when navigating to your community from the parent site.
position
Controls how scrolling works. Two options are available:
- sticky (default): The community content sticks to the top of the viewport as the user scrolls, pushing your site's header out of view. This provides the most app-like, immersive experience.
- static: The community renders at its natural position in the page. The user scrolls your parent page to move through the community content, and your header stays visible. Use this if the embedded community is one section of a longer page.
ssostring
A jsConnect SSO string for seamless sign-in. When provided, users who are already signed in to your website are automatically signed in to the community as well - no extra login step required. See SSO Authentication below for important details.
style
Standard CSS styling applied to the embed element. height: 100vh is recommended for the best experience.
How URL Sync / Deeplinking Works
The Modern Embed keeps your website's URL and the embedded community's URL in sync using the URL hash (the part of the URL after the # sign).
Here's what this means in practice:
- When a user navigates to a discussion inside the embed, your website URL updates automatically. For example, if the user opens a discussion, the parent URL might look like:
https://www.example.com/community#/discussion/42/my-topic - When a user copies your website URL and shares it with someone, that person will land directly on the same community page - the embed then reads the hash and opens the right content.
- Browser Back and Forward buttons work as expected, navigating through the community's history.
This all happens automatically. No additional configuration is needed.
SSO Authentication
The Modern Embed is SSO-agnostic: any SSO connector that works in Vanilla works within the embed. The embed is an iframe with a communication layer, and the SSO flow happens inside it. That said, some connectors are a better fit for embedded contexts than others, and there are browser-level gotchas to be aware of.
Choosing an SSO Connector for the Embed
jsConnect is the best fit for embedded communities. It authenticates inline using a specially formatted SSO string - no redirects to external login pages, no cookie chain to manage during authentication. Your server generates the string, and it's passed directly to the embed. For full details on generating the SSO string, see Embedded SSO with jsConnect.
To use jsConnect SSO with the embed, pass the string using either:
- The
ssostring attribute on the <vanilla-embed> element, or - A
window.vanilla_sso JavaScript variable set before the embed renders.
If both are present, the attribute takes precedence.
JWT SSO is also a good fit. The JWT token can be passed as a URL parameter, which avoids the redirect-based cookie issues that affect other connectors during initial authentication.
OAuth2 and SAML work in the embed, but come with significant caveats. Both use redirect-based flows that send the user to an external identity provider (IdP) login page within the iframe, then redirect back. This introduces two challenges that can cause failures - see Third-Party Cookies and Identity Provider Framing below.
Third-Party Cookies and SSO
Because the embedded community loads inside an iframe on your website, any cookies the community sets are treated as third-party cookies by the browser. Modern browsers are increasingly restricting or blocking these:
- Safari blocks third-party cookies by default (Intelligent Tracking Prevention).
- Firefox partitions third-party cookies by default (Total Cookie Protection).
- Chrome is phasing out third-party cookies (Privacy Sandbox).
This affects SSO in two ways:
During authentication (OAuth2 and SAML): These connectors store temporary data in cookies or server-side sessions tied to cookies (for example, OAuth2 stores a state token). If the browser blocks the cookie during the redirect flow, the connector can't verify the returning request and the sign-in fails. Users may see errors like "Invalid/Expired state token" (OAuth2) or experience silent authentication failures (SAML).
jsConnect and JWT SSO are less affected here because they don't rely on a redirect chain.
After authentication (all connectors): Even when sign-in succeeds, the session cookie Vanilla sets afterward is still a third-party cookie. If the browser blocks or partitions it, the user may appear logged out when navigating to another page within the embed, or lose their session when the iframe reloads (for example, when using the browser Back button).
Identity Provider Framing Restrictions
Many identity providers (IdPs) set security headers (X-Frame-Options: DENY or Content-Security-Policy: frame-ancestors 'self') that prevent their login pages from loading inside any iframe. When an OAuth2 or SAML redirect happens inside the embed, the IdP's login page simply refuses to render - the user sees a blank page or a browser error.
This is not a Vanilla issue - it's the IdP enforcing its own security policy. Common IdPs that block framing by default include Azure AD / Microsoft Entra ID, Okta, and Google. Others vary by configuration.
jsConnect and JWT SSO are not affected because they don't redirect to an external IdP login page.
Recommendations
If you're setting up SSO for an embedded community, here's the practical guidance:
- jsConnect is the first choice for embedded contexts - it's the smoothest path with the fewest moving parts.
- JWT SSO is a good alternative if your application already has JWTs available.
- OAuth2 and SAML work, but may require workarounds such as opening the sign-in flow in a popup window instead of within the iframe, or having your parent application handle authentication before rendering the embed. See the Technical Reference: SSO Connector Details section for specific workaround options.
If your authenticated community needs to work reliably across all browsers (especially Safari and mobile), consider whether a first-party setup - where the community lives on its own domain and users navigate to it directly with SSO - may be a better fit than a full-page embed. You can still pass an sso parameter on the first request to sign users in seamlessly, avoiding the third-party cookie limitation entirely.
Force Embed and Admin Bypass
Force Embed
When Force Embedding is enabled (see Configure Your Embed Settings above), visitors who navigate directly to your community URL - for example, by following an old bookmark or clicking a search engine result - are automatically redirected to your website's embedded version.
This keeps the experience consistent: everyone interacts with the community through your website.
Admin Bypass
If you need to access the community directly (for example, to reach the admin Dashboard), you can bypass the force-embed redirect by appending ?bypassEmbed=true to the community URL:
https://your-community.example.com/dashboard?bypassEmbed=true
This sets a session flag so you won't be redirected again during that browser session.
Things to Keep in Mind
- Embedded comments and WordPress integrations are not supported. The Modern Embed is for full community embedding only. Continue using the legacy embed system for blog comment embeds and WordPress integrations.
- All SSO connectors work, but some fit better than others. The
ssostring attribute is jsConnect-specific, but JWT, OAuth2, and SAML all function within the embed through their normal flows. jsConnect and JWT are the smoothest options; OAuth2 and SAML may require workarounds. See SSO Authentication for details. - Third-party cookie blocking can affect SSO sessions. Safari, Firefox, and eventually Chrome restrict or block third-party cookies, which can cause users to appear logged out inside the embed even after successful authentication. If reliable SSO across all browsers is a priority, consider a first-party community setup. See Third-Party Cookies and SSO for details.
- Always set a height. Without
height: 100vh (or another explicit height), the embed element may not display correctly. - Hub/node sites work out of the box. Set the
remoteurl to include a node slug to restrict to one node, or omit the slug to allow all nodes. Subcommunity paths are preserved correctly in the URL hash. See Hub and Node for more on hub/node architecture.
Technical Reference
This section is intended for developers implementing the Modern Embed or building custom integrations around it. It covers the embed's internal architecture, event system, and SPA framework integration patterns.
Architecture Overview
The parent page includes a single <script> tag that registers the <vanilla-embed> custom element (a standard Web Component). When the element is added to the DOM, it creates an iframe pointing to the community. The embed script itself is safe to load at application startup - no iframes, listeners, or network requests are created until a <vanilla-embed> element is actually rendered into the DOM.
Communication between the parent page and the iframe happens via postMessage. The iframe-side script polls window.location.href every 200ms to detect navigation (including SPA/React Router navigation within the community) and sends updates to the parent. All incoming messages are validated by origin check - the parent only accepts postMessage events from the remoteurl origin.
postMessage Events
The iframe-side script sends the following event types to the parent page:
embeddedHrefUpdate
Sent when the community URL changes. Fires on page load and every 200ms when the URL changes (to catch SPA navigation).
Payload: { type, href, force? }
The parent strips the remoteurl prefix from href to get the relative path, then sets window.location.hash to that path (with SSO parameters stripped).
embeddedTitleUpdate
Sent when the page title changes. The parent updates document.title to match.
Payload: { type, title }
embeddedScrollUpdate
Sent when the scroll position changes. Used by the sticky positioning mode to coordinate scrolling between the parent page and the iframe.
Payload: { type, isScrolled, isScrollEnd }
navigateExternalDomain
Sent when a user clicks a link to a domain outside the community. The parent navigates to the external URL (since the iframe can't navigate the parent directly).
Payload: { type, href }
URL Sync Internals
The embed uses window.location.hash on the parent page for deeplink sync. When the hash changes (browser back/forward or manual editing), the embed detects this via a hashchange listener and loads the corresponding community page.
To avoid conflicts with browser history, the embed clones and replaces the iframe rather than modifying iframe.src directly.
Force-embed note: When force-embed is enabled, the redirect places the full community URL (not just the path) in the hash. The embed's createFrameUrl method handles both formats (it strips the remoteurl prefix if present).
SPA / React Integration
The <vanilla-embed> element is a standard Custom Element with lifecycle callbacks:
connectedCallback(): Fires when the element enters the DOM. Creates the iframe, registers event listeners.disconnectedCallback(): Fires when the element leaves the DOM. Removes event listeners. The iframe is garbage collected by the browser as part of normal DOM removal.
SPA frameworks like React trigger these callbacks automatically:
import { useEffect } from "react";// Load the embed script once at app startup.useEffect(() => { const script = document.createElement("script"); script.src = "https://your-community.example.com/api/v2/embed/script"; document.head.appendChild(script);}, []);// In your community route component:function CommunityPage() { return ( <vanilla-embed remoteurl="https://your-community.example.com" style={{ height: "100vh" }} /> );}
Expected flow looks like this:
Navigate to /community → React renders <vanilla-embed> → connectedCallback initializes the embed. Navigate away → React removes the element → disconnectedCallback cleans up. Navigate back → a fresh connectedCallback initializes a new embed.
Routing Compatibility
The embed uses window.location.hash for deeplinks. This is compatible with BrowserRouter (pathname-based routing) because the SPA owns the pathname and the embed owns the hash.
If your app uses HashRouter, the embed's hash management will conflict. You would need to switch to BrowserRouter or coordinate hash usage between the two systems.
Cleanup Behavior
When the element is removed from the DOM, disconnectedCallback removes the message and hashchange event listeners. The iframe and its children are garbage collected by the browser as part of normal DOM removal. For applications that rapidly mount and unmount the embed, the cleanup is sufficient (the browser handles iframe destruction).
Cross-Origin Requirements
The parent page and the community must be on different origins (the standard embed use case). The embed script validates message origins and only accepts postMessage events from the remoteurl origin.
jsConnect SSO Timestamp Requirements
When generating the jsConnect SSO string for the Modern Embed, the UNIX timestamp included in the payload must be close to the current time. Vanilla validates the timestamp on each request and rejects payloads where the timestamp is more than approximately 20 minutes away from the server's current time.
If the timestamp is stale - for example, because it was generated once at page build time and cached, or because the signing server's clock has drifted (users will fail to authenticate silently). The embed will load, but the user will appear logged out.
To avoid this:
- Generate the SSO string on each page load (or at least refresh the timestamp frequently) rather than caching it for long periods.
- Keep the server that generates the SSO string synchronized via NTP to avoid clock drift.
- If users report intermittent sign-in failures in the embed, check whether the timestamp in the SSO payload is within the ~20-minute window relative to the Vanilla server's time.
SSO Connector Details
This section provides a detailed technical breakdown of how each SSO connector behaves in an embedded context, what specifically breaks under third-party cookie restrictions, and what workarounds are available.
Vanilla's Framing Configuration
Vanilla correctly manages its own framing headers when embedding is enabled. The community allows framing from the configured Remote URL domain: it sends Content-Security-Policy: frame-ancestors 'self' <remote-domain> and does not send X-Frame-Options: SAMEORIGIN. When embedding is disabled, it sends X-Frame-Options: SAMEORIGIN.
This means Vanilla's own pages (sign-in forms, entry/connect pages, community pages) load correctly inside the embed iframe. The framing issue described under Identity Provider Framing Restrictions only applies to external IdP pages that Vanilla redirects to during OAuth2 and SAML flows.
jsConnect in Detail
Mechanism: The SSO string is appended as ?sso=STRING on the iframe URL and authenticated server-side on page load. No redirects, no external IdP pages, no cookie chain during authentication.
What breaks due to 3rd party cookie blocking: The authentication itself is unaffected (the SSO string is a URL parameter, not a cookie). However, the session cookie Vanilla sets after authentication is still a third-party cookie - the user may lose their session on subsequent page loads if the browser blocks it.
Embed compatibility: Excellent - this is the smoothest path for embedded contexts.
See Embedded SSO with jsConnect for the full implementation guide, and jsConnect Timestamp Requirements above for timestamp validation details.
JWT SSO in Detail
Mechanism: The JWT token is passed in the Authorization header or as a ?jwt=TOKEN query parameter on the entry URL. The parent application generates the JWT and can navigate the iframe to the auth endpoint directly.
What breaks due to 3rd party cookie blocking: Less affected for initial authentication (the JWT is passed as a URL parameter, not a cookie). But the resulting Vanilla session cookie is still a third-party cookie, so session persistence after the initial sign-in is subject to cookie blocking.
Embed compatibility: Good - no redirect chain to an external IdP, which avoids the framing and cookie problems during authentication.
OAuth2 in Detail
Mechanism: Vanilla redirects to the IdP's authorize endpoint → the user authenticates → the IdP redirects back to Vanilla with an auth code → Vanilla exchanges the code for a token and profile. All of these redirects happen within the iframe.
What breaks under 3rd party cookie blocking: OAuth2 sets a state token cookie before redirecting to the IdP. When the IdP redirects back, the browser may not send the third-party cookie, so the state token can't be verified. The user sees an "Invalid/Expired state token" error.
What breaks under IdP framing restrictions: If the IdP sets X-Frame-Options: DENY or frame-ancestors 'self', its login page refuses to render inside the embed iframe. The user sees a blank page where the login form should appear.
Workarounds:
- Open the sign-in flow in a popup window (
window.open) instead of within the iframe, then reload the iframe after authentication completes. - Have the parent page handle the OAuth flow entirely - get the token, exchange it for a Vanilla session, then render the embed with the user already authenticated.
- Use the Storage Access API where browser support allows.
- Configure the IdP to allow framing from the community's domain (if the IdP supports this setting).
SAML in Detail
Mechanism: Vanilla redirects to the IdP for authentication, and the IdP POSTs the SAMLResponse back to Vanilla. Vanilla stashes the profile in the server-side session and processes the connection.
What breaks due to 3rd party cookie blocking: SAML stashes data in the server-side session, identified by the session cookie. If the session cookie isn't sent (because the browser blocks it as a third-party cookie), the stash is empty when Vanilla processes the response, and authentication fails silently.
What breaks under IdP framing restrictions: Same issue as OAuth2 - the IdP login page may refuse to load inside the iframe. This is very common with SAML IdPs and often not configurable on the IdP side.
Workarounds:
- Open the SAML flow in a popup window or navigate
_top (the parent window) to the sign-in URL. - Have the parent application handle SAML authentication before rendering the embed.
- Pre-authenticate users on the parent side and pass a jsConnect or JWT token to the embed instead of using SAML directly within the iframe.
Connector Compatibility Summary
| jsConnect | JWT SSO | OAuth2 | SAML |
|---|
Works in embed? | Yes | Yes | With caveats | With caveats |
Needs redirects? | No | No | Yes | Yes |
Affected by 3P cookie blocking? | Session only | Session only | Auth flow + session | Auth flow + session |
Affected by IdP framing? | No | No | Yes | Yes |
Recommended for embed? | First choice | Good alternative | Needs workarounds | Needs workarounds |