The frame is a separate origin. The embedded app cannot read the partner's DOM, cookies or JavaScript, and the partner's page cannot read ours. The host script listens for a fixed set of message types and ignores everything else. On the marketplace side, Content Security Policy allows framing only from known partner domains.
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.
Commercial properties from our partner marketplace, listed here on our own domain.
postMessage log2
- host → embed+0.0s
theme:update{"brand":"#818cf8","fontSize":16} - embed → host+0.0s
ready{"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
Manuel Kasbarian - CEO, SophistiXWe 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.
Follow in the footsteps of Manuel and bring your vision to life.
Open to new ProjectsGet In Touch