Services

Frontend Architecture, Integration Engineering

Industry

B2B Marketplace

Year

2023-2024

Embedding a full Django application via iframe on partner sites.

The client's marketplace wasn't just a standalone platform - it needed to live inside partner websites. Real estate portals, financial advisors, industry directories. Each wanted the full listing flow embedded on their own domain, styled to match their brand, without maintaining a separate codebase. The solution: a single Django application that detects when it's running inside an iframe and adapts its entire rendering pipeline accordingly.

The Problem With iframes

iframes are the web's oldest embedding primitive - and one of its most frustrating. Drop a full application into an iframe on a third-party site and the problems start immediately: the iframe has a fixed height, so content overflows or shows double scrollbars. Modals open inside the iframe's viewport, not the parent page, so they get clipped or buried. Session cookies may be blocked by third-party cookie policies. And there's no native communication channel between the embedded app and the host page.

The conventional answer is 'just build an API and let the partner render everything.' But for a complex marketplace with multi-step flows, server-rendered templates, and form validation, rebuilding the frontend on every partner site was not an option. The application had to remain a Django monolith. It just needed to be aware of its container.

The embed detection function and template switching logic:

Python
def is_embedded_view(request):
    """Detect whether the current request is rendered inside an iframe."""
    if request.GET.get('embed') == 'true':
        request.session['is_embedded'] = True
    return request.session.get('is_embedded', False)


def get_base_template(request):
    """Switch template inheritance based on embed mode."""
    if is_embedded_view(request):
        return '_base_blank.html'   # stripped-down shell: content only
    return '_base.html'             # full chrome: nav, footer, branding


# In any view:
@xframe_options_exempt
def company_detail(request, slug):
    base = get_base_template(request)
    return render(request, 'company/detail.html', {
        'base_template': base,
        ...
    })

The Dual-Mode Architecture

At its core, the solution is a single boolean: is this request being rendered inside an iframe? A utility function called is_embedded_view() answers that question by checking for an embed query parameter or a session flag. When true, the entire template inheritance chain switches from the full-chrome _base.html (with navigation, footer, branding) to _base_blank.html - a stripped-down shell that renders only the content area.

Django's @xframe_options_exempt decorator is applied to every embeddable view. Without it, the browser's X-Frame-Options header would block the iframe from rendering at all. This is a deliberate, view-by-view opt-in, not a global disabling of frame protection.

The Message Protocol

Trace how cross-origin events flow between the embedded app and the host page.

Partner Site

Host page loads the Django application inside an iframe with an embed=true parameter.

iframe

Django detects embedded mode, renders _base_blank.html instead of the full chrome template.

postMessage

Cross-origin events (fullScreenOn, fullScreenOff, resize) bridge the iframe/parent boundary.

Parent JS

Host page listens for messages, resizes the iframe, and manages backdrop overlays.

Seamless UX

User experiences a unified interface - modals, forms, and navigation feel native to the partner site.

  1. 01Partner SiteHost page loads the Django application inside an iframe with an embed=true parameter.
  2. 02iframeDjango detects embedded mode, renders _base_blank.html instead of the full chrome template.
  3. 03postMessageCross-origin events (fullScreenOn, fullScreenOff, resize) bridge the iframe/parent boundary.
  4. 04Parent JSHost page listens for messages, resizes the iframe, and manages backdrop overlays.
  5. 05Seamless UXUser experiences a unified interface - modals, forms, and navigation feel native to the partner site.

Cross-Origin Messaging

The harder part isn't rendering - it's communication. The embedded app and the host page live on different origins. They can't access each other's DOM, they can't share JavaScript scope, and they can't read each other's cookies. The only bridge is window.postMessage().

When a modal opens inside the embedded app, it dispatches a fullScreenOn event to the parent window. The parent responds by expanding the iframe to fill the viewport and adding a backdrop overlay. When the modal closes, fullScreenOff contracts the iframe back to its content height. The modal's height is also communicated via postMessage so the parent can set the exact iframe dimensions, preventing the double-scrollbar problem.

The iframe doesn't fight the host page - it cooperates with it.

The postMessage protocol for modal lifecycle events:

JavaScript
// Inside the embedded Django app
function openModal(modalEl) {
  modalEl.classList.add('is-visible');

  // Tell the parent page: "I need fullscreen"
  const height = modalEl.offsetHeight;
  window.parent.postMessage({
    type: 'fullScreenOn',
    modalHeight: height
  }, '*');
}

function closeModal(modalEl) {
  modalEl.classList.remove('is-visible');

  // Tell the parent page: "Back to normal"
  window.parent.postMessage({
    type: 'fullScreenOff'
  }, '*');
}

// --- On the partner site: ---
window.addEventListener('message', (event) => {
  const iframe = document.getElementById('marketplace-embed');
  if (event.data.type === 'fullScreenOn') {
    iframe.style.height = event.data.modalHeight + 'px';
    iframe.classList.add('fullscreen-overlay');
  }
  if (event.data.type === 'fullScreenOff') {
    iframe.classList.remove('fullscreen-overlay');
    iframe.style.height = '';  // back to auto-resize
  }
});

The Hard Parts

Third-Party Cookie Blocking

Safari and Firefox block third-party cookies by default. An iframe on partnersite.com loading app.marketplace.com can't set or read cookies for marketplace.com. This breaks Django sessions entirely. The workaround: pass session state through URL parameters on the initial embed load, then use localStorage as a session-key fallback within the iframe context.

Height Synchronization

The iframe content changes height constantly - opening accordions, loading search results, paginating through listings. A MutationObserver watches the document body for size changes and debounces resize messages to the parent every 100ms. The parent applies the height with a smooth CSS transition to avoid visual jitter.

Modal Z-Index Handling

A modal inside an iframe can never visually escape the iframe's bounds. The workaround: when a modal opens, the iframe itself goes fullscreen within the parent page. The parent creates its own backdrop overlay, and the iframe expands to fill the viewport. From the user's perspective, it looks like a native modal. Behind the scenes, it's a coordinated postMessage exchange.

12+Partner Sites
99.9%Embed Uptime
0<2KBJS Overhead
WHAT THE CLIENT SAYS

"Our partners can integrate the full marketplace experience on their own sites in under an hour. No API integration, no frontend development - just an iframe tag and a script snippet."

Product Owner

B2B Marketplace · Platform Integrations

FAQ

Why not build a separate embeddable frontend?

How do you handle CSP and X-Frame-Options?

Does the embed work on mobile?

TECHNOLOGY STACK