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.
Before you start
Section titled “Before you start”- In the store’s admin open Settings → Apps → your app → Tokens and create a standing token. The app must
hold
write_blogsandwrite_media. - In n8n create a Header Auth credential: name
Authorization, valueBearer <your token>. Every HTTP node below uses it. - Note the store’s host, for example
shop.example.com.
The flow
Section titled “The flow”Trigger → 1 Upload images (loop) → 2 Build the body → 3 PUT post → 4 Check warnings1. Upload each image
Section titled “1. Upload each image”An HTTP Request node that runs once per image item:
- Method
POST, URLhttps://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, namefile, input fielddata
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.
2. Build the body
Section titled “2. Build the body”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.
3. PUT the post
Section titled “3. PUT the post”An HTTP Request node:
- Method
PUT, URLhttps://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.
4. Check the warnings
Section titled “4. Check the warnings”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.
Importable starting point
Section titled “Importable starting point”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.