Bir Discord botunu sadece komut yanıtlayan bir araçtan kullanışlı bir arayüze dönüştüren şey bileşenlerdir (components): onay butonları, rol seçme menüleri, başvuru formları. Kullanıcı bir butona bastığında, menüden seçim yaptığında ya da bir formu gönderdiğinde botunuza yeni bir interaction gelir ve bunu tıpkı slash komut gibi yanıtlamanız gerekir.
Bu rehberde Discord'un resmî Component Reference belgesi ve discord.js rehberi ışığında butonların stillerini, beş farklı select menu türünü, modal içinde kullanılabilen güncel bileşenleri, custom_id sınırlarını ve bu etkileşimleri collector ile mi yoksa kalıcı bir dinleyiciyle mi karşılamanız gerektiğini anlatıyoruz. Komut tarafını henüz kurmadıysanız önce slash komut oluşturma yazısına göz atın.
custom_id: her bileşenin kimliği
Buton ve select menu gibi etkileşimli bileşenler bir custom_id taşır. Bunu siz belirlersiniz; kullanıcı bileşeni kullandığında Discord aynı değeri interaction ile geri gönderir. Belgeye göre custom_id 1-100 karakterlik bir string'dir ve aynı mesajdaki bileşenler aynı custom_id'yi paylaşamaz. Modal'ın kendi custom_id'si de 1-100 karakterdir.
Ayrıca her bileşen isteğe bağlı, 32 bitlik sayısal bir id alanı alabilir. Bu, custom_id'den farklıdır: mesaj içindeki bileşeni tanımlamak için kullanılır, verilmezse Discord sırayla doldurur.
Butonlar: stiller ve kurallar

Buton yalnızca mesajlarda kullanılabilir ve bir Action Row içine ya da Components V2'deki bir Section'ın accessory alanına yerleştirilir. Bir Action Row en fazla 5 buton taşır. Buton etiketi en fazla 80 karakter, link URL'si en fazla 512 karakterdir.
| Stil | Değer | Kullanım | Gerekli alan |
|---|---|---|---|
| Primary | 1 | Gruptaki en önemli eylem | custom_id |
| Secondary | 2 | Alternatif ya da destekleyici eylem | custom_id |
| Success | 3 | Olumlu onay | custom_id |
| Danger | 4 | Geri alınamaz eylem (ban, silme) | custom_id |
| Link | 5 | Bir URL açar | url |
| Premium | 6 | Satın alma | sku_id |
- Link butonları
custom_idalamaz ve tıklandığında botunuza interaction göndermez. - Premium butonlar
sku_idiçermek zorundadır;custom_id,label,urlveyaemojialamaz ve interaction göndermez. Mağaza simgesi, SKU adı ve fiyatı otomatik gösterilir. Ayrıntı: Discord uygulama premium abonelik. - Discord'un tasarım önerisi: bir grupta yalnızca bir Primary buton kullanın; eşit önemdeki butonlar için Secondary seçin.
setDisabled(true)butonu mesajdan kaldırmadan devre dışı bırakır.
const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const onay = new ButtonBuilder()
.setCustomId(`ban:onay:${hedef.id}`)
.setLabel('Banla')
.setStyle(ButtonStyle.Danger);
const iptal = new ButtonBuilder()
.setCustomId('ban:iptal')
.setLabel('Vazgeç')
.setStyle(ButtonStyle.Secondary);
const kurallar = new ButtonBuilder()
.setLabel('Sunucu kuralları')
.setURL('https://example.com/kurallar')
.setStyle(ButtonStyle.Link);
const satir = new ActionRowBuilder().addComponents(iptal, onay, kurallar);
await interaction.reply({ content: `${hedef} banlansın mı?`, components: [satir] });Modal: açılır formlar

Modal, kullanıcıdan form biçiminde veri toplayan bir açılır penceredir. Bir slash komuta ya da bileşen interaction'ına ilk yanıt olarak gösterilir; modal göndermesine (MODAL_SUBMIT) ve PING'e yanıt olarak modal açılamaz. Modal açmadan önce defer edemezsiniz.
| Modal alanı | Sınır |
|---|---|
| custom_id | 1-100 karakter |
| title | En fazla 45 karakter |
| components | 1 ile 5 arası üst düzey bileşen |
Modal yapısı yakın zamanda değişti. Belgeye göre artık metin girişleri ve menüler bir Label bileşeninin içine konuyor; Action Row içinde Text Input kullanımı kullanımdan kaldırılmış (deprecated) durumda ve Text Input üzerindeki label alanının yerini Label bileşeninin label ve description alanları aldı. Modal içinde kullanılabilen güncel bileşenler:
| Bileşen (değer) | Ne işe yarar | Önemli sınırlar |
|---|---|---|
| Label (18) | Bir alanı başlık ve açıklamayla sarar | label en fazla 45, description en fazla 100 karakter |
| Text Input (4) | Kısa (Short) ya da çok satırlı (Paragraph) metin | min/max uzunluk ve ön değer en fazla 4000 karakter; placeholder 100 karakter |
| Text Display (10) | Modal içinde bilgi metni (markdown) | Label gerektirmez |
| String / User / Role / Mentionable / Channel Select | Modal içinde seçim | Label içinde; required alanı yalnızca modalda geçerli |
| File Upload (19) | Dosya yükletir | 0-10 dosya; file_types ile en fazla 10 tür filtresi |
| Radio Group (21) | Tek seçim | 2-10 seçenek |
| Checkbox Group (22) | Çoklu seçim | 1-10 seçenek |
| Checkbox (23) | Evet / hayır | Zorunlu yapılamaz; zorunluysa tek seçenekli Checkbox Group kullanın |
const {
ModalBuilder, LabelBuilder, TextInputBuilder, TextInputStyle, MessageFlags,
} = require('discord.js');
// Butona basılınca başvuru formunu aç
if (interaction.isButton() && interaction.customId === 'basvuru-ac') {
const modal = new ModalBuilder().setCustomId('basvuru').setTitle('Yetkili Başvurusu');
const yas = new LabelBuilder()
.setLabel('Yaşınız')
.setTextInputComponent(
new TextInputBuilder().setCustomId('yas').setStyle(TextInputStyle.Short).setMaxLength(3));
const neden = new LabelBuilder()
.setLabel('Neden yetkili olmak istiyorsunuz?')
.setDescription('Kısaca deneyiminizi anlatın')
.setTextInputComponent(
new TextInputBuilder().setCustomId('neden').setStyle(TextInputStyle.Paragraph).setMinLength(30));
modal.addLabelComponents(yas, neden);
await interaction.showModal(modal);
}
// Form gönderilince
if (interaction.isModalSubmit() && interaction.customId === 'basvuru') {
const yas = interaction.fields.getTextInputValue('yas');
const neden = interaction.fields.getTextInputValue('neden');
await interaction.reply({ content: 'Başvurunuz alındı.', flags: MessageFlags.Ephemeral });
}Rehber ayrıca seçim menüleri için getStringSelectValues(), dosyalar için getUploadedFiles() ve yeni bileşenler için getRadioGroup(), getCheckboxGroup(), getCheckbox() yardımcılarını gösteriyor. Boş bırakılan metin alanı boş string, seçim yapılmayan menü boş dizi döner.
Etkileşimleri karşılamak: collector mı, global handler mı?
Bileşen interaction'ları da 3 saniye içinde yanıtlanmalıdır. discord.js rehberi üç yol sunar:
| Yöntem | Ne zaman uygun | Dezavantajı |
|---|---|---|
| awaitMessageComponent() | Tek bir yanıt beklenen akışlar: onay / iptal | Zaman aşımında hata fırlatır; bot yeniden başlarsa bekleme kaybolur |
| createMessageComponentCollector() | Belirli bir süre boyunca çok sayıda etkileşim: anket, sayfalama | Bellekte yaşar; yeniden başlatmada kaybolur |
| Events.InteractionCreate dinleyicisi | Kalıcı arayüzler: rol menüsü, destek talebi butonu | Durumu custom_id ya da veritabanında kendiniz taşımalısınız |
// 1) Tek seferlik onay: awaitMessageComponent
const yanit = await interaction.reply({ content: 'Emin misiniz?', components: [satir], withResponse: true });
try {
const tik = await yanit.resource.message.awaitMessageComponent({
filter: (i) => i.user.id === interaction.user.id,
time: 60_000,
});
await tik.update({ content: tik.customId.startsWith('ban:onay') ? 'Banlandı.' : 'İptal edildi.', components: [] });
} catch {
await interaction.editReply({ content: '1 dakika içinde yanıt gelmedi.', components: [] });
}
// 2) Kalıcı handler: bot yeniden başlasa da çalışır
client.on(Events.InteractionCreate, async (i) => {
if (i.isButton() && i.customId === 'destek-ac') {
await i.reply({ content: 'Destek talebiniz açıldı.', flags: MessageFlags.Ephemeral });
} else if (i.isStringSelectMenu() && i.customId === 'oyun-sec') {
await i.update({ content: `Seçiminiz: ${i.values.join(', ')}`, components: [] });
}
});- update(): Bileşenin bağlı olduğu mesajı düzenleyerek interaction'ı yanıtlar. Mesajı yerinde değiştirmek için en doğru yoldur.
- deferUpdate(): Yanıtı erteler ve kullanıcıya yükleniyor durumu göstermez; işlem sonrası
editReply()ya dafollowUp()kullanılır. - filter: Onay butonlarında yalnızca komutu çalıştıran kişinin basabilmesi için
i.user.id === interaction.user.idfiltresi kullanın. - Komut, buton ve modal yanıtlarındaki zamanlama hataları için etkileşim başarısız oldu hatası yazısına bakın.
Mesaj başına sınırlar
- Klasik (legacy) mesajlarda en fazla 5 Action Row üst düzey bileşen olabilir.
- Bir Action Row ya en fazla 5 buton ya da tek bir select menu içerir.
- Components V2 bayrağıyla gönderilen mesajlarda toplam 40 bileşene kadar çıkılabilir ve butonlar Section içinde metnin yanında da durabilir. Ayrıntılar: Components V2 nedir.
- Webhook ile bileşen gönderiyorsanız belge
?with_components=truesorgu parametresini istiyor; yoksa bileşenler yok sayılır.
Sık Sorulan Sorular
Butona basınca "Etkileşim başarısız oldu" çıkıyor, neden?
Butonun custom_id'sini dinleyen bir kod yok, collector süresi dolmuş ya da yanıt 3 saniyeyi geçiyor. Link butonları ise zaten interaction göndermez.
custom_id'ye ne kadar veri koyabilirim?
En fazla 100 karakter. Kullanıcı ID'si gibi kısa durum bilgileri sığar; daha büyük veriler için bir anahtar koyup asıl veriyi SQLite ya da Redis gibi bir depoda tutun.
Modal'da kaç alan olabilir?
Modal en fazla 5 üst düzey bileşen alır. discord.js rehberine göre bunların her biri bir Label ya da Text Display olabilir.
Modal'a select menu koyabilir miyim?
Evet. String, user, role, mentionable ve channel select menüleri modal içinde bir Label'ın içine konarak kullanılabilir. Modalda required alanı geçerlidir, mesajlarda yok sayılır.
Bileşenli mesaja embed de ekleyebilir miyim?
Klasik bileşenlerde evet, content ve embeds ile birlikte kullanılabilir. Components V2 bayrağı kullanıyorsanız hayır; o modda metin ve görseller bileşenlerle verilir. Embed tarafı için Discord embed mesaj oluşturma yazısına bakın.
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: Slash komut oluşturma · Etkileşim başarısız oldu hatası · Components V2 nedir · Embed mesaj oluşturma · Premium abonelik · Discord webhook ve bot entegrasyonu