n8n ile Google Sheets Veri Okuma ve Yazma
Google Sheets, Türkiye’deki şirketlerin büyük çoğunluğunda hâlâ birincil veri depolama aracı olarak kullanılıyor. Muhasebe tabloları, müşteri listeleri, envanter kayıtları, satış raporları… Bunların hepsini bir noktada başka sistemlerle senkronize etmek gerekiyor ve bu iş genellikle “acil” olarak sysadmin’in masasına düşüyor. n8n tam da bu noktada devreye giriyor: görsel iş akışı yapısıyla Google Sheets’e hem okuma hem de yazma işlemlerini kodlamadan, ama kodun gücünü de kaybetmeden gerçekleştirmenizi sağlıyor.
Bu yazıda sıfırdan başlayarak gerçek dünya senaryolarını ele alacağım. Kurulum detaylarına takılmadan doğrudan entegrasyona odaklanacağız.
Google Sheets ile n8n Entegrasyonuna Başlamadan Önce
n8n’in Google Sheets node’u, Google Sheets API v4’ü kullanıyor. Bu nedenle önce Google Cloud Console üzerinde bir proje oluşturmanız ve OAuth 2.0 kimlik bilgilerini ya da Service Account oluşturmanız gerekiyor.
Hangi yöntemi seçmeli?
- OAuth 2.0: Kişisel Google hesabınızdaki spreadsheet’lere erişmek için idealdir. Kullanıcı bazlı izin akışı gerektirir.
- Service Account: Otomasyon senaryolarında tercih edilen yöntemdir. Hesap bağımsız çalışır, token yenileme derdi yoktur. Şirket ortamlarında bu yöntemi öneririm.
Service Account oluşturduktan sonra indirdiğiniz JSON dosyasındaki bilgileri n8n’in credential ayarlarına girmeniz yeterli. Spreadsheet’i de o service account’ın e-posta adresiyle paylaşmayı unutmayın, aksi hâlde “403 Permission Denied” hatasıyla karşılaşırsınız ve nerede hata yaptığınızı bulmak zaman alabilir.
# Service Account JSON dosyasından gerekli alanları çıkarmak için
cat service-account.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
print('Client Email:', data['client_email'])
print('Private Key ID:', data['private_key_id'])
"
Temel Okuma İşlemi: Spreadsheet’ten Veri Çekme
n8n’deki Google Sheets node’unu iş akışınıza eklediğinizde karşınıza birkaç operasyon seçeneği çıkıyor. En çok kullandıklarım şunlar:
- Read Rows: Belirtilen aralıktaki satırları okur
- Append Row: Sona yeni satır ekler
- Update Row: Var olan satırı günceller
- Delete Row: Satır siler
- Lookup Row: Belirli bir sütunda değer arar
Basit bir okuma senaryosundan başlayalım. Diyelim ki her sabah saat 08:00’de çalışan bir iş akışı var ve bu iş akışı satış ekibinin güncellediği bir Google Sheets tablosundan bugünkü hedefleri çekiyor.
Spreadsheet ID’yi URL’den alabilirsiniz:
# Google Sheets URL formatı:
# https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit
# Örnek:
# https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/edit
SPREADSHEET_ID="1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms"
SHEET_NAME="Satış Hedefleri"
RANGE="A1:E100"
n8n iş akışında Google Sheets node konfigürasyonu şu şekilde görünür. Node’un “Expression” modunda girdiğiniz değerler:
# Node parametreleri (Expression modunda kullanım)
Spreadsheet ID: {{ $vars.SALES_SHEET_ID }}
Sheet Name: Satış Hedefleri
Range: A:E
Options > First Row Is Header: true
First Row Is Header seçeneğini açık tutmanızı şiddetle tavsiye ederim. Bu sayede dönen veri JSON formatında, sütun başlıklarını key olarak kullanarak gelir. Aksi hâlde dizi indeksleriyle uğraşmak zorunda kalırsınız ki bu da workflow bakımını cehenneme çevirir.
Gerçek Senaryo 1: Müşteri Listesinden Toplu E-posta Gönderimi
En sık karşılaştığım otomasyon ihtiyaçlarından biri bu. Pazarlama ekibi bir Google Sheets’te müşteri listesi tutuyor ve her kampanya öncesinde “bunu otomatik yapabilir miyiz?” sorusu geliyor.
İş akışı yapısı:
- Schedule Trigger: Her Pazartesi 09:00
- Google Sheets (Read): Müşteri listesini çek
- IF Node:
email_onaylısütunu “EVET” olanları filtrele - Send Email / SMTP Node: E-posta gönder
- Google Sheets (Update): “son_gonderim_tarihi” sütununu güncelle
Filtreleme için IF node’unda kullandığım expression:
# IF Node koşulu - email_onaylı sütunu kontrolü
{{ $json["email_onaylı"] === "EVET" && $json["email"] !== "" }}
# Birden fazla koşul için AND operatörü
{{ $json["email_onaylı"] === "EVET" && $json["email"] !== "" && $json["aktif"] === "1" }}
Güncelleme işleminde satır numarasını dinamik olarak geçirmek için şunu kullanıyorum:
# Update Row - Row Number parametresi
# Google Sheets node Read işleminde her row'a otomatik row_number eklenir
Row Number: {{ $json["row_number"] }}
# Güncellenecek değer
son_gonderim_tarihi: {{ new Date().toLocaleDateString('tr-TR') }}
Bu noktada önemli bir detay: n8n’in Google Sheets node’u “Read Rows” yaparken her satıra row_number field’ı otomatik ekliyor. Bu değeri saklamak ve sonraki “Update Row” operasyonunda kullanmak çok işinize yarayacak.
Gerçek Senaryo 2: Sistem Monitoring Verilerini Sheets’e Yazma
Bu senaryo daha teknik ve benim en sevdiğim kullanım alanlarından. Birkaç sunucunun disk kullanımını, RAM durumunu ve servis sağlığını Google Sheets’e yazıp oradan da bir dashboard oluşturabilirsiniz. Küçük ekipler için Grafana kurmadan önce bu yeterli bir çözüm oluyor.
Önce n8n sunucusundan sistem metriklerini toplayan bir script:
#!/bin/bash
# /opt/scripts/collect_metrics.sh
HOSTNAME=$(hostname)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
RAM_TOTAL=$(free -m | awk 'NR==2{print $2}')
RAM_USED=$(free -m | awk 'NR==2{print $3}')
RAM_PERCENT=$(echo "scale=1; $RAM_USED * 100 / $RAM_TOTAL" | bc)
LOAD_AVG=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | tr -d ',')
# JSON formatında çıktı ver - n8n HTTP Request ile çağırılacak
echo "{
"hostname": "$HOSTNAME",
"timestamp": "$TIMESTAMP",
"disk_usage_percent": $DISK_USAGE,
"ram_usage_percent": $RAM_PERCENT,
"load_average": $LOAD_AVG
}"
n8n iş akışında bu scripti çalıştırmak için Execute Command node’u ya da HTTP Request node’u kullanabilirsiniz. Ben genellikle küçük bir Flask endpoint yazıp HTTP Request ile çekmeyi tercih ediyorum, böylece n8n sunucusuna SSH bağlantısı gerektirmiyor.
Metrikler geldikten sonra Google Sheets’e yazma kısmı:
# Append Row - Google Sheets Node parametreleri
Operation: Append Row
Spreadsheet ID: {{ $vars.MONITORING_SHEET_ID }}
Sheet Name: Sunucu Metrikleri
# Columns (key-value çiftleri olarak)
hostname: {{ $json.hostname }}
tarih_saat: {{ $json.timestamp }}
disk_kullanim: {{ $json.disk_usage_percent }}
ram_kullanim: {{ $json.ram_usage_percent }}
yuk_ortalama: {{ $json.load_average }}
durum: {{ $json.disk_usage_percent > 85 ? "UYARI" : "NORMAL" }}
Buradaki inline ternary expression özellikle kullanışlı. Disk %85’i geçince otomatik “UYARI” yazıyor ve Sheets’teki conditional formatting ile kırmızıya dönüyor. Basit ama etkili.
Gerçek Senaryo 3: Sheets’ten Veri Okuyup Veritabanına Senkronize Etme
Bu senaryo özellikle eski sistemlerden modernleşme sürecinde sık karşılaşılan bir ihtiyaç. Muhasebe departmanı hâlâ Sheets’te çalışıyor ama yeni sistemin bir PostgreSQL veritabanı var.
# n8n Code Node içinde kullanılabilecek veri dönüşümü
# Sheets'ten gelen veriyi DB formatına çevirmek için
const sheetsData = $input.all();
const transformedData = sheetsData.map(item => {
const row = item.json;
// Tarih formatı dönüşümü: DD.MM.YYYY -> YYYY-MM-DD
const dateParts = row['fatura_tarihi'].split('.');
const isoDate = `${dateParts[2]}-${dateParts[1]}-${dateParts[0]}`;
// Para birimi temizleme: "1.250,50 TL" -> 1250.50
const amount = parseFloat(
row['tutar']
.replace(' TL', '')
.replace('.', '')
.replace(',', '.')
);
return {
json: {
invoice_id: row['fatura_no'],
customer_name: row['musteri_adi'].trim(),
invoice_date: isoDate,
amount_try: amount,
status: row['durum'].toLowerCase(),
created_at: new Date().toISOString()
}
};
});
return transformedData;
Bu Code node çıktısı doğrudan PostgreSQL node’una bağlanıyor ve INSERT işlemi gerçekleştiriyor. Sheets’teki her satır için ayrı bir DB kaydı oluşturuluyor.
Duplicate kontrolü için önce DB’den sorgulama yapıp IF node ile filtreleme ekliyorum:
# PostgreSQL Node - Duplicate kontrolü
Query: SELECT invoice_id FROM invoices WHERE invoice_id = $1
Query Parameters: {{ $json.invoice_id }}
# IF Node koşulu - Kayıt yoksa INSERT yap
{{ $json.length === 0 }}
Hata Yönetimi ve Loglama
Üretim ortamında n8n workflow’larında hata yönetimi çok kritik. Google Sheets API rate limit’lerine takılmak, token süresinin dolması ya da spreadsheet’in erişim izinlerinin değişmesi gibi durumlar olabiliyor.
Error Trigger node’u ile tüm hataları merkezi olarak yakalamak ve bir log sheet’ine yazmak iyi bir pratik:
# Error Workflow - Hata loglarını Sheets'e yaz
# Bu workflow ayrı bir workflow olarak tanımlanır
# Ana workflow'larda "Error Workflow" olarak seçilir
# Google Sheets Append Row parametreleri
workflow_adi: {{ $json.workflow.name }}
hata_zamani: {{ new Date().toLocaleString('tr-TR', {timeZone: 'Europe/Istanbul'}) }}
hata_mesaji: {{ $json.execution.error.message }}
hata_node: {{ $json.execution.error.node.name }}
execution_id: {{ $json.execution.id }}
ortam: production
Bu şekilde tüm iş akışı hatalarınız tek bir Sheets’te toplanıyor. Sabah işe geldiğinizde ilk bakacağınız yer burası olacak.
Büyük Veri Setleriyle Çalışma ve Rate Limiting
Google Sheets API’nin günlük istek limitleri var. Ücretsiz hesaplar için dakikada 60 okuma isteği sınırı mevcut. Binlerce satırlık verileri işlerken bu limite çarpmak mümkün.
# Büyük veri setleri için batch işleme
# SplitInBatches Node konfigürasyonu
Batch Size: 50
# Her batch arasında bekleme için Wait Node
Wait Amount: 2
Wait Unit: seconds
Ayrıca Google Sheets node’unda Range parametresini iyi kullanmak önemli. Tüm spreadsheet’i çekmek yerine ihtiyacınız olan aralığı belirtin:
# Kötü pratik - tüm sayfayı çeker, gereksiz veri transferi
Range: A:Z
# İyi pratik - sadece ihtiyaç duyulan sütunlar
Range: A:F
# Daha iyi pratik - başlık satırını biliyorsanız
Range: A2:F1000
# ya da dinamik olarak
Range: {{ `A2:F${$vars.MAX_ROWS}` }}
Lookup ile Çift Yönlü Senkronizasyon
Bazen bir sistemden gelen veriyi Sheets’te aramak ve eşleşen satırı güncellemek gerekiyor. Yani sadece “append” değil, “upsert” mantığı gerekiyor.
# Önce Lookup ile kaydın var olup olmadığını kontrol et
Operation: Lookup
Lookup Column: musteri_id
Lookup Value: {{ $json.customer_id }}
# IF Node - kayıt bulundu mu?
{{ $json.row_number !== undefined }}
# TRUE branch: Update Row
Operation: Update Row
Row Number: {{ $json.row_number }}
son_guncelleme: {{ new Date().toLocaleDateString('tr-TR') }}
bakiye: {{ $json.new_balance }}
# FALSE branch: Append Row
Operation: Append Row
musteri_id: {{ $json.customer_id }}
musteri_adi: {{ $json.customer_name }}
bakiye: {{ $json.new_balance }}
kayit_tarihi: {{ new Date().toLocaleDateString('tr-TR') }}
Bu pattern, basit bir CRM otomasyonu için yeterli. Müşteri veritabanınızı Sheets üzerinde tutabilir ve farklı kaynaklardan gelen güncellemeleri otomatik olarak senkronize edebilirsiniz.
n8n’i Docker Üzerinde Çalıştıranlar İçin
Eğer n8n’i Docker Compose ile ayağa kaldırıyorsanız, Google Service Account JSON dosyasını volume olarak mount etmek yerine environment variable olarak geçirmenizi öneririm:
# docker-compose.yml
version: '3.8'
services:
n8n:
image: n8nio/n8n
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- GOOGLE_APPLICATION_CREDENTIALS=/home/node/.n8n/google-sa.json
volumes:
- n8n_data:/home/node/.n8n
- /opt/secrets/google-sa.json:/home/node/.n8n/google-sa.json:ro
ports:
- "5678:5678"
volumes:
n8n_data:
# Service account dosyasının izinlerini kontrol et
# n8n container'ı node kullanıcısıyla çalışıyor (UID 1000)
sudo chown 1000:1000 /opt/secrets/google-sa.json
sudo chmod 400 /opt/secrets/google-sa.json
Performans ve Bakım İpuçları
Gerçek hayatta birkaç kez yanıp bunları öğrendim:
- Spreadsheet’i bölün: Tek bir büyük sheet yerine “Veri”, “Arşiv”, “Log” gibi ayrı sheet’ler kullanın. n8n’de sheet adını dinamik olarak belirleyebilirsiniz.
- Header satırını sabitleyin: Hiçbir zaman header satırını silmeyin ya da değiştirmeyin. Tüm workflow’lar bu isimlere göre çalışıyor. Değişiklik gerekiyorsa önce workflow’ları güncelleyin.
- Credentials rotasyonu: Service Account anahtarlarını en az yılda bir kez yenileyin. Takvime ekleyin, unutmayın. Eski anahtar devre dışı kalınca tüm otomasyonlar sessizce ölüyor.
- Test sheet’i oluşturun: Production sheet’in bir kopyasını her zaman test için kullanın. n8n’de environment variable olarak sheet ID’yi tutun ve test/prod geçişini kolaylaştırın.
# n8n Variables (Settings > Variables)
SHEETS_CUSTOMER_LIST_PROD=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms
SHEETS_CUSTOMER_LIST_TEST=2CyiNWt1YSB6oGNLwceCaakhnVqrusmct85PhWF3vqnt
# Workflow'da kullanım
Spreadsheet ID: {{ $vars["SHEETS_CUSTOMER_LIST_" + $vars.ENVIRONMENT] }}
Sonuç
Google Sheets’i bir otomasyon hub’ı olarak kullanmak başlangıçta biraz “hacky” gelebilir, ama şunu söyleyeyim: çoğu şirket için bu yeterince pragmatik bir çözüm. Herkesin anlayabileceği bir arayüzde duruyor, versiyon geçmişi var, paylaşımı kolay. Asıl mesele bu veriyi doğru araçlarla doğru noktalara taşımak.
n8n, bu taşıma işini görsel olarak yönetilebilir kılıyor. Kod bilmeyen bir takım arkadaşı bile iş akışını okuyabiliyor, hangi adımda ne olduğunu anlayabiliyor. Bu, bakım açısından ciddi bir avantaj.
En sık kullandığım pattern’ı özetlersem: Sheets veri deposu, n8n ise iş mantığı ve entegrasyon katmanı. Birbirlerinin işini yapmaya çalışmaları yerine bu rollerde tutmak, uzun vadede hayatınızı kolaylaştırıyor.
Sorularınız için blog’un yorum bölümünü ya da LinkedIn’i kullanabilirsiniz. Özellikle büyük veri setleriyle yaşanan rate limit sorunları hakkında deneyimlerinizi duymak isterim.
