In Defence of iFrames: The Most Misunderstood Extension Runtime
iFrames get dismissed as clunky legacy tech. Yet Figma, Atlassian, and Salesforce chose them deliberately. Here's why the dismissal is wrong.
In Defence of iFrames: The Most Misunderstood Extension Runtime
If you ask a developer what they think of iFrames, you'll get a familiar reaction.
A grimace. Maybe an eye-roll. Possibly a story about some painful cross-origin debugging session from 2015.
The conventional wisdom in platform engineering circles is clear: iFrames are legacy technology, a clunky workaround, a compromise you make when you haven't figured out something better. If your extension runtime uses iFrames, you're doing it wrong.
That conventional wisdom is incorrect.
Figma chose iFrames deliberately. Atlassian built their entire Marketplace ecosystem on them for years. Salesforce Canvas runs on iFrames. These are not accidental choices made by teams that didn't know better. They are considered architectural decisions made by people who understood the trade-offs and chose isolation over elegance.
This post is a defence of iFrames as an extension runtime. Not because they're perfect, but because the dismissal is lazy, and the laziness leads platform teams to build worse things.
Why iFrames get dismissed
The complaints are real. They're just not fatal.
Cross-origin messaging is verbose. Without a well-designed abstraction, window.postMessage is cumbersome. You're passing serialisable objects back and forth, handling async responses, and debugging messages that have no stack trace. It's not fun.
iFrames don't auto-size. If your plugin renders dynamic content, you need to explicitly signal resize events across the boundary. The browser won't do it for you.
The disconnect is visible. Native UI components in the host application don't automatically appear inside the iFrame. Custom fonts, theme variables, hover states: all of it has to be explicitly threaded through or recreated. At its worst, a plugin feels like a foreign object embedded in the product.
Async-only communication. You cannot make a synchronous call from the host into an iFrame. If your extension needs to read document state and return a result synchronously, you're working against the model.
These are genuine engineering problems. But they are all solvable engineering problems. And none of them are as serious as the problems you create when you choose a less isolated runtime to avoid them.
What iFrames actually give you
When you run an extension inside an iFrame, the browser enforces the isolation boundary for you.
That sentence deserves to sit on its own.
You do not need to write isolation code. You do not need a custom sandbox, a restricted JavaScript runtime, or a permissions enforcement layer. The browser's cross-origin policy guarantees that code inside the iFrame cannot access the host application's DOM, cannot read the host's cookies or storage, and cannot call APIs that the host hasn't explicitly exposed.
Every other runtime model requires you to build that boundary yourself. A sandboxed process runtime requires you to define and enforce capability APIs. A serverless runtime requires you to design and secure the invocation model. Both require ongoing maintenance as the platform evolves.
With iFrames, the browser is doing the heavy lifting. The boundary is structural, not implemented.
Dependency conflicts disappear. Each plugin runs in its own JavaScript context. A plugin that ships React 17 and a plugin that ships React 19 do not conflict. A plugin that uses jQuery doesn't poison the host's globals. CSS from one plugin cannot leak into another. The isolation that causes friction at the communication boundary is the same isolation that makes the ecosystem stable.
The security boundary is explicit and auditable. Everything that crosses from the plugin to the host must go through a structured message. If a plugin wants to modify the document, trigger an action, or read user data, that request is visible, serialisable, and controllable. The platform team defines the message API. Nothing happens implicitly.
This is the opposite of in-process plugin architectures, where plugins share memory and execution context with the host. In those models, the boundary is as strong as your code and your developer docs. In an iFrame model, the boundary is as strong as the browser.
Figma's deliberate choice
Figma's plugin architecture is one of the most studied extension systems in design tooling. When the team published their approach, they were clear that iFrames were part of the design, not a fallback.
The final system is a hybrid. Plugin logic that needs synchronous access to the Figma document runs in a Realms-based sandbox on the main thread. Plugin UI runs in an iFrame. The two parts communicate via a postMessage abstraction that Figma wraps into a cleaner API, so plugin developers don't deal with raw message handling.
The pure iFrame approach was initially ruled out for one specific reason: synchronous document access. Figma's document model has complex interdependencies, and requiring plugin authors to handle fully asynchronous reads and writes would have made the developer experience untenable. So they solved that specific problem with the Realms sandbox, and kept the iFrame for everything else.
The lesson isn't "iFrames failed and Figma had to find something better." The lesson is that Figma identified exactly which problem iFrames don't solve well (synchronous document access) and engineered around it, while keeping the iFrame boundary for UI, browser APIs, and network access.
That is how you evaluate a runtime model: understand what it's good at, understand what it isn't, and compose accordingly. The dismissal of iFrames never gets this specific. It just says they're bad and moves on.
Atlassian Connect: a whole ecosystem on iFrames
Before Forge, Atlassian's primary extensibility model was Connect.
Connect worked like this: third-party developers built apps that hosted their own services. Atlassian products embedded those apps via iFrames, with REST APIs for data access and a JavaScript bridge for communication. The apps lived outside Atlassian's infrastructure entirely.
This architecture ran the Atlassian Marketplace for years. Thousands of apps. Hundreds of thousands of installations across Jira, Confluence, and Bitbucket. Real commercial businesses were built on it.
Connect's limitation wasn't the iFrame. It was the operational burden on developers, who had to run their own servers, handle their own auth, and manage their own data residency. Atlassian's answer was Forge: a serverless runtime that moved execution into Atlassian's infrastructure. Forge still uses iFrames for custom UI via its Custom UI model. The boundary is the same. What changed is where the logic runs.
Connect is moving to end-of-support now, but not because iFrames failed. Because the hosting model changed. The isolation model worked.
The real decision you're making
When you choose an extension runtime, you're not just choosing a technology. You're distributing security responsibility.
With an in-process plugin model, the platform team owns the safety of everything the plugin can do. If a plugin can access shared state, the platform team has to audit every API. If a plugin can block the main thread, the platform team has to enforce resource limits. If a plugin crashes, the platform owns the blast radius.
With iFrames, the browser owns the isolation boundary. The platform team designs the message API, which is a much smaller surface. A plugin cannot access what you haven't explicitly exposed. A crash in the iFrame does not crash the host.
This is why iFrames are not the lazy choice. They are the choice that lets you sleep at scale.
The in-process model gives developers more power. The iFrame model gives the platform more control. For most extension surfaces, especially UI panels, contextual tools, and dashboard widgets, control is the right trade.
Solving the real pain points
The problems that generate the eye-rolls are solvable.
Cross-origin messaging: Wrap postMessage in a typed abstraction. Define a message schema and generate the client code. Plugin developers should never need to think about raw postMessage. Figma did this. Your platform can too.
Sizing: Pass resize signals via the message layer. The plugin reports its rendered height, the host resizes the frame. This is a few dozen lines of code.
Styling: Provide a design token API that the host injects into the iFrame on load. Let plugin developers consume your theme variables. It won't be seamless, but it will be consistent.
Async-only communication: Design your extension points to be async-friendly. For cases where synchronous reads are genuinely required, consider the hybrid approach Figma used: a secondary synchronous API surface for document access, with the iFrame handling everything else.
None of these solutions are trivial. But none of them are harder than building a correct process sandbox from scratch.
When iFrames are the wrong choice
To be clear: iFrames are not always right.
They are not suited for extensions that need to run in the background, process data asynchronously, or integrate at the server side. Automation triggers, data transformations, webhook handlers: these belong in serverless or sandboxed process models.
They are also not suited for extensions that require deeply synchronous integration with core platform logic, where the latency of message passing would make the experience unusable. Low-latency audio processing, real-time rendering pipelines, game engine plugins: these need closer integration than iFrames can provide.
The previous post in this series covers when to use each model and how mature platforms layer all three. The point here is not that iFrames win every comparison. It's that they deserve to be evaluated honestly, not reflexively dismissed.
The pattern that actually matters
The engineers at Figma, Atlassian, and Salesforce who chose iFrames for their extension surfaces weren't working around a lack of alternatives. They understood what isolation means at scale.
When you have thousands of third-party developers building extensions for your platform, you cannot audit every decision they make. You cannot test every version of every dependency they ship. You cannot predict every way a plugin might fail.
What you can do is design a runtime where failures are contained, where security boundaries are enforced without ongoing effort, and where a badly-behaved plugin can't take down the host.
iFrames do that. They're not elegant. But platform stability at scale is more important than elegance at the extension boundary.
The dismissal of iFrames is the dismissal of a technology that does exactly what you need, in favour of something that sounds more sophisticated but requires more work to be safe.
Build the safe thing. Solve the friction problems. They're smaller than the alternative.