Back to Homepage
Services

Frontend Architecture, Integration Engineering

Industry

B2B Marketplace

Year

2023-2024

A marketplace that runs inside its partners' websites.

"Can the whole marketplace live inside our site?" Real estate portals, financial advisors and industry directories asked the same question. Each wanted the full listing flow on its own domain, in its own brand, and none of them wanted to build or maintain a frontend for it. The marketplace itself was a Django monolith with server-rendered templates.

THE CHALLENGE

Four ways an iframe breaks

A full application inside a frame on someone else's page breaks in four places. The frame has a fixed height, so content overflows or shows a second scrollbar inside the first. A modal opens inside the frame's own viewport, so it gets clipped or lands behind the partner's header. Safari and Firefox block third-party cookies, so the Django session never arrives and the app cannot tell who is logged in. And the two pages share nothing: no DOM, no JavaScript scope, no cookies. The conventional answer is an API and a frontend the partner builds. For a marketplace with multi-step flows, server-side validation, PDF generation and dynamic pricing, that meant a second frontend to keep in sync with the first, on every partner site.

THE SOLUTION

One app that knows it is inside a frame

The decision was to keep the Django monolith and make it aware of its container. A helper called is_embedded_view() checks for an embed parameter or a session flag, and every template then extends _base_blank.html instead of _base.html: the same views and forms, without navigation, footer and branding. Only views meant for partners carry the xframe_options_exempt decorator; frame protection stays on everywhere else. Across the origin boundary the only bridge is postMessage. A MutationObserver watches the embedded document for size changes and sends a resize message, debounced to one every 100 ms, and the host applies the height with a CSS transition. The trade-off: the partner has to include a short script and trust a fixed set of message types. The alternative, a separate embeddable frontend, would have doubled the surface to maintain.

Embed detection and template switching, decided once per request:

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,
        ...
    })

Host and embed, talking

The page on the left is a partner site; the dashed box is the iframe. Change the brand colour and font size with theme sync on and off. Then switch off height reporting and navigate inside the embed: the inner scrollbar is the bug every partner would have reported. The log shows every message with its payload.

Partner brand colour
16 px

Commercial properties from our partner marketplace, listed here on our own domain.

  • Office floor, Vienna 3rd district1,250 m² · €2,400,000
  • Logistics hall, Linz4,800 m² · €6,900,000
  • Retail unit, Graz320 m² · €890,000
postMessage log2
  1. host → embed+0.0stheme:update{"brand":"#818cf8","fontSize":16}
  2. embed → host+0.0sready{"path":"/listings"}
1 / 1Messages, embed → host / host → embed
176 pxiframe height
176 pxEmbed content
0 pxCut off

The modal handshake, embed side and host side:

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
  }
});

Sessions without third-party cookies

The Django session cookie belongs to the marketplace domain, and a frame on the partner's domain is exactly the case in which Safari and Firefox refuse to send it. The initial embed request therefore carries the session state as URL parameters, and inside the frame the session key is kept in localStorage as a fallback. Logins survive the frame.

THE RESULT

A partner integration became a copy-paste job

A partner's agency now embeds the marketplace with an iframe tag and a script snippet; nobody on the partner side writes against an API or builds a frontend. The marketplace team keeps one set of templates and one deployment, and the embed has stayed as available as the platform itself since launch.

KEY METRICS

12+Partner sites live
99.9%Embed uptime
<2KBJS added to the partner page
CLIENT FEEDBACK

"A new partner sends us their brand colours and gets an iframe tag and a script snippet back. Their agency has the marketplace running on their domain within the hour, and nobody on their side has to write against our API."

Product Owner

B2B marketplace, platform integrations

FOR YOUR PROJECT

  • When it applies

    A working web app that partners want on their domain, in their brand, without building a frontend. Embedding beats an API when the flows are multi-step and server-rendered: the partner gets every future feature for free.

  • What to check

    Which views may be framed at all, since X-Frame-Options and CSP frame-ancestors are opt-in per view, and whether your login survives without third-party cookies in Safari and Firefox. Test that before you promise a partner a date.

  • What it needs

    A blank base template, a host script of a few dozen lines, and a protocol of three or four message types. Plan the most time for the cookie handoff and for modals; the height sync is the easy part.

FAQ

TECHNOLOGY STACK

Django
Python
JavaScript

We have enjoyed working with Daniel for 10 years now. We highly appreciate his fast response times around the clock and his all-round knowledge. Whether server configurations or programming, he always has the right solution.

Manuel Kasbarian - CEO, SophistiX

Follow in the footsteps of Manuel and bring your vision to life.

Get In TouchGet In Touch