İçeriğe geç

Publish blog posts from n8n

Bu içerik henüz dilinizde mevcut değil.

This recipe publishes a post from n8n with plain HTTP Request nodes. Every field is described on the blog API page; here is only the wiring.

  1. In the store’s admin open Settings → Apps → your app → Tokens and create a standing token. The app must hold write_blogs and write_media.
  2. In n8n create a Header Auth credential: name Authorization, value Bearer <your token>. Every HTTP node below uses it.
  3. Note the store’s host, for example shop.example.com.
Trigger → 1 Upload images (loop) → 2 Build the body → 3 PUT post → 4 Check warnings

An HTTP Request node that runs once per image item:

  • Method POST, URL https://shop.example.com/apps/media/uploads
  • Authentication: your Header Auth credential
  • Header Idempotency-Key: {{$json.key}}-{{$json.locale}}-img-{{$itemIndex}}-{{$now.toMillis()}}
  • Body: multipart-form-data, one parameter of type n8n Binary File, name file, input field data

The answer carries mediaAssetId and url. Keep both. The key above is new on every run on purpose: n8n rebuilds a multipart body with a new boundary each time, and the platform compares bytes, so a re-sent upload under an old key would be refused with 422. A repeated upload only costs an extra unused asset.

Keep mediaAssetId for the cover and banner, and url for images used inside the text.

A Code node assembles the request. Replace each {psimageN} placeholder of the document with <img src="{url}" alt="…"> using the matching upload’s url, and pick the cover and banner ids:

// Code node, "Run once for all items". The uploads arrive in the same order as the placeholders.
const uploads = $input.all().map(i => i.json);
const doc = $('Trigger').first().json;
let body = doc.body;
uploads.forEach((u, n) => {
body = body.replace(`{psimage${n + 1}}`, `<img src="${u.url}" alt="">`);
});
return [{ json: {
key: doc.key, locale: doc.locale, slug: doc.slug, title: doc.title,
metaTitle: doc.metaTitle, metaDescription: doc.metaDescription,
categories: String(doc.catIds).split(',').map(s => s.trim()).filter(Boolean),
body,
coverImage: { mediaAssetId: uploads[0].mediaAssetId },
bannerImage: { mediaAssetId: uploads[uploads.length - 1].mediaAssetId },
} }];

If a picture is already on a public https address you can skip the upload and send { "url": "…" } as coverImage or bannerImage, or leave an external <img> in the body: both are imported for you.

An HTTP Request node:

  • Method PUT, URL https://shop.example.com/apps/blog/posts
  • Header Idempotency-Key: {{$json.key}}-{{$json.locale}}-v1
  • Body (JSON): the object from step 2, plus status

Send the languages one after another: in the PUT node’s Options → Batching set Items per Batch to 1 and a Batch Interval of about 1500 ms, and put the store’s default language first. Two languages of one post written at the same moment can overwrite each other.

Use the same key for the Turkish and English documents of one folder (<brand>/<YYYY-MM-DD>/<n>) and a different locale for each; they become one post with two editions. Bump v1 to v2 when you deliberately re-send changed content. A retry of the same content keeps v1 and just gets the first answer back.

To mimic the legacy “not published first” step, send "status": "draft" on the first run and "published" later (same key, new Idempotency-Key). Send status with the store’s default-language edition.

Add an IF node on {{ $json.warnings.length > 0 }} and route that branch to your notification channel. Warnings are not failures, the post was saved, but they mean something was skipped: an unknown category or tag, a body image that could not be imported, a locale that was mapped. Turn on Retry on Fail for the PUT node: the stable Idempotency-Key makes a retry safe.

Paste this into n8n (Import from clipboard). It downloads one image, uploads it and creates a draft post. Replace shop.example.com, pick your Header Auth credential on both CIQRA nodes, and edit the values in Document.

{
"name": "CIQRA blog: upload + upsert",
"nodes": [
{
"parameters": {},
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [0, 0]
},
{
"parameters": {
"assignments": {
"assignments": [
{ "id": "1", "name": "key", "value": "acme/2026-10-15/1", "type": "string" },
{ "id": "2", "name": "locale", "value": "en", "type": "string" },
{ "id": "3", "name": "slug", "value": "hello-from-n8n", "type": "string" },
{ "id": "4", "name": "title", "value": "Hello from n8n", "type": "string" },
{ "id": "5", "name": "imageUrl", "value": "https://example.com/picture.jpg", "type": "string" }
]
}
},
"name": "Document",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [220, 0]
},
{
"parameters": {
"url": "={{ $json.imageUrl }}",
"options": { "response": { "response": { "responseFormat": "file" } } }
},
"name": "Download image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [440, 0]
},
{
"parameters": {
"method": "POST",
"url": "https://shop.example.com/apps/media/uploads",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Idempotency-Key", "value": "={{ $('Document').item.json.key }}-img-{{ $now.toMillis() }}" }
]
},
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{ "parameterType": "formBinaryData", "name": "file", "inputDataFieldName": "data" }
]
}
},
"name": "Upload image",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [660, 0],
"credentials": { "httpHeaderAuth": { "id": "REPLACE_ME", "name": "CIQRA app token" } }
},
{
"parameters": {
"method": "PUT",
"url": "https://shop.example.com/apps/blog/posts",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "Idempotency-Key", "value": "={{ $('Document').item.json.key }}-{{ $('Document').item.json.locale }}-v1" }
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ key: $('Document').item.json.key, locale: $('Document').item.json.locale, slug: $('Document').item.json.slug, title: $('Document').item.json.title, body: '<p>Written by an automation.</p><img src=\"' + $json.url + '\" alt=\"\">', status: 'draft', coverImage: { mediaAssetId: $json.mediaAssetId } }) }}",
"options": {}
},
"name": "Upsert post",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [880, 0],
"retryOnFail": true,
"maxTries": 3,
"credentials": { "httpHeaderAuth": { "id": "REPLACE_ME", "name": "CIQRA app token" } }
}
],
"connections": {
"Manual Trigger": { "main": [[{ "node": "Document", "type": "main", "index": 0 }]] },
"Document": { "main": [[{ "node": "Download image", "type": "main", "index": 0 }]] },
"Download image": { "main": [[{ "node": "Upload image", "type": "main", "index": 0 }]] },
"Upload image": { "main": [[{ "node": "Upsert post", "type": "main", "index": 0 }]] }
}
}

The node parameter names follow n8n’s HTTP Request node v4.2. If your n8n version renames one, the import still gives you the right nodes in the right order, and the steps above say what each field must hold.