n8n-skill-claude

内容来源:SKILL.md(标准 Skill 格式) · 原始地址 · 查看安装指南

原始内容

n8n Workflow Automation Skill

by @thealiilhan

Bu skill, n8n ile production-ready otomasyon workflow'ları oluşturmak için gereken tüm pattern'leri, node kurallarını ve entegrasyon detaylarını içerir.


TEMEL EXPRESSION SYNTAX

{{ $json.fieldName }}                          → mevcut node çıktısı
{{ $json['field name'] }}                      → boşluklu alan adı
{{ $('NodeAdı').item.json.fieldName }}          → başka node'dan veri
{{ $('NodeAdı').all()[0].json.fieldName }}      → başka node'dan ilk item
{{ $node["NodeAdı"].json.fieldName }}           → alternatif syntax
{{ JSON.stringify($json.array) }}              → array'i JSON string'e çevir
{{ $now.toISO() }}                             → şu anki timestamp (Luxon)
{{ $today.toFormat('yyyy-MM-dd') }}            → tarih formatı
{{ $json.list.length }}                        → array uzunluğu
{{ $runIndex }}                                → SplitInBatches loop index

Kritik: $('NodeAdı') büyük/küçük harf duyarlıdır. Türkçe karakter dahil, node adı değişirse expression da güncellenmeli.


CODE NODE — JAVASCRIPT

// Tüm input item'larını al
const items = $input.all();

// Tek item
const item = $input.first();

// Başka node'dan veri al
const config = $('Config').item.json;
const allRows = $('Apify Sonuçları').all();

// Temel output formatı — her zaman { json: {} } döndür
return items.map(item => ({
  json: {
    fieldA: item.json.something,
    fieldB: 'statik değer',
    processedAt: new Date().toISOString()
  }
}));

// Filtreleme
const filtered = items.filter(item => item.json.rating <= 2);
return filtered.map(item => ({ json: item.json }));

// Birden fazla item üret (split/expand)
const result = [];
for (const item of items) {
  for (const row of item.json.rows || []) {
    result.push({ json: row });
  }
}
return result;

// Boş kontrol — hiç output vermemek için
if (!items.length) return [];

HTTP Request (Code Node içinden)

const response = await this.helpers.httpRequest({
  method: 'POST',
  url: 'https://api.example.com/endpoint',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer TOKEN'
  },
  body: { key: 'value' },
  json: true  // body'yi otomatik stringify, response'u parse et
});
return [{ json: response }];

JSON Parse (güvenli)

let parsed;
try {
  parsed = JSON.parse(rawText);
} catch (e) {
  const cleaned = rawText
    .replace(/```json\n?/g, '')
    .replace(/```\n?/g, '')
    .trim();
  parsed = JSON.parse(cleaned);
}

SUPABASE ENTEGRASYONU

HTTP Request Node ile (önerilen)

Method: POST
URL: https://PROJE_ID.supabase.co/rest/v1/tablo_adi
Headers:
  apikey: SERVICE_ROLE_KEY
  Authorization: Bearer SERVICE_ROLE_KEY
  Content-Type: application/json
  Prefer: return=representation              → insert sonrası veriyi döndür
  Prefer: resolution=merge-duplicates        → upsert (üsttekiyle birlikte)
Body: {{ $json }}

Code Node içinden (batch insert)

const rows = $input.all().map(item => ({
  field1: item.json.value1,
  field2: item.json.value2
}));

const response = await this.helpers.httpRequest({
  method: 'POST',
  url: `https://${PROJECT_ID}.supabase.co/rest/v1/${TABLE}`,
  headers: {
    'apikey': SERVICE_ROLE_KEY,
    'Authorization': `Bearer ${SERVICE_ROLE_KEY}`,
    'Content-Type': 'application/json',
    'Prefer': 'return=minimal'
  },
  body: rows,
  json: true
});
return [{ json: { inserted: rows.length } }];

SELECT / Filtreleme

GET https://PROJE.supabase.co/rest/v1/tablo?select=*&status=eq.active&limit=100
GET https://PROJE.supabase.co/rest/v1/tablo?select=id,name&created_at=gte.2024-01-01
GET https://PROJE.supabase.co/rest/v1/tablo?select=*&order=created_at.desc&limit=50

Kritik: RLS açıksa anon key çalışmaz — mutlaka service role key kullan.


APIFY ASYNC POLLING PATTERN

Apify actor'ları asenkron çalışır. Run başlatılır, bitene kadar status poll edilir, sonra dataset çekilir.

Run Başlat → Run ID Kaydet → Wait → Status Kontrol → Poll State Güncelle
                                                              ↓
                                          SUCCEEDED? → YES → Dataset Çek
                                                     → NO  → Max Poll Aşıldı?
                                                               → YES → Hata log
                                                               → NO  → Wait'e dön (loop)

Adım 1 — Run Başlat (HTTP Request)

POST https://api.apify.com/v2/acts/ACTOR_ID/runs?token=APIFY_TOKEN
Content-Type: application/json
Body: { ...actor input parametreleri }

Adım 2 — Run ID Kaydet (Set Node)

runId     = {{ $json.data.id }}
pollCount = 0
maxPolls  = 20

Adım 3 — Wait Node

15–30 saniye (actor ağırlığına göre ayarla)
NOT: Wait node bir webhookId alır. Node'u silip yeniden eklersen ID değişir, loop kırılır.

Adım 4 — Status Kontrol (HTTP Request)

GET https://api.apify.com/v2/acts/ACTOR_ID/runs/{{ $('Run ID Kaydet').item.json.runId }}?token=APIFY_TOKEN

Adım 5 — Poll State Güncelle (Set Node)

runId     = {{ $('Run ID Kaydet').item.json.runId }}
pollCount = {{ $('Run ID Kaydet').item.json.pollCount + 1 }}
maxPolls  = {{ $('Run ID Kaydet').item.json.maxPolls }}
status    = {{ $json.data.status }}

Adım 6 — IF: SUCCEEDED mi?

leftValue:  {{ $json.status }}
operation:  equals
rightValue: SUCCEEDED
TRUE  → Dataset Çek
FALSE → Max Poll Aşıldı mı? (pollCount >= maxPolls)

Adım 7 — Dataset Çek (HTTP Request)

GET https://api.apify.com/v2/actor-runs/{{ $('Run ID Kaydet').item.json.runId }}/dataset/items
Params: token=APIFY_TOKEN, format=json, clean=true

Alternatif: run-sync (kısa actor'lar için)

POST https://api.apify.com/v2/acts/ACTOR_ID/run-sync-get-dataset-items?token=TOKEN&waitForFinish=120

2 dakika üzeri işlerde timeout'a düşer → o zaman async pattern kullan.


SOSYAL MEDYA SCRAPING

Instagram (RapidAPI)

# Profil
GET https://instagram-social-api.p.rapidapi.com/v1/info?username_or_id_or_url=USERNAME

# Postlar
GET https://instagram-social-api.p.rapidapi.com/v1/posts?username_or_id_or_url=USERNAME

Headers: X-RapidAPI-Key, X-RapidAPI-Host: instagram-social-api.p.rapidapi.com

# Veri yapısı:
# response.data.items[] → postlar
# item.caption.text → caption
# item.caption.hashtags[] → hashtagler
# item.location.name → lokasyon
# item.like_count, item.comment_count

TikTok (RapidAPI — keyword arama)

GET https://tiktok-scraper7.p.rapidapi.com/feed/search
Params:
  keywords: arama terimi
  region: tr
  count: 10
  cursor: 0
  publish_time: 0 (tümü) | 1 (gün) | 7 (hafta) | 30 (ay)
  sort_type: 0 (alaka) | 1 (beğeni)
Headers: X-Rapidapi-Key, X-Rapidapi-Host: tiktok-scraper7.p.rapidapi.com

Twitter/X (Apify — run-sync)

POST https://api.apify.com/v2/acts/61RPP7dywgiy0JPD0/run-sync-get-dataset-items?token=TOKEN
Body:
{
  "searchTerms": ["arama terimi"],
  "maxItems": 100,
  "sort": "Latest",
  "tweetLanguage": "tr",
  "onlyVerifiedUsers": false,
  "onlyTwitterBlue": false
}
Timeout: 300000ms

# Veri yapısı:
# item.id, item.text, item.createdAt, item.url
# item.author.userName, item.author.name, item.author.followers
# item.author.isVerified, item.author.isBlueVerified, item.author.location
# item.likeCount, item.retweetCount, item.replyCount, item.viewCount

Trustpilot (Apify — async)

Actor ID: 6q70QEFc2Zk0ObldU
Body:
{
  "companyUrls": ["domain.com"],
  "maxReviewsPerCompany": 10,
  "sort": "recency",
  "verified": false,
  "withReplies": false,
  "includeCompanyInfo": true
}
→ Async polling pattern zorunlu (2+ dakika sürer)

YouTube (RapidAPI)

GET https://youtube-search-and-download.p.rapidapi.com/search
Params: query, type=v, sort=r (relevance) | u (upload date)
Headers: X-RapidAPI-Key, X-RapidAPI-Host: youtube-search-and-download.p.rapidapi.com

Meta Ads Library / Google Ads Transparency

→ Apify actor ile çek, async pattern uygula
→ Veri: advertiser, ad copy, start date, platforms, media

VERİ DEPOLAMA

Google Sheets — Append

Node: Google Sheets → Operation: Append
documentId: Sheet URL veya ID
sheetName: Sayfa adı
Columns mappingMode: autoMapInputData

# Kritik: JSON key adları = kolon başlığı (büyük/küçük harf dahil eşleşmeli)
return items.map(item => ({
  json: {
    'Tweet ID': item.json.id,
    'Kullanıcı': item.json.author?.userName || '',
    'Beğeni': item.json.likeCount || 0,
    'Tarih': item.json.createdAt,
    'Çekilme Zamanı': new Date().toISOString()
  }
}));

Google Sheets — Upsert

Node: Google Sheets → Operation: appendOrUpdate
matchingColumns: ['Tweet ID']  → bu kolona göre güncelle, yoksa ekle

Supabase — Upsert (duplicate önleme)

Headers'a ekle: Prefer: resolution=merge-duplicates
+ on_conflict parametresi için URL'e ekle: ?on_conflict=unique_kolon

EMAIL

Brevo ile HTML Mail

POST https://api.brevo.com/v3/smtp/email
Headers: api-key: BREVO_KEY, Content-Type: application/json
Body:
{
  "sender": { "name": "Bot Adı", "email": "gonderici@domain.com" },
  "to": [{ "email": "{{ $json.recipientEmail }}", "name": "{{ $json.recipientName }}" }],
  "cc": [{ "email": "{{ $json.ccEmail }}" }],
  "subject": "{{ $json.subject }}",
  "htmlContent": "{{ $json.htmlContent }}"
}
Timeout: 30000ms

HTML Mail Şablonu

const htmlContent = `
<!DOCTYPE html><html>
<body style="background:#f3f4f6; padding:40px 10px; margin:0;">
  <div style="max-width:650px; margin:0 auto; background:#fff; border-radius:12px;
              box-shadow:0 4px 20px rgba(0,0,0,0.08); border:1px solid #e5e7eb; overflow:hidden;">
    <div style="background:linear-gradient(135deg,#4A90E2 0%,#6366f1 100%);
                padding:35px 20px; text-align:center; color:white;">
      <h1 style="margin:0; font-size:24px; font-family:sans-serif;">${title}</h1>
      <p style="margin-top:8px; opacity:0.9; font-size:14px; font-family:sans-serif;">${date}</p>
    </div>
    <div style="padding:30px 40px;">${body}</div>
    <div style="background:#f9fafb; padding:20px; text-align:center; border-top:1px solid #e5e7eb;">
      <p style="margin:0; color:#9ca3af; font-size:12px; font-family:sans-serif;">Otomatik rapor</p>
    </div>
  </div>
</body></html>`;

Gmail (n8n Native)

Node: Gmail → Operation: Send
To: {{ $json.email }}
Subject: {{ $json.subject }}
Message: {{ $json.htmlContent }}
Message Type: HTML

AI ENTEGRASYONU

OpenAI (HTTP Request Node)

POST https://api.openai.com/v1/chat/completions
Headers: Authorization: Bearer sk-..., Content-Type: application/json
Body:
{
  "model": "gpt-4o-mini",
  "messages": [
    { "role": "system", "content": "Sen bir uzman asistansın." },
    { "role": "user", "content": {{ JSON.stringify($json.prompt) }} }
  ],
  "response_format": { "type": "json_object" },
  "temperature": 0.7,
  "max_tokens": 2000
}

# Yanıtı oku: {{ $json.choices[0].message.content }}

n8n AI Node (LangChain)

Node: @n8n/n8n-nodes-langchain.openAi
modelId: gpt-4o-mini
responses → content: {{ $json.prompt }}

# Çıktıyı oku: {{ $json.output[0].content[0].text }}

Prompt İçinde n8n Değişkeni

// String içinde
`Şu kullanıcıyı analiz et: ${$('Config').item.json.username}`

// Expression içinde — tırnak sorununu önlemek için JSON.stringify kullan
{{ JSON.stringify($json.data) }}

// Sistem promptu + veri birleştirme
const prompt = `Veriyi işle:\n${JSON.stringify(items.map(i => i.json))}`;

WORKFLOW MİMARİSİ

Config Node Pattern

Her workflow'un başına bir Set node koy, tüm değişkenleri burada tut:

Node: Set (adı: Config)
  companyUrls   = "domain1.com,domain2.com"
  maxResults    = 5
  targetEmail   = "hedef@email.com"
  slackWebhook  = "https://hooks.slack.com/..."

Kullanımı: {{ $('Config').item.json.maxResults }}

Multi-Platform Paralel Akış

Trigger → Config
              ↓
    ┌─────────┼──────────┬──────────┐
  Platform1 Platform2 Platform3 Platform4
    ↓           ↓          ↓          ↓
  Format     Format     Format     Format
    ↓           ↓          ↓          ↓
  Hedef      Hedef      Hedef      Hedef

Her dal bağımsız çalışır, birinin hatası diğerini etkilemez.

Split → Process → Merge

// Büyük array'i item'lara böl
return bigArray.map(row => ({ json: row }));

// Sonra SplitInBatches node ile batch'le (rate limit varsa)
// Batchsize: 10, her batch arasında Wait: 1sn

// Merge node ile sonunda birleştir
// Mode: Append All Items

Website Monitor Pattern

Schedule Trigger (12 saatte bir)
    → URL Listesi (Code Node)  [array → her URL ayrı item]
    → HTTP Request (onError: continueErrorOutput)
        ├── [Başarı output] → IF status == "up" → log
        └── [Hata output]  → Email / Slack alert

HATA YÖNETİMİ

HTTP Request Node

Settings → On Error: continueErrorOutput
  → output 1: başarılı yanıt
  → output 2: hata ($json.error.message)

Settings → Retry On Fail: true, Max Tries: 2, Wait: 1000ms
Settings → Timeout: 300000 (scraping işlemlerinde)

Code Node — Try/Catch

try {
  const result = JSON.parse($json.responseText);
  return [{ json: { success: true, data: result } }];
} catch (error) {
  return [{ json: { success: false, error: error.message, raw: $json.responseText } }];
}

Hata Email/Slack Bildirimi

// Hata branch'inde:
return [{
  json: {
    subject: `⚠️ Workflow Hatası - ${new Date().toLocaleDateString('tr-TR')}`,
    body: `Node: ${$json.node || 'bilinmiyor'}\nHata: ${$json.error?.message || 'bilinmiyor'}`,
    recipient: 'ali@domain.com'
  }
}];

TRIGGERLAR

# Zamanlama
Schedule Trigger
  field: minutes (minutesInterval: 30)
  field: hours   (hoursInterval: 6)
  field: days    (hour: 9, minute: 0)  → her sabah 09:00
  field: weeks   → haftalık

# Manuel test
Manual Trigger

# Webhook (dışarıdan tetikleme)
Webhook Trigger
  HTTP Method: POST
  URL: https://n8n-instance/webhook/UUID
  Authentication: Header Auth

# Form
n8n Form Trigger → form alanlarını tanımla
  URL: https://n8n-instance/form/UUID

CREDENTIAL TIPLERI

Servis n8n Credential Tipi Detay
Apify httpQueryAuth ?token=TOKEN query param
Apify httpHeaderAuth Authorization: Bearer TOKEN
RapidAPI httpHeaderAuth X-RapidAPI-Key header
Supabase httpHeaderAuth apikey + Authorization: Bearer
OpenAI httpHeaderAuth Authorization: Bearer sk-...
Brevo httpHeaderAuth api-key header
Google Sheets googleSheetsOAuth2Api OAuth2
Gmail gmailOAuth2 OAuth2
GitHub githubApi Personal access token
Slack slackApi Bot token

SIK YAPILAN HATALAR

1. $('NodeAdı') — node adı değişti, expression güncellenmedi → kırmızı hata
2. Supabase anon key ile RLS bypass → 401/403, service role key kullan
3. Apify run-sync timeout → 2dk+ işlerde async pattern'e geç
4. Code node'da return eksik veya yanlış format → null/undefined output
5. Google Sheets autoMapInputData — key adı kolon başlığıyla eşleşmiyor → boş satır
6. Wait node loop'ta silinip yeniden eklendi → webhookId değişir, loop kırılır
7. Array'i expression'a direkt koymak → [object Object], JSON.stringify kullan
8. OpenAI prompt'unda çift tırnak içinde çift tırnak → expression bozulur
9. Supabase batch insert'te rows boş array → 400 hatası, önce kontrol et
10. Schedule Trigger timezone — n8n instance UTC'de, Türkiye UTC+3
11. continueOnFail ≠ continueErrorOutput — ikincisi hata branch'i açar, birincisi açmaz
12. SplitInBatches loop'ta $('önceki node') — loop index'ine göre değişir, dikkatli kullan

ÖNEMLİ NOTLAR

  • Code node çıktısı her zaman [{ json: {...} }] formatında olmalı
  • n8n expression'larında Luxon (tarih), lodash ve temel JS kullanılabilir
  • HTTP Request node'da JSON body gönderirken Content-Type: application/json header'ı ekle
  • continueOnFail ile continueErrorOutput farklı davranır — hata yönetiminde ikincisini tercih et
  • Supabase'de Prefer: return=representation insert sonrası eklenen satırı döndürür
  • Apify dataset URL'inde clean=true boş/null kayıtları filtreler
  • n8n Cloud'da timeout 1 dakika, self-hosted'da ayarlanabilir