window.postMessage(), farklı origin'lerden gelen pencereler arasında (bir sayfa ile açtığı açılır pencere ya da gömdüğü iframe gibi) güvenli biçimde mesaj göndermeyi sağlayan yöntemdir. Gönderen hedefPencere.postMessage(veri, "https://hedef.example") çağırır, alan taraf message olayını dinler ve mutlaka event.origin değerini kontrol eder. İki taraf arasında özel bir hat için MessageChannel, aynı origin'deki tüm sekmelere yayın için BroadcastChannel kullanılır.
Bu yazıda postMessage() parametrelerini, targetOrigin kuralını, gelen olayın data, origin ve source alanlarını, MDN'in güvenlik uyarılarını, port tabanlı iletişimi ve sekmeler arası yayını anlatıyoruz. MessageChannel, BroadcastChannel ve URL örneklerini Node.js 24 ile çalıştırdık; bunlar tarayıcıda da aynı standarda göre çalışır. Worker'larla mesajlaşma Web Workers yazısında, konuların tamamı Web API öğrenme rehberinde.
Neden postMessage? Aynı origin politikası

Normalde farklı sayfalardaki betikler birbirine yalnızca aynı origin'den (aynı protokol, alan adı ve port) geliyorlarsa erişebilir. Bu kurala aynı origin politikası denir ve CORS yazısında ayrıntılı anlatılır. window.postMessage() bu kısıtlamayı, doğru kullanıldığında güvenli kalan, denetimli bir yoldan aşmayı sağlar: iki pencere birbirinin içine erişmez, yalnızca mesaj alıp verir.
Mesaj göndermek için önce hedef pencerenin Window nesnesine sahip olmanız gerekir. Bu nesne açılır pencereler için window.open() dönüş değerinden, iframe'ler için iframe.contentWindow özelliğinden, açılan pencerenin açan pencereye ulaşması için window.opener özelliğinden ve gelen bir mesaja yanıt vermek için event.source değerinden elde edilir.
Söz dizimi ve targetOrigin kuralı
postMessage(message)
postMessage(message, targetOrigin)
postMessage(message, targetOrigin, transfer)
postMessage(message, options) // { targetOrigin, transfer }message, structured clone algoritmasıyla seri hâle getirilir; bu sayede nesne, dizi, Map, Date gibi pek çok türü kendiniz JSON'a çevirmeden gönderebilirsiniz. Algoritmanın neyi kopyalayıp neyi kopyalayamadığını Web Workers yazısında örneklerle gösterdik. transfer, sahipliği karşı tarafa devredilecek transferable nesnelerin dizisidir; bunlar göndericide artık kullanılamaz ve mesajın içinde ya da başka bir yolla alıcıya ulaşabilir olmalıdır.
targetOrigin, mesajın teslim edilmesi için alıcı pencerenin sahip olması gereken origin'dir. Eşleşme tam olmalıdır: şema, alan adı ve port. Alıcı pencere o anda başka bir origin'deyse mesaj teslim edilmez. Verilmezse varsayılan değer "/"'dır, yani çağıran sayfanın kendi origin'i. "*" herhangi bir origin anlamına gelir. MDN, hedef belgenin nerede olacağını biliyorsanız her zaman belirli bir origin vermenizi ister; aksi hâlde kötü niyetli bir site, sizin haberiniz olmadan pencerenin konumunu değiştirip gönderdiğiniz veriyi ele geçirebilir. İki istisna vardır: data: adresleri opak origin'e sahip olduğu için onlara mesaj göndermek "*" gerektirir; file: adresli bir sayfaya göndermek de şu an "*" ister.
Örnek: sayfa ile iframe arasında mesajlaşma
Aşağıdaki örnekte https://panel.example.com üzerindeki sayfa, https://odeme.example.net adresinden gelen bir iframe'e mesaj gönderiyor ve yanıtı dinliyor. Kod MDN'deki açılır pencere örneğinin yapısını izler; iframe yüklenmeden mesaj gönderilmez.
<iframe id="odeme" src="https://odeme.example.net/widget.html" title="Ödeme"></iframe>
<p id="durum"></p>// https://panel.example.com üzerindeki sayfa
const iframe = document.querySelector("#odeme");
const durum = document.querySelector("#durum");
iframe.addEventListener("load", () => {
iframe.contentWindow.postMessage({ tur: "tutar", deger: 250 }, "https://odeme.example.net");
});
window.addEventListener("message", (event) => {
if (event.origin !== "https://odeme.example.net") return; // gönderen doğrulanmadan hiçbir şey yapılmaz
if (typeof event.data?.tur !== "string") return; // mesajın biçimi de doğrulanır
durum.textContent = event.data.tur;
});// https://odeme.example.net/widget.html içindeki betik
window.addEventListener("message", (event) => {
if (event.origin !== "https://panel.example.com") return;
if (event.data?.tur !== "tutar" || typeof event.data.deger !== "number") return;
// Yanıt için event.source ve event.origin kullanmak MDN'in önerdiği kalıptır
event.source.postMessage({ tur: "hazir" }, event.origin);
});Gelen olay: data, origin ve source
Alıcı pencerede tetiklenen MessageEvent nesnesinin üç önemli alanı vardır. data gönderilen değerin kopyasıdır. origin, postMessage() çağrıldığı anda gönderen pencerenin origin'idir: protokol, ://, alan adı ve protokolün varsayılanından farklıysa : ile port. Yani https://example.org 443 numaralı portu, http://example.net 80 numaralı portu ima eder, http://example.com:8080 ise portu açıkça içerir. source ise gönderen window nesnesine başvurudur ve iki yönlü iletişim kurmak için kullanılır.
Origin karşılaştırmasında elle dize kesmek yerine URL nesnesinin origin özelliğinden yararlanabilirsiniz; varsayılan port kuralını o da uygular. İzin verilen origin'leri bir kümede tutup tam eşitlik aramak, adres öneki ya da parçası üzerinden yapılan gevşek karşılaştırmaların açtığı kapıyı kapatır:
const IZINLI = new Set(["https://panel.example.com", "http://example.com:8080"]);
console.log(new URL("https://panel.example.com/giris?x=1").origin); // https://panel.example.com
console.log(new URL("https://example.org:443/").origin); // https://example.org
console.log(new URL("http://example.com:8080/sayfa").origin); // http://example.com:8080
const guvenilir = (origin) => IZINLI.has(origin);
console.log(guvenilir("https://panel.example.com")); // true
console.log(guvenilir("https://panel.example.com.evil.example")); // falseMDN birkaç ince ayrıntıyı daha not eder. origin değeri, o pencerenin şu anki ya da gelecekteki origin'i olduğunun garantisi değildir; pencere mesaj gönderildikten sonra başka bir adrese gitmiş olabilir. Gönderen pencere javascript: ya da data: adresi içeriyorsa origin, o adresi yükleyen betiğin origin'idir. Uluslararası alan adlarında (IDN) değer şimdilik tutarlı biçimde Unicode ya da punycode olmayabilir, bu yüzden böyle bir site bekliyorsanız iki biçimi de kontrol edin. document.domain değerinin ise origin üzerinde etkisi yoktur.
Güvenlik kuralları

- Beklemiyorsanız dinlemeyin: başka sitelerden mesaj almayı beklemiyorsanız hiç
messagedinleyicisi eklemeyin. MDN bunu güvenlik sorunlarından kaçınmanın kesin yolu olarak tanımlar. - Göndereni doğrulayın: mesaj bekliyorsanız gönderenin kimliğini her zaman
origin, gerekirsesourceile doğrulayın. Herhangi bir pencere, iframe hiyerarşisindeki herhangi bir pencereye mesaj gönderebilir. - Biçimi doğrulayın: gönderen güvenilir olsa bile gelen verinin sözdizimini kontrol edin. Güvendiğiniz sitedeki bir açık, kontrolsüz kullanılan veri üzerinden sizin sitenizde XSS açığına dönüşebilir; XSS konusu için XSS ve CSP yazısına bakın.
- Hedefi tam verin: başka pencerelere veri gönderirken
"*"yerine her zaman tam hedef origin verin. - Veriyi HTML olarak basmayın: gelen metni sayfaya yazacaksanız
innerHTMLyerinetextContentkullanın; farkları innerHTML ve textContent yazısında.
Zamanlama da önemlidir: postMessage() çağrıldıktan sonra MessageEvent, bekleyen tüm yürütme bağlamları bittikten sonra gönderilir. Örneğin çağrı bir olay işleyicisinin içindeyse o işleyici ve aynı olayın diğer işleyicileri tamamlanmadan mesaj teslim edilmez. Alıcıdaki işleyicinin hata fırlatıp fırlatmadığını gönderen taraf öğrenemez; bir onay gerekiyorsa alıcının yanıt mesajı göndermesi gerekir.
MessageChannel: iki uç arasında özel hat
Channel Messaging API, aynı belgeye bağlı farklı tarama bağlamlarında (iki iframe, ana belge ile bir iframe, iki worker) çalışan betiklerin, her ucunda bir port bulunan iki yönlü bir kanal üzerinden doğrudan konuşmasını sağlar. new MessageChannel() iki MessagePort üretir: port1 ve port2. Kanalı oluşturan taraf port1'i tutar, port2'yi window.postMessage() çağrısının aktarım listesiyle karşı tarafa devreder. Aktarılan port artık gönderen tarafta kullanılamaz.
// Ana sayfa (MDN örneğinden uyarlandı)
const channel = new MessageChannel();
const port1 = channel.port1;
iframe.addEventListener("load", () => {
port1.onmessage = (e) => {
output.textContent = e.data;
};
// port2 iframe'e aktarılır
iframe.contentWindow.postMessage("init", "https://widget.example.net", [channel.port2]);
});// iframe içindeki betik
let port2;
window.addEventListener("message", (e) => {
if (e.origin !== "https://panel.example.com") return;
port2 = e.ports[0];
port2.onmessage = (msg) => {
port2.postMessage(`Message received by IFrame: "${msg.data}"`);
};
});Karşı taraf portu olay nesnesinin ports dizisinden alır. Bundan sonraki tüm mesajlar window üzerinden değil bu porttan akar; böylece sayfadaki diğer message dinleyicileri bu trafiği görmez. MDN'in örneği ilk mesajda "*" kullanır; hedef origin'i biliyorsanız yukarıdaki gibi tam değer vermek yine daha güvenlidir. Port, onmessage atandığında örtük olarak başlar; addEventListener("message", ...) kullanıyorsanız port.start() çağırmanız gerekir. Kanal işi bitince port.close() ile kapatılır. Aynı mekanizmanın iki yönlü çalışmasını Node.js 24 ile denedik:
const kanal = new MessageChannel();
kanal.port2.onmessage = (event) => {
console.log("port2 aldı:", event.data); // port2 aldı: merhaba
kanal.port2.postMessage(`alındı: ${event.data}`);
};
kanal.port1.onmessage = (event) => {
console.log("port1 aldı:", event.data); // port1 aldı: alındı: merhaba
kanal.port1.close();
};
kanal.port1.postMessage("merhaba");MDN, kanal mesajlaşmanın en çok işe yaradığı durumu şöyle anlatır: oyun, adres defteri ya da müzik çalar gibi başka sitelerin yeteneklerini iframe'lerle kendi arayüzüne gömen bir site düşünün. Bu parçalar kendi başlarına çalışırken sorun yoktur; ama ana siteden adres defterine kişi eklemek ya da oyundaki skoru ana profile yazmak istediğinizde iki taraf arasında güvenilir bir yol gerekir. Origin'lerin birbirine güvenip güvenmediğini ve mesajların nasıl aktarıldığını düşünmek zorundasınız. Port tabanlı kanal, bir kez el sıkışıldıktan sonra yalnızca iki ucun eriştiği bir hat sunarak bu işi kolaylaştırır: portu kime verdiyseniz yalnızca o konuşabilir.
BroadcastChannel: aynı origin'deki tüm sekmelere yayın
Broadcast Channel API, aynı origin'deki pencereler, sekmeler, iframe'ler ve worker'lar arasında basit iletişim sağlar. MessageChannel'ın aksine karşı tarafa bir başvuru tutmanız gerekmez: aynı adla bir BroadcastChannel oluşturan her bağlam o kanala "abone" olur. Kurucu tek parametre alır, kanalın adı; o adla ilk bağlanan sizseniz alttaki kanal oluşturulur.
postMessage() ile gönderilen veri structured clone ile seri hâle getirilir ve kanala bağlı tüm BroadcastChannel nesnelerinde message olayı tetiklenir; mesajı gönderen nesnenin kendisi hariç. Seri durumdan geri çevrilemeyen mesajlarda messageerror tetiklenir. Kanaldan ayrılmak için close() çağrılır; nesne kanaldan kopar ve çöp toplayıcı tarafından temizlenebilir. MDN, bu API'nin oturum açma ya da kapama gibi kullanıcı eylemlerini aynı origin'deki diğer sekmelerde algılamak için kullanılabileceğini örnekler. Aşağıdaki örnekte üç nesne aynı kanala bağlanıyor; gönderen A kendi mesajını almıyor:
const sekmeA = new BroadcastChannel("oturum");
const sekmeB = new BroadcastChannel("oturum");
const sekmeC = new BroadcastChannel("oturum");
sekmeA.onmessage = () => console.log("A kendi mesajını aldı"); // hiç çalışmaz
sekmeB.onmessage = (event) => console.log("B:", event.data.tur); // B: cikis
sekmeC.onmessage = (event) => {
console.log("C:", event.data.tur); // C: cikis
setTimeout(() => [sekmeA, sekmeB, sekmeC].forEach((k) => k.close()), 50);
};
console.log(sekmeA.name); // oturum
sekmeA.postMessage({ tur: "cikis" });API mesajlara herhangi bir anlam yüklemez; hangi tür mesajın geleceğini ve ne yapılacağını sizin kodunuz bilir. Bu yüzden { tur: "cikis" } gibi bir alanla mesaj protokolünüzü kendiniz tanımlamalısınız. MDN ayrıca daha kesin bir kuralı not eder: iletişim, aynı depolama bölümünü (storage partition) kullanan bağlamlar arasında mümkündür. Depolama önce üst düzey siteye göre bölümlendiği için a.com sayfasına gömülü b.com iframe'i, ayrı bir sekmede açık b.com sayfasıyla teknik olarak aynı origin'de olsa bile konuşamaz.
Paylaşılan bellek ve çapraz origin izolasyonu
postMessage() çağrısı SharedArrayBuffer ile hata fırlatıyorsa sitenizin çapraz origin izolasyonu eksik olabilir. MDN'e göre paylaşılan bellek iki HTTP başlığına bağlıdır: Cross-Origin-Opener-Policy: same-origin ve Cross-Origin-Embedder-Policy: require-corp (ya da credentialless). İzolasyonun başarılı olup olmadığı pencere ve worker bağlamlarında bulunan crossOriginIsolated özelliğiyle kontrol edilir.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpconst myWorker = new Worker("worker.js");
if (crossOriginIsolated) {
const buffer = new SharedArrayBuffer(16);
myWorker.postMessage(buffer);
} else {
const buffer = new ArrayBuffer(16);
myWorker.postMessage(buffer);
}Hangi yöntemi seçmeli?

| Yöntem | Kimler arasında | Origin | Karşı tarafa başvuru |
|---|---|---|---|
| window.postMessage | Pencere, açılır pencere, iframe | Farklı origin olabilir | Gerekir (contentWindow, opener, open) |
| MessageChannel | İki bağlam arasında özel hat | Port aktarıldıktan sonra fark etmez | İlk aktarım için gerekir |
| BroadcastChannel | Aynı origin'deki tüm sekme, iframe ve worker | Aynı origin ve depolama bölümü | Gerekmez, kanal adı yeterli |
| Worker postMessage | Sayfa ve onun worker'ı | Worker betiği aynı origin | Worker nesnesi |
Sık yapılan hatalar
messagedinleyicisindeevent.originkontrolü yapmamak. MDN, bunun siteler arası betik çalıştırma saldırılarına kapı açtığını vurgular.- Origin'i
includes()ya da önek karşılaştırmasıyla kontrol etmek. Tam eşitlik ya da izinli origin kümesi kullanın. - Hassas veriyi
targetOriginolarak"*"vererek göndermek. - iframe yüklenmeden
contentWindow.postMessage()çağırmak.loadolayını bekleyin ya da iframe'in "hazırım" mesajını bekleyin. - Gelen veriyi doğrulamadan
innerHTMLile sayfaya basmak. - Aktarılan
MessagePortya daArrayBuffernesnesini gönderdikten sonra kullanmaya devam etmek. addEventListener()ile dinlenen porttastart()çağırmayı unutmak.- BroadcastChannel ile gönderen sekmenin de mesajı alacağını varsaymak. Gönderen nesne kendi mesajını almaz.
Sık Sorulan Sorular
postMessage ile fonksiyon gönderebilir miyim?
Hayır. Mesaj structured clone ile kopyalanır ve fonksiyonlar bu algoritmayla kopyalanamaz; DataCloneError alınır. Bunun yerine hangi işlemin yapılacağını belirten bir alan gönderin.
event.origin ile event.source arasındaki fark nedir?
origin, gönderen pencerenin mesaj anındaki origin'ini dize olarak verir ve kimlik doğrulamada kullanılır. source ise gönderen pencerenin kendisine başvurudur ve yanıt göndermek için kullanılır. MDN, doğrulamada gerektiğinde ikisinin birlikte kontrol edilmesini önerir.
Farklı alan adlarındaki iki sekme BroadcastChannel ile konuşabilir mi?
Hayır. BroadcastChannel yalnızca aynı origin ve aynı depolama bölümündeki bağlamlar arasında çalışır. Farklı origin'ler için ortak bir pencere ilişkisi (açılır pencere ya da iframe) ve window.postMessage() gerekir.
Mesaj ulaştı mı, nasıl anlarım?
postMessage() undefined döndürür ve teslimi ya da alıcıdaki hataları bildirmez. Onay gerekiyorsa alıcı taraf event.source.postMessage() ya da port üzerinden bir yanıt mesajı göndermelidir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · Web Workers ve structured clone · CORS ve aynı origin · localStorage ve storage olayı · XSS ve CSP koruma · Clickjacking ve frame-ancestors