innerHTML bir öğenin içeriğini HTML işaretlemesi olarak okur ve yazar; atadığınız dize HTML olarak ayrıştırılır. textContent düğümün ve torunlarının metnini olduğu gibi verir ve yazarken dizeyi düz metin olarak ekler. innerText ise metnin ekranda nasıl göründüğünü dikkate alır: gizli öğeleri atlar ve CSS'e göre hesaplanır. Kısacası HTML eklemek istiyorsanız innerHTML, kullanıcıdan gelen veya düz metin yazıyorsanız textContent kullanılır.
Bu yazıda bu üç özelliği, outerHTML ve outerText ile birlikte, insertAdjacentHTML() ailesini ve güvenlik açısından önerilen setHTML() metodunu MDN belgelerine göre karşılaştırıyoruz. XSS saldırılarının ayrıntılarını XSS ve CSP koruma yazısında ele aldık; burada yalnızca DOM API tarafına odaklanıyoruz. Serinin tamamı için Web API öğrenme rehberi sayfasına bakabilirsiniz.
Kısa karşılaştırma

| Özellik veya metot | Okurken ne döner | Yazarken ne yapar | HTML ayrıştırır mı |
|---|---|---|---|
| innerHTML | Torunların HTML serileştirmesi | Tüm içeriği ayrıştırılan HTML ile değiştirir | Evet |
| outerHTML | Öğenin kendisi dahil HTML | Öğeyi ayrıştırılan HTML ile değiştirir | Evet |
| textContent | Tüm metin torunlarının birleşimi | Tüm çocukları tek bir metin düğümüyle değiştirir | Hayır |
| innerText | Ekranda görünen metin | İçeriği metinle değiştirir, satır sonlarını br yapar | Hayır |
| outerText | innerText ile aynı | Öğenin kendisini metinle değiştirir | Hayır |
| insertAdjacentHTML() | Okuma yok | Belirtilen konuma HTML ekler | Evet |
| insertAdjacentText() | Okuma yok | Belirtilen konuma metin düğümü ekler | Hayır |
innerHTML: HTML olarak okumak ve yazmak
innerHTML okunduğunda tarayıcı öğenin torunlarını HTML dizesine serileştirir. Değer, kaynaktaki boşlukları da içerir. MDN'in örneği:
<div id="example">
<p>My name is Joe</p>
</div>const myElement = document.querySelector("#example");
const contents = myElement.innerHTML;
console.log(contents); // "\n <p>My name is Joe</p>\n"Yazıldığında ise verilen değer HTML olarak ayrıştırılır ve öğenin tüm torunları sonuçla değiştirilir. Önceki çocuk düğümler ve onlara eklenmiş olay dinleyicileri kaybolur. MDN birkaç ayrıntıyı belirtir:
nullatamak boş dize atamakla aynıdır:elt.innerHTML = nullileelt.innerHTML = ""eşdeğerdir.- Okunan serileştirme shadow root'ları içermez. Shadow root'larla birlikte HTML almak için
Element.getHTML()kullanılır. Yazarken de bildirimsel shadow root'lar oluşmaz;shadowrootmodeiçeren bir<template>düz bir şablon öğesi olarak ayrıştırılır. Bunun içinsetHTMLUnsafe()gerekir. - Ebeveyni
Documentolan bir düğüme HTML eklemeye çalışmakNoModificationAllowedErrorfırlatır. - Trusted Types bir CSP ile zorunlu tutuluyorsa ve varsayılan bir politika yoksa,
innerHTML'e düz dize atamakTypeErrorverir. - Bazı tarayıcılar, öznitelik değerlerindeki
<ve>karakterlerini serileştirirken<ve>olarak yazar; MDN bunun mutation XSS denen bir açığı önlemeye yönelik olduğunu açıklar.
innerHTML neden bir güvenlik riski?
MDN, innerHTML'i XSS saldırıları için muhtemelen en yaygın kanal olarak tanımlar ve bu tür API'lere enjeksiyon noktası (injection sink) adını verir. Kullanıcıdan gelen bir dize temizlenmeden innerHTML'e verilirse, saldırgan sayfanızda JavaScript çalıştırabilir. Özellik <script> öğelerinin çalışmasını engeller; ancak MDN bunun yeterli olmadığını, başka pek çok yolla zararlı kod çalıştırılabileceğini gösterir:
const name = "<img src='x' onerror='alert(1)'>";
el.innerHTML = name; // shows the alertBurada src değeri geçerli bir görsel adresi olmadığı için error olayı tetiklenir ve onerror özniteliğindeki kod çalışır. Aynı risk outerHTML, insertAdjacentHTML() ve setHTMLUnsafe() için de geçerlidir. MDN'in önerdiği çözümler şunlardır:
- İçeriğin düz metin olması gerektiğini biliyorsanız
textContentveyainsertAdjacentText()kullanın. Metin HTML olarak ayrıştırılmaz. - HTML eklemeniz gerekiyorsa ve tarayıcı destekliyorsa
setHTML()kullanın; içeriği temizleyerek ekler. - Dize yerine
TrustedHTMLnesneleri atayın verequire-trusted-types-forCSP yönergesiyle Trusted Types kullanımını zorunlu kılın. Böylece her dize, onu temizleme şansı olan bir dönüştürme fonksiyonundan geçer. MDN örneklerinde bu fonksiyon genellikle DOMPurify gibi bir kütüphaneyle yazılır.
MDN'in getAttribute() sayfası bu konuya ilginç bir uyarı ekler: kaynak işaretlemedeki < gibi karakter referansları ayrıştırma sırasında çözülür. Bir öznitelikte kaçırılmış görünen bir değeri okuyup innerHTML'e verirseniz, kaçışlar artık çözülmüş olur ve sonuç XSS için kullanılabilir. Güvenilmeyen veriyi her zaman metin güvenli bir API ile yazın.
outerHTML: öğenin kendisiyle birlikte
outerHTML, öğenin kendisini ve torunlarını içeren HTML'i verir. Atama yapıldığında öğenin tamamı ayrıştırılan HTML ile ebeveynin içinde değiştirilir. MDN iki şaşırtıcı davranışı örnekler:
const div = document.createElement("div");
div.outerHTML = '<div class="test">test</div>';
console.log(div.outerHTML); // output: "<div></div>"Öğenin ebeveyni yoksa, yani belgeye bağlı değilse, outerHTML ataması hiçbir şeyi değiştirmez. İkinci davranış şudur: öğe belgede değiştirilse bile atama yaptığınız değişken hâlâ eski öğeyi gösterir.
const p = document.querySelector("p");
console.log(p.nodeName); // shows: "P"
p.outerHTML = "<div>This div replaced a paragraph.</div>";
console.log(p.nodeName); // still "P";Yani atamadan sonra yeni <div>'e ulaşmak için onu yeniden seçmeniz gerekir. outerHTML okunduğunda bazı öznitelik değerleri kaçırılır; MDN'in örneğinde bir bağlantının href değerindeki & karakteri & olarak serileştirilir. Doğrudan Document'in çocuğu olan bir öğede, örneğin document.documentElement üzerinde atama yapmak NoModificationAllowedError fırlatır.
textContent: düz metin, hızlı ve güvenli

textContent, Node arayüzünde tanımlıdır ve düğümün ve torunlarının metin içeriğini temsil eder. Döndürdüğü değer düğüm türüne göre değişir:
documentveya doctype düğümündenulldöner. Belgenin tüm metni içindocument.documentElement.textContentkullanılır.- Metin, yorum, CDATA ve işleme talimatı düğümlerinde düğümün kendi metnini, yani
nodeValuedeğerini verir. - Diğer düğümlerde, yorumlar ve işleme talimatları hariç tüm çocuk düğümlerin
textContentdeğerlerinin birleşimini verir. Çocuk yoksa sonuç boş dizedir.
<div id="divA">This is <span>some</span> text!</div>let text = document.getElementById("divA").textContent;
// The text variable is now: 'This is some text!'
document.getElementById("divA").textContent = "This text is different!";
// The HTML for divA is now:
// <div id="divA">This text is different!</div>MDN, metin okumak veya yazmak için innerHTML kullanmamanızı önerir: ham HTML ile çalıştığı için XSS'e açıktır ve metnin HTML sözdizimi içermediğinden emin olsanız bile HTML ayrıştırıcısını çağırdığı için anlamsal olarak daha zayıf ve daha yavaştır. Kullanıcı adı, yorum, arama terimi gibi her türlü dış veriyi sayfaya yazmanın varsayılan yolu textContent olmalıdır.
innerText: ekranda görünen metin
innerText, HTMLElement arayüzünde tanımlıdır ve düğümün ekranda çizilen (rendered) metin içeriğini temsil eder. MDN'e göre okunduğunda, kullanıcının öğenin içeriğini fareyle seçip panoya kopyaladığında alacağı metne yakın bir sonuç verir. textContent ile farkları şöyle özetlenir:
textContent,<script>ve<style>dahil tüm öğelerin metnini verir.innerTextyalnızca insanın okuyabileceği öğeleri gösterir.textContentgizli öğelerin metnini de döndürür;innerTextstilleri dikkate aldığı için gizli öğelerin metnini atlar ve<br>gibi öğelerin etkisini satır sonu olarak yansıtır.innerTextCSS'i hesaba kattığı için okunduğunda güncel hesaplanmış stilleri elde etmek üzere bir yeniden akış (reflow) tetikler. MDN, yeniden akışın hesaplama açısından pahalı olabileceğini ve mümkün olduğunda kaçınılması gerektiğini belirtir.- Öğe çizilmiyorsa, örneğin belgeden ayrılmışsa veya görünümden gizlenmişse,
innerTexttextContentile aynı değeri döndürür.
Yazıldığında innerText da öğenin tüm çocuklarını kaldırır; ek olarak dizedeki satır sonlarını <br> öğelerine dönüştürür. outerText okunduğunda innerText ile aynı değeri verir, ancak yazıldığında öğenin içini değil, öğenin kendisini verilen metinle değiştirir. MDN'in örneğinde bir <div> içindeki <p> için p.outerText = "Whole element replaced" paragrafın tamamını kaldırıp yerine metni koyar; innerText ise paragrafı korur ve yalnızca içini değiştirir.
insertAdjacentHTML, insertAdjacentText ve insertAdjacentElement

innerHTML içeriğin tamamını değiştirir. İçeriği silmeden belirli bir konuma ekleme yapmak için insertAdjacent ailesi kullanılır. Üç metot da aynı dört konum adını kabul eder:
<!-- beforebegin -->
<p>
<!-- afterbegin -->
foo
<!-- beforeend -->
</p>
<!-- afterend -->"beforebegin": öğenin kendisinden önce. Yalnızca öğe ağaçtaysa ve bir ebeveyn öğesi varsa geçerlidir."afterbegin": öğenin içinde, ilk çocuğundan önce."beforeend": öğenin içinde, son çocuğundan sonra."afterend": öğenin kendisinden sonra. Yine ağaçta olmayı ve bir ebeveyn öğesini gerektirir.
insertAdjacentHTML(position, input) girdiyi HTML olarak ayrıştırır ve sonuç düğümleri verilen konuma ekler. MDN'e göre metot üzerinde kullanıldığı öğeyi yeniden ayrıştırmaz, bu yüzden öğenin içindeki mevcut öğeleri bozmaz; ek serileştirme adımı olmadığı için doğrudan innerHTML ile içerik değiştirmekten çok daha hızlıdır. Ancak <script> veya olay işleyici öznitelikleri gibi güvenli olmayan içeriği temizlemez. Geçersiz bir konum adı veya iyi biçimlenmemiş XML SyntaxError, ebeveyni olmayan bir öğede beforebegin/afterend kullanmak NoModificationAllowedError verir. <template> öğeleri için MDN, metodu doğrudan şablona değil, şablonun content özelliğine uygulamayı önerir.
insertAdjacentText(where, data) aynı konumlara düz metin düğümü ekler; geçersiz konumda SyntaxError fırlatır. MDN, var olan metin düğümüne eklenmediğini, her çağrıda yeni bir metin düğümü oluşturulduğunu belirtir. insertAdjacentElement(position, element) ise hazır bir öğe düğümünü ekler ve eklenen öğeyi, başarısız olursa null döndürür. Öğe oluşturmanın diğer yolları için DOM öğe oluşturma ve ekleme yazısına bakabilirsiniz.
<input id="yorum" type="text" />
<p id="yorumlar">Yorumlar: </p>const liste = document.querySelector("#yorumlar");
const yeniYorum = document.querySelector("#yorum").value; // kullanıcı girdisi
// Güvenli: metin olarak eklenir, HTML ayrıştırılmaz
liste.insertAdjacentText("beforeend", yeniYorum);setHTML(): temizleyerek HTML eklemek
HTML Sanitizer API'nin bir parçası olan element.setHTML(input, options), bir HTML dizesini ayrıştırıp temizler ve öğenin alt ağacı olarak ekler. MDN'e göre metot, verilen temizleyici (sanitizer) izin verse bile XSS açısından güvensiz öğe ve öznitelikleri her zaman kaldırır: <script>, <frame>, <iframe>, <embed>, <object>, SVG use öğesi ve olay işleyici öznitelikleri bunlardandır. Destekleyen tarayıcılarda, kullanıcıdan gelen HTML'i eklerken innerHTML'in yerine kullanılması önerilir. Bu metot için tarayıcı desteğini MDN uyumluluk tablosundan kontrol edin.
const unsanitizedString = "abc <script>alert(1)<" + "/script> def";
const target = document.getElementById("target");
// Varsayılan temizleyici yapılandırmasıyla
target.setHTML(unsanitizedString);MDN önemli bir tuzağa dikkat çeker: setHTML() ile temizlenen içeriği innerHTML ile okuyup başka bir öğeye yine innerHTML ile yazmak güvenli değildir. Temizleme bağlama göre yapılır; bir bağlamda güvenli olan işaretleme başka bir öğede güvensiz hale gelebilir. Kural basittir: HTML dizelerini yalnızca setHTML() gibi güvenli metotlarla ekleyin. setHTMLUnsafe() ise varsayılan olarak hiçbir temizleme yapmaz; MDN, bu yüzden <script> çalışmasını engelleyen innerHTML'den bile daha az güvenli olabileceğini belirtir ve neredeyse her durumda setHTML()'i önerir.
Mini örnek: arama sonucunu güvenle göstermek
Kullanıcının yazdığı terimi bir başlıkta vurgulu göstermek isteyelim. Yapıyı öğelerle kurup kullanıcı verisini yalnızca metin olarak yazmak, innerHTML ile dize birleştirmekten hem daha güvenli hem de daha okunurdur:
<p id="sonuc"></p>function sonucuGoster(terim, adet) {
const sonuc = document.getElementById("sonuc");
const vurgu = document.createElement("strong");
vurgu.textContent = terim; // HTML olarak yorumlanmaz
sonuc.replaceChildren(`${adet} sonuç bulundu: `, vurgu);
}
sonucuGoster("<img src=x onerror=alert(1)>", 0);Son satırdaki zararlı görünen terim sayfada olduğu gibi, açılı ayraçlarıyla birlikte metin olarak görünür; tarayıcı onu bir <img> öğesine çevirmez. Aynı işi sonuc.innerHTML = `${adet} sonuç bulundu: <strong>${terim}</strong>` ile yapsaydık, yukarıda MDN'in gösterdiği onerror saldırısı çalışırdı.
Sık yapılan hatalar
- Kullanıcı verisini innerHTML ile yazmak: Yorum, ad, arama terimi gibi dış verileri
textContentile yazın. HTML gerekiyorsa temizleme yapın. - innerHTML += ile ekleme yapmak: Bu kalıp öğenin içeriğini okuyup serileştirir, ardından birleştirilmiş dizeyi yeniden ayrıştırarak tüm torunları yenileriyle değiştirir; eski düğümlere
addEventListener()ile eklenmiş dinleyiciler yeni düğümlere geçmez. Sona eklemek içininsertAdjacentHTML("beforeend", ...)veyaappend()kullanın. - textContent ile innerText'i aynı sanmak: Biri tüm metni, diğeri görünen metni verir; gizli öğeler ve
<style>içeriği iki sonucu farklılaştırır. - outerHTML sonrası eski değişkeni kullanmak: Değişken hâlâ kaldırılmış öğeyi gösterir; yeni öğeyi tekrar seçin.
- Belgeye bağlı olmayan öğede outerHTML veya beforebegin kullanmak: Ebeveyn olmadığında atama etkisizdir veya hata alırsınız.
- Temizlenmiş HTML'i yeniden innerHTML ile taşımak: MDN'in uyardığı mutation XSS riskine yol açar.
Sık Sorulan Sorular
innerHTML ile textContent arasındaki temel fark nedir?
innerHTML dizeyi HTML olarak ayrıştırır ve etiketleri öğeye dönüştürür; textContent ise dizeyi düz metin olarak ekler, etiketler metin olarak görünür. Okurken de innerHTML işaretlemeyi, textContent yalnızca metni verir.
innerHTML ile eklenen script çalışır mı?
MDN'e göre innerHTML, enjekte edilen <script> öğelerinin çalışmasını engeller. Ancak bu sizi korumaz: onerror gibi olay işleyici öznitelikleri üzerinden kod çalıştırmak yine mümkündür. Bu yüzden güvenilmeyen içerik için innerHTML kullanılmamalıdır.
Bir öğenin içini tamamen boşaltmanın en iyi yolu hangisi?
element.replaceChildren() argümansız çağrıldığında tüm çocukları kaldırır. element.textContent = "" de içeriği boşaltır. innerHTML = "" de çalışır, ancak HTML ayrıştırıcısını gereksiz yere devreye sokar.
innerText neden yavaş olabilir?
Çünkü stilleri dikkate alır ve MDN'e göre okunduğunda güncel hesaplanmış stilleri elde etmek için yeniden akış (reflow) tetikler. Görünüm önemli değilse textContent tercih edin. Performans ölçümünü Performance API yazısında anlattık.
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 · XSS ve CSP koruma · DOM öğe oluşturma · classList ve setAttribute · DOM nedir