Интеграция WordPress и n8n: черновики, медиа и публикация без дублей ¶
Обновлено: 2026-05-30
Импортируйте JSON в n8n, замените credentials, домены, IDs, токены, callback URL, лимиты и production-политики под вашу инфраструктуру.
Проблема: контент-команда хочет автоматизировать публикации, но прямой create post из n8n быстро создаёт дубли, теряет featured image, публикует сырой текст и не показывает редактору, что именно ушло в WordPress.
Решение: интеграция WordPress и n8n должна собирать стабильный slug, искать существующий пост, загружать медиа отдельно, создавать draft или update, а публикацию оставлять редактору или отдельному approval-шагу. Такой подход закрывает не демо-сценарий, а реальную production-боль: повторы, нестабильный mapping, API-ошибки, секреты, лимиты и понятный audit trail.
Проблема: почему простая интеграция ломается в production ¶
Автоматизация ценна только тогда, когда она даёт предсказуемый результат при повторе события, изменении полей, временной ошибке API и ручной правке на стороне сервиса. Поэтому здесь важны не только credentials и HTTP Request, но и контракт данных, ключ дедупликации, проверка статуса и понятный журнал.
Для этой страницы основной объект — WordPress draft post. Входной контракт должен явно фиксировать title, slug, status, category_ids, featured_media, external_content_id, canonical_url. Если эти поля приходят нестабильно, workflow начинает угадывать и создаёт дубли, неверные отчёты или записи без владельца.
Надёжная связка через n8n строится вокруг детерминированных проверок: сначала validation и idempotency, затем запрос во внешний API, затем запись результата в CMS/CRM/таблицу/аналитику и alert, если бизнес-действие не завершилось.
Архитектура workflow для n8n ¶
| Блок | Задача | Production-проверка |
|---|---|---|
| Content trigger | получает brief, markdown или запись из Notion | есть external_content_id и title |
| Normalize post | собирает slug, статус и meta | slug стабилен, status только draft/pending |
| Find existing post | ищет по external_id или slug | нет дубля при повторном запуске |
| Upload media | загружает featured image отдельно | есть media_id и alt text |
| Create or update draft | создаёт или обновляет пост через REST API | не публикует без approval |
| Editor audit | возвращает ссылку на черновик | редактор видит post_id и статус |
Такой workflow удобно сопровождать: mapping, API-запрос, retry, callback и human-readable audit не смешиваются в одной ноде.
Контракт входных данных ¶
{
"external_content_id": "brief-2026-05-30-n8n-crm",
"title": "Как связать n8n и CRM без дублей",
"slug": "n8n-crm-integration-without-duplicates",
"excerpt": "Практический материал для интеграторов n8n и CRM.",
"markdown": "# Как связать n8n и CRM\n\nТекст статьи...",
"status": "draft",
"categories": [
12,
18
],
"tags": [
"n8n",
"crm",
"automation"
],
"featured_image_url": "https://example.ru/images/n8n-crm.png",
"canonical_url": "https://nodbot.ru/workflows/webhook-idempotency-to-postgres/"
}Payload можно расширять, но нельзя делать обязательные поля “по настроению”. Если источник не передал внешний ID, ключ объекта, получателя или период отчёта, workflow должен остановиться с понятной ошибкой до записи или отправки.
Code Node: нормализация, mapping и guard-условия ¶
const src = $json.body ?? $json;
const title = String(src.title ?? '').trim();
if (title.length < 10) throw new Error('WordPress title is too short');
const externalId = String(src.external_content_id ?? '').trim();
if (!externalId) throw new Error('external_content_id is required for idempotency');
const slugBase = String(src.slug ?? title)
.toLowerCase()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9а-яё]+/gi, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 90);
const status = ['draft', 'pending'].includes(src.status) ? src.status : 'draft';
const categories = Array.isArray(src.categories) ? src.categories.filter(Number.isFinite) : [];
return [{
json: {
action: 'upsert_wordpress_draft',
idempotency_key: `wordpress:${externalId}`,
lookup: { meta_key: 'nodbot_external_content_id', meta_value: externalId, slug: slugBase },
post: {
title,
slug: slugBase,
status,
excerpt: String(src.excerpt ?? '').trim(),
content_markdown: String(src.markdown ?? '').trim(),
categories,
tags: src.tags ?? [],
meta: { nodbot_external_content_id: externalId, canonical_url: src.canonical_url ?? '' }
},
media: { featured_image_url: src.featured_image_url ?? '' }
}
}];Этот скрипт n8n приводит данные к стабильному контракту, формирует idempotency key и не пропускает опасный payload дальше по цепочке.
Готовый workflow JSON: скачать и импортировать ¶
В архиве страницы есть импортируемый workflow JSON и тестовый payload. После импорта замените credentials, домены, IDs, callback URL, лимиты и правила доступа. Не запускайте сценарий на production-данных, пока не проверены повторы, пустые значения и ошибки API.
{
"name": "Nodbot - WordPress draft upsert with media and approval",
"nodes": [
{
"name": "Content trigger",
"type": "n8n-node",
"purpose": "получает brief, markdown или запись из Notion"
},
{
"name": "Normalize post",
"type": "n8n-node",
"purpose": "собирает slug, статус и meta"
},
{
"name": "Find existing post",
"type": "n8n-node",
"purpose": "ищет по external_id или slug"
},
{
"name": "Upload media",
"type": "n8n-node",
"purpose": "загружает featured image отдельно"
},
{
"name": "Create or update draft",
"type": "n8n-node",
"purpose": "создаёт или обновляет пост через REST API"
},
{
"name": "Editor audit",
"type": "n8n-node",
"purpose": "возвращает ссылку на черновик"
}
],
"connections": "Content trigger → Normalize post → Find existing post → Upload media → Create or update draft → Editor audit"
}Пошаговая настройка связки ¶
- Создайте Application Password или OAuth-доступ для WordPress REST API с минимальными правами на posts/media.
- Добавьте meta field для external_content_id, чтобы workflow мог искать уже созданный черновик.
- Разделите создание draft и публикацию: production-сценарий не должен сразу ставить status=publish без approval.
- Проверьте загрузку featured media и alt text отдельно от создания post.
- Записывайте post_id, slug, editor_url и внешний ID обратно в Notion, Google Sheets или CRM.
Что проверить после импорта workflow
Откройте каждую ноду, замените credentials и IDs, включите dry-run там, где доступно, затем выполните сценарий на тестовом объекте. Для внешних API добавьте rate limit, alert и отдельную тестовую сущность.
Тесты перед production ¶
Минимальный smoke test:
curl -X POST "https://YOUR-N8N-DOMAIN/webhook/wordpress-draft-upsert-n8n" -H "Content-Type: application/json" --data @integration-wordpress-n8n-publish-drafts-payload.json- повторный payload с тем же external_content_id
- заголовок без slug
- битая featured_image_url
- status=publish во входном payload
- ошибка WordPress 401/403/429
Отдельно проверьте, что retry n8n не создаёт повторную запись или отправку. Для критичных действий используйте durable storage: Postgres, CRM custom field, CMS meta, audit table или другой слой с уникальным ключом.
Production-риски ¶
- Workflow публикует сырой AI-текст сразу в publish.
- Дедупликация делается только по title, который редактор может изменить.
- Изображение загружается, но не привязывается как featured_media.
- Application Password хранится в Code Node или публичном JSON.
- HTML очищается WordPress-фильтрами и ломает блоки кода.
Полезные ссылки и смежные материалы ¶
Внутренняя перелинковка помогает перейти от общего integration-гайда к готовым workflow, а внешние ссылки ведут на официальную документацию API и n8n-нод.
Критерии готовности ¶
- Повторный запуск обновляет существующий draft, а не создаёт дубль.
- post_id и slug возвращаются в источник контента.
- featured media и alt text проверены на тестовой статье.
- Публикация отделена от генерации и требует approval.
- Ошибки REST API уходят в alert или DLQ.