Kısa cevap: Bir formu JavaScript ile göndermek için form öğesinin submit olayını dinler, event.preventDefault() ile tarayıcının sayfayı yenileyen varsayılan gönderimini durdurur, new FormData(form) ile alanların değerlerini toplar ve bu nesneyi fetch() çağrısına gövde olarak verirsiniz. submit olayı düğmede değil formun kendisinde tetiklenir; hangi düğmeye basıldığını event.submitter söyler. Formu koddan göndermeniz gerekirse doğrulamayı ve submit olayını atlayan form.submit() yerine, gerçek bir düğme tıklaması gibi davranan form.requestSubmit() kullanın.

Bu yazıda submit olayının hangi durumlarda gelip hangi durumlarda gelmediğini, FormData nesnesinin yöntemlerini ve tuzaklarını, formdata olayını, form.elements koleksiyonunu ve dosya gönderirken Content-Type başlığına neden dokunmamanız gerektiğini MDN belgelerine göre anlatıyoruz. FormData tarayıcı ile Node.js'te aynı standart üzerinden bulunduğu için formdan bağımsız FormData örneklerini Node.js 24.4.0 ile çalıştırıp çıktıları doğruladık; form öğesi kullanan örnekler MDN'ye göre yazıldı. Form etiketleri ve doğrulama öznitelikleri HTML tarafıdır, HTML öğrenme rehberinde işlenir. Serinin tamamı için Web API öğrenme rehberi sayfasına bakın.

submit olayı ne zaman gelir?

MDN'ye göre submit olayı form gönderildiğinde form öğesinin kendisinde tetiklenir, içindeki button ya da input type="submit" üzerinde değil. Olay nesnesi SubmitEvent türündedir ve submitter özelliği gönderimi başlatan düğmeyi verir. Olay üç durumda gelir: Kullanıcı bir gönder düğmesine tıkladığında, form örtük olarak gönderildiğinde ve bir betik form.requestSubmit() çağırdığında. Betik form.submit() çağırdığında ise olay gönderilmez. Doğrulamayı geçemeyen bir form gönderilmeye çalışıldığında invalid olayı tetiklenir, doğrulama gönderimi engellediği için submit olayı da gelmez.

Örtük gönderim genellikle bir metin alanındayken Enter tuşuna basmaktır. MDN tarayıcının tepkisini şöyle özetler: Formda devre dışı olmayan bir varsayılan gönder düğmesi varsa tarayıcı o düğmeye bir click olayı gönderir; varsayılan düğme, ağaç sırasında formun ilk gönder düğmesidir. Formda hiç gönder düğmesi yoksa, örtük gönderim yalnızca formda metin, arama, e-posta, şifre, sayı ve tarih gibi türlerden en fazla bir input varsa gerçekleşir. Varsayılan düğme devre dışıysa ya da düğme yokken bu türden birden fazla alan varsa Enter formu göndermez. Bu kural, "Enter'a basınca form neden gönderilmiyor" sorusunun en sık cevabıdır.

html
<form id="iletisim">
  <label>Ad <input type="text" name="ad" required /></label>
  <label>E-posta <input type="email" name="eposta" required /></label>
  <button type="submit" name="niyet" value="gonder">Gönder</button>
  <button type="submit" name="niyet" value="taslak">Taslak kaydet</button>
</form>
<p id="durum"></p>
javascript
const form = document.getElementById("iletisim");
const durum = document.getElementById("durum");

form.addEventListener("submit", (event) => {
  event.preventDefault(); // sayfanın yenilenmesini engeller
  const dugme = event.submitter; // tıklanan düğme veya null
  durum.textContent = `Seçilen işlem: ${dugme ? dugme.value : "bilinmiyor"}`;
});

submitter değeri gönderim bir düğmeyle tetiklenmediyse null olur. Birden fazla gönder düğmesi olan formlarda, örneğin "Gönder" ve "Taslak kaydet" gibi, hangi işlemin istendiğini bu özellikle ayırt edersiniz. MDN'nin örneği aynı fikri farklı ödeme yöntemlerine karşılık gelen düğmelerle gösterir.

FormData nesnesi

FormData, form alanlarını ve değerlerini temsil eden anahtar değer çiftleri kümesidir. MDN, bu nesnenin fetch(), XMLHttpRequest.send() ve navigator.sendBeacon() ile gönderilebildiğini ve kodlama türü multipart/form-data olan bir formun kullanacağı biçimi kullandığını belirtir. Yapıcı üç şekilde çağrılır: boş olarak, bir form öğesiyle ya da bir form öğesi ve bir gönder düğmesiyle.

javascript
new FormData()
new FormData(form)
new FormData(form, submitter)

Form verildiğinde nesne formun o anki değerleriyle doldurulur: Anahtarlar alanların name özelliğinden, değerler gönderilecek değerlerinden gelir, dosya alanlarının içeriği de kodlanır. MDN'nin notu kritiktir: Yalnızca başarılı form kontrolleri, yani name özniteliği olan ve devre dışı olmayan alanlar dahil edilir. Devre dışı bir fieldset içindeki alanlar da eklenmez. submitter parametresi verilirse ve düğmenin name özniteliği varsa düğmenin değeri de eklenir. Düğme gönder düğmesi değilse TypeError, formun üyesi değilse NotFoundError fırlatılır.

javascript
const form = document.getElementById("iletisim");

form.addEventListener("submit", (event) => {
  event.preventDefault();
  // Tıklanan düğmenin name ve value değeri de dahil edilir
  const veri = new FormData(form, event.submitter);
  for (const [anahtar, deger] of veri) {
    console.log(`${anahtar}: ${deger}`);
  }
});

append, set, get ve getAll

append, set, get ve getAll: append(ad, deger, dosyaAdi), set(ad, deger, dosyaAdi), get(ad), getAll(ad)

FormData bir Map gibi görünse de önemli bir farkı vardır: Anahtarlar benzersiz olmak zorunda değildir, çünkü bir formda aynı adı taşıyan birden fazla alan bulunabilir (örneğin aynı adlı onay kutuları). append() mevcut anahtara yeni bir değer ekler, set() ise o anahtarın bütün değerlerini silip tek değerle değiştirir. get() ilk değeri ya da anahtar yoksa null, getAll() tüm değerleri bir dizi olarak ya da boş dizi döndürür. Değer dize ya da Blob değilse dizeye çevrilir. Aşağıdaki örnek Node.js 24.4.0 ile çalıştırıldı.

javascript
const formData = new FormData();
formData.append("etiket", "js");
formData.append("etiket", "dom");
formData.append("adet", 3);
console.log(formData.get("etiket")); // js
console.log(JSON.stringify(formData.getAll("etiket"))); // ["js","dom"]
console.log(typeof formData.get("adet")); // string

formData.set("etiket", "css");
console.log(JSON.stringify(formData.getAll("etiket"))); // ["css"]

console.log(formData.has("eposta")); // false
console.log(formData.get("eposta")); // null
console.log(JSON.stringify(formData.getAll("eposta"))); // []

formData.delete("adet");
for (const [anahtar, deger] of formData) {
  console.log(anahtar, deger); // etiket css
}
YöntemDöndürdüğüNot
append(ad, deger, dosyaAdi)undefinedVar olan değerlerin sonuna ekler
set(ad, deger, dosyaAdi)undefinedVar olan tüm değerlerin yerine geçer
get(ad)ilk değer veya nullBirden çok değer varsa yalnızca ilki
getAll(ad)diziAnahtar yoksa boş dizi
has(ad)booleanAnahtarın varlığı
delete(ad)undefinedAnahtarı tüm değerleriyle siler
entries(), keys(), values()yineleyicifor...of ile doğrudan da gezilebilir

FormData doğrudan for...of ile gezilebilir; MDN for (const p of myFormData) yazımının entries() ile aynı olduğunu belirtir. Nesneyi URLSearchParams yapıcısına da verebilirsiniz; böylece bir formun basit GET gönderiminde üreteceği sorgu dizesine benzer bir dize elde edersiniz. Bunun için dosya içermeyen bir veri kullanın.

javascript
const fd = new FormData();
fd.append("q", "web api");
fd.append("sayfa", "2");
console.log(new URLSearchParams(fd).toString()); // q=web+api&sayfa=2

Dosya ve Blob eklemek

append() ve set() üçüncü parametre olarak sunucuya bildirilecek dosya adını alır. Bu parametre yalnızca değer bir Blob ya da File olduğunda anlamlıdır. Varsayılan dosya adı Blob için "blob", File için dosyanın kendi adıdır. Bir dosya girişinden gelen dosyayı input.files[0] ile ekleyebilirsiniz; dosya okuma ve Blob ayrıntıları File API ve Blob yazısında anlatılıyor.

javascript
const fd = new FormData();
const blob = new Blob(["merhaba"], { type: "text/plain" });
fd.append("ek", blob);
fd.append("rapor", blob, "rapor.txt");
console.log(fd.get("ek").name); // blob
console.log(fd.get("rapor").name); // rapor.txt
console.log(fd.get("rapor") instanceof File); // true

fetch ile gönderim ve Content-Type tuzağı

FormData nesnesini fetch() çağrısında body olarak verdiğinizde tarayıcı gövdeyi multipart/form-data biçiminde kodlar ve Content-Type başlığını alanları ayıran sınır (boundary) ifadesiyle birlikte kendisi kurar. MDN burada açık bir uyarı yapar: Dosya ve blob yüklerken bu başlığı elle ayarlamayın; ayarlarsanız tarayıcı sınır ifadesini başlığa koyamaz ve sunucu gövdeyi ayrıştıramaz. Bu davranışı yerel bir Response nesnesiyle, ağa çıkmadan görebilirsiniz; aşağıdaki satır Node.js 24.4.0 ile çalıştırıldı.

javascript
const yanit = new Response(fd);
console.log(yanit.headers.get("Content-Type").startsWith("multipart/form-data; boundary=")); // true

Sunucunuz JSON bekliyorsa FormData yerine Object.fromEntries(formData) ile düz bir nesne oluşturup JSON.stringify() ile göndermek ve Content-Type: application/json başlığını ayarlamak gerekir. Bu yöntemde aynı adlı birden fazla değer varsa yalnızca sonuncusu kalır (Node.js ile denendi) ve dosyalar taşınamaz; bu yüzden çok değerli alanlar için getAll() kullanın. JSON tarafı için JSON.parse ve stringify yazısına, istek gövdeleri ve hata yakalama için Fetch API yazısına bakın.

requestSubmit ile submit farkı

requestSubmit ile submit farkı: submit olayı, Kısıt doğrulaması, Gönder düğmesinin değeri, preventDefault ile durdurma

form.submit() formu gönderir ama yalnızca bunu yapar: submit olayı tetiklenmez, formun onsubmit işleyicisi çalışmaz ve kısıt doğrulaması devreye girmez. Ayrıca input type="submit" değeri bu yolla gönderilen veriye eklenmez. form.requestSubmit() ise bir gönder düğmesine tıklanmış gibi davranır: Form içeriği doğrulanır, yalnızca doğrulama başarılıysa gönderilir ve submit olayı forma gönderilir. İsteğe bağlı submitter parametresi formun üyesi olan bir gönder düğmesi olmalıdır; düğmenin formmethod gibi form* öznitelikleri formun davranışını geçersiz kılar.

Davranışform.submit()form.requestSubmit()
submit olayıTetiklenmezTetiklenir
Kısıt doğrulamasıÇalışmazÇalışır, geçersizse gönderilmez
Gönder düğmesinin değeriEklenmezsubmitter verilirse eklenir
preventDefault ile durdurmaMümkün değilsubmit dinleyicisinde mümkün

formdata olayı

formdata olayı, formun verisini temsil eden giriş listesi oluşturulduktan sonra form üzerinde tetiklenir. Bu, form gönderildiğinde olur ama new FormData(form) çağrısıyla da tetiklenebilir. Olay iptal edilemez ve kabarcıklanmaz. Dinleyici event.formData üzerinden nesneye erişip gönderilmeden önce değerleri değiştirebilir. MDN örneğinde iki alanın değeri bu olayda küçük harfe çevrilir; sonuçta FormData nesnesi değiştirilmiş değerleri taşırken formdaki alanlar olduğu gibi kalır.

javascript
const formElem = document.querySelector("form");

formElem.addEventListener("formdata", (e) => {
  const veri = e.formData;
  veri.set("eposta", veri.get("eposta").trim().toLowerCase());
  veri.append("kaynak", "iletisim-sayfasi"); // kullanıcının görmediği ek alan
});

form.elements ile alanlara erişim

form.elements forma bağlı tüm liste form kontrollerini içeren bir HTMLFormControlsCollection döndürür. Bir kontrole sırasıyla, name ya da id değeriyle erişebilirsiniz. Koleksiyon canlıdır; forma kontrol eklenip çıkarıldıkça güncellenir ve öğeler belgedeki ağaç sırasındadır. Dahil edilen öğeler button, fieldset, input, object, output, select, textarea ve forma bağlı özel öğelerdir; tarihsel nedenlerle type="image" olan input öğeleri dışarıda bırakılır. MDN örneği önemli bir ayrıntıyı gösterir: Koleksiyon formun içinde fiziksel olarak duran öğeleri değil, form özniteliğiyle o forma bağlanmış öğeleri listeler.

javascript
const form = document.getElementById("iletisim");
const alanlar = form.elements;
const adAlani = alanlar["ad"]; // name ile erişim
const ilkAlan = alanlar[0]; // sıra ile erişim

// Gönderim sırasında tüm kontrolleri kilitlemek
function kilitle(kilitli) {
  for (const kontrol of form.elements) {
    kontrol.disabled = kilitli;
  }
}

Burada dikkat: FormData devre dışı alanları içermediği için kilitle(true) çağrısını veriyi topladıktan sonra yapmalısınız; aksi halde boş bir nesne gönderirsiniz.

Uygulamalı mini örnek: sayfa yenilemeden form gönderimi

Aşağıdaki kod yukarıdaki parçaları birleştirir: submit olayını durdurur, düğme bilgisiyle birlikte veriyi toplar, alanları kilitler, fetch() ile gönderir ve sonucu gösterir. Kod tarayıcıya özgüdür ve çalıştırılmadı; /iletisim adresi kendi sunucunuzdaki bir uç noktayı temsil eder.

javascript
const form = document.getElementById("iletisim");
const durum = document.getElementById("durum");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const veri = new FormData(form, event.submitter); // önce veriyi topla
  kilitle(true); // sonra kilitle
  durum.textContent = "Gönderiliyor...";
  try {
    const yanit = await fetch("/iletisim", {
      method: "POST",
      body: veri, // Content-Type başlığını elle ayarlamayın
    });
    if (!yanit.ok) {
      throw new Error(`HTTP ${yanit.status}`);
    }
    durum.textContent = "Mesajınız alındı.";
    form.reset(); // alanları varsayılan değerlerine döndürür
  } catch (hata) {
    durum.textContent = `Gönderilemedi: ${hata.message}`;
  } finally {
    kilitle(false);
  }
});

form.reset() formu varsayılan değerlerine döndürür ve bir reset olayı tetikler. MDN, bir değeri setAttribute() ile ayarladıysanız reset() çağrısının onu eski varsayılana değil, setAttribute() ile verilen değere döndüreceğini not eder. Yanıt durum kodlarının anlamı için HTTP durum kodları yazısına, formların başka sitelerden kötüye kullanılmasına karşı korunma için CSRF yazısına bakın.

Sık yapılan hatalar

Sık yapılan hatalar: Dinleyiciyi düğmenin click olayına koymak, preventDefault çağırmamak, Alanlara name vermemek, Alanları veri toplamadan önce devre dışı bırakmak
  • Dinleyiciyi düğmenin click olayına koymak: Enter ile örtük gönderim ve requestSubmit() kaçar; formun submit olayını dinleyin.
  • preventDefault çağırmamak: Tarayıcı formu klasik yolla gönderir ve sayfa yenilenir, fetch() isteği yarıda kalabilir.
  • Alanlara name vermemek: FormData yalnızca name özniteliği olan alanları alır.
  • Alanları veri toplamadan önce devre dışı bırakmak: Devre dışı alanlar FormData içine girmez.
  • multipart gönderimde Content-Type ayarlamak: Sınır ifadesi kaybolur, sunucu gövdeyi okuyamaz.
  • Çok değerli alanda get kullanmak: Yalnızca ilk değer gelir; getAll() kullanın.
  • Koddan göndermek için submit() kullanmak: Doğrulama ve submit dinleyicileri atlanır; requestSubmit() tercih edin.

Sık Sorulan Sorular

FormData içeriğini nasıl görürüm?

for (const [anahtar, deger] of formData) döngüsüyle ya da entries(), keys(), values() yineleyicileriyle gezebilirsiniz. Değerler dize ya da Blob nesneleridir.

FormData neden boş ya da eksik geliyor?

En sık iki neden vardır: Alanın name özniteliği yoktur ya da alan veya içinde bulunduğu fieldset devre dışıdır. MDN örneğindeki onay kutusu işaretli olduğu hâlde disabled olduğu için FormData içine girmez. Alanın başka bir formun form özniteliğine bağlı olması da onu bu formun verisinden çıkarır.

submit olayında async fonksiyon kullanabilir miyim?

Evet, ama preventDefault() çağrısını ilk await ifadesinden önce yapmanız gerekir. MDN'ye göre dinleyicinin dönüş değeri yok sayılır; bir async fonksiyon ise ilk await noktasında bir Promise döndürerek çıkar. Bu noktaya kadar iptal edilmemiş bir olayın varsayılan eylemini sonradan durduramazsınız. async ve await için async/await yazısına bakın.

Formun geçerli olup olmadığını koddan nasıl sorarım?

form.checkValidity() tüm bağlı kontroller kurallara uyuyorsa true döndürür ve geçersiz her öğede invalid olayı tetikler. form.reportValidity() aynı kontrolü yapar ve iptal edilmemiş sorunları kullanıcıya gösterir. Kuralların kendisi required, pattern gibi HTML öznitelikleriyle tanımlanır.

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 · addEventListener kullanımı · Fetch API ile GET ve POST · Request, Response ve Headers · File API ve Blob · CSRF saldırısı ve korunma · HTML öğrenme rehberi