Stop Passing Data in URLs. I Learned This the Hard Way.
A practical note on using postMessage instead of URL parameters for cleaner, safer iframe communication in LWC.
You know that moment when something works, but deep down you know it is kind of wrong?
That was me with iframes in LWC.
At first, I was just stuffing data into the URL. Quick. Easy. Done.
<iframe src="https://example.com/page?userId=123&token=abc"></iframe>
And yeah, it worked. But every time I looked at it, it felt off.
Tokens in the URL. Long messy strings. And the worst part, changing anything meant rebuilding the whole URL again.
It is one of those things that feels fine until it suddenly is not.
So I Switched to postMessage
Honestly, this is one of those small changes that makes everything cleaner.
Instead of pushing data through the URL, you just send it after the iframe loads.
No clutter. No exposure. Just data moving where it should.
Sending Data (LWC -> iframe)
HTML:
<template>
<iframe src="https://example.com/page" onload={handleLoad}></iframe>
</template>
JS:
import { LightningElement } from 'lwc';
export default class IframePostMessage extends LightningElement {
handleLoad() {
const iframe = this.template.querySelector('iframe');
const message = {
userId: '123',
token: 'secure-token'
};
iframe.contentWindow.postMessage(message, 'https://example.com');
}
}
That is it. No weird URL building. No hacks.
Receiving It Inside the iframe
window.addEventListener('message', (event) => {
if (event.origin !== 'https://your-lightning-domain.com') {
return;
}
const data = event.data;
console.log('Got data from LWC:', data);
});
Quick side note. Do not skip the origin check.
I know it is tempting. I did it too at first. But yeah, do not.
And Then I Realized, It Goes Both Ways
This is the part that clicked for me.
You are not just sending data into the iframe. You can also get data back.
Which suddenly makes things way more flexible.
Sending Data Back (iframe -> LWC)
window.addEventListener('message', (event) => {
if (event.origin !== 'https://your-lightning-domain.com') {
return;
}
const data = event.data;
const response = {
status: 'success',
message: 'Data received'
};
window.parent.postMessage(response, event.origin);
});
Simple. You get something, you respond.
Catching That Response in LWC
import { LightningElement } from 'lwc';
export default class IframePostMessage extends LightningElement {
connectedCallback() {
window.addEventListener('message', this.handleMessage.bind(this));
}
handleMessage(event) {
if (event.origin !== 'https://example.com') {
return;
}
console.log('Response from iframe:', event.data);
}
}
And now you have got two-way communication.
No reloads. No URL tricks. Just clean messaging.
What Changed for Me
Look, this is not some best-practice lecture.
It just made my life easier.
- I stopped worrying about exposing data
- I did not have to rebuild URLs every time something changed
- Debugging became way less painful
It feels more like how things should work.
If You Are Still Using URL Params…
I get it. It is fast. It works.
But if you have ever had that feeling like “this is getting messy”, yeah, that is your signal.
Switch to postMessage.
Start simple:
- Send one object
- Validate origin
- Log everything first
And build from there.
Diagram
If you want to extend this later, you can add message types or a small message handler layer. But honestly, start simple.