{ "name": "Auto categorize wordpress template", "tags": [], "nodes": [ { "id": "2017403c-7496-48f8-a487-8a017c7adfe3", "name": "When clicking ‘Test workflow’", "type": "n8n-nodes-base.manualTrigger", "position": [ 680, 320 ], "parameters": {}, "typeVersion": 1 }, { "id": "82ff288f-4234-4192-9046-33e5ffee5264", "name": "Wordpress", "type": "n8n-nodes-base.wordpress", "position": [ 1500, 320 ], "parameters": { "resource": "post", "operation": "update", "postId": "={{ $json.id }}", "updateFields": { "categories": "={{ [$json.categoryId] }}" } }, "typeVersion": 1 }, { "id": "521deb22-62dd-4b5f-8b9a-aab9777821da", "name": "Sticky Note1", "type": "n8n-nodes-base.stickyNote", "position": [ 620, -100 ], "parameters": { "width": 504.88636363636317, "content": "## Source\n\nThe original workflow links to Keith Rumjahn’s case study and tutorial. Performance depends on your site, model service and collection size.\n\n[Case study](https://rumjahn.com/how-to-use-a-i-to-categorize-wordpress-posts-and-streamline-your-content-organization-process/) · [Video tutorial](https://www.youtube.com/watch?v=IvQioioVqhw)" }, "typeVersion": 1 }, { "id": "4090d827-f8cd-47ef-ad4f-654ee58216f6", "name": "Sticky Note", "type": "n8n-nodes-base.stickyNote", "position": [ 860, 180 ], "parameters": { "color": 3, "width": 188.14814814814804, "height": 327.3400673400663, "content": "### Get wordpress posts\n\nTurn off return all if you're running into issues.\n" }, "typeVersion": 1 }, { "id": "71585d54-fdcc-42a5-8a0e-0fac3adc1809", "name": "Sticky Note2", "type": "n8n-nodes-base.stickyNote", "position": [ 1080, 80 ], "parameters": { "color": 4, "width": 315.1464152082392, "height": 416.90235690235625, "content": "## Existing WordPress categories\n\nSet Category Taxonomy to a JSON array of real existing category IDs and names before running. The default [] fails validation. All titles are classified before any update; one invalid classification stops the whole write phase. Each update replaces the existing category list with exactly one configured ID. Source model default is made explicit as current gpt-5-mini; native model transport allows two additional transient retries. No workflow-level retries. OpenAI lists December 11, 2026 as its shutdown date: select and recheck an available model in both formats before that date. https://developers.openai.com/api/docs/deprecations/ Review your taxonomy and keep the site stable while listing pages. See README." }, "typeVersion": 1 }, { "id": "29354054-8600-4e45-99d0-6f30f779a505", "name": "Sticky Note3", "type": "n8n-nodes-base.stickyNote", "position": [ 1480, 240 ], "parameters": { "color": 5, "width": 171.64983164983155, "height": 269.59595959595947, "content": "### Update category" }, "typeVersion": 1 }, { "id": "d9fe6289-6b97-4830-80aa-754ac4d4b3e0", "name": "Get All Wordpress Posts", "type": "n8n-nodes-base.wordpress", "position": [ 900, 320 ], "parameters": { "resource": "post", "operation": "getAll", "returnAll": true, "options": {} }, "typeVersion": 1, "alwaysOutputData": true }, { "id": "ed40bf13-8294-4b4e-a8b6-5749989d3420", "name": "OpenAI Chat Model", "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi", "position": [ 1080, 540 ], "parameters": { "model": "gpt-5-mini", "options": { "maxRetries": 2 } }, "typeVersion": 1 }, { "id": "dafeb935-532e-4067-9dfb-7e9a6bbc4e5a", "name": "Classify All Post Titles", "type": "@n8n/n8n-nodes-langchain.chainLlm", "position": [ 1100, 320 ], "parameters": { "promptType": "define", "text": "=You are an expert content strategist and taxonomy specialist with extensive experience in blog categorization and content organization.\n\nI will provide you with a blog post's title. Your task is to assign ONE primary category ID from this fixed list:\n\n{{ $json.categoryList }}\n\nAnalyze the title and return only the single most relevant category ID number that best represents the main focus of the post. While a post might touch on multiple topics, select the dominant theme that would be most useful for navigation purposes.\n\n{{ $json.title }}\n\nOutput only the category number", "messages": { "messageValues": [ { "type": "SystemMessagePromptTemplate", "message": "You are a helpful assistant" } ] }, "batching": { "batchSize": 1, "delayBetweenBatches": 0 } }, "typeVersion": 1.7 }, { "id": "wordpress-category-settings", "name": "Set Category Taxonomy", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ -400, 0 ], "parameters": { "assignments": { "assignments": [ { "id": "taxonomy-json", "name": "taxonomyJson", "type": "string", "value": "[]" } ] }, "options": {} } }, { "id": "wordpress-category-taxonomy", "name": "Validate Category Taxonomy", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ -200, 0 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "function validateTaxonomy(taxonomy) {\n if (!Array.isArray(taxonomy) || !taxonomy.length) throw new Error('Provide a nonempty category taxonomy.');\n const ids = new Set();\n return taxonomy.map(category => {\n if (!category || !Number.isSafeInteger(category.id) || category.id < 1 || typeof category.name !== 'string' || !category.name.trim()) {\n throw new Error('Each category needs a positive integer ID and nonempty name.');\n }\n if (ids.has(category.id)) throw new Error('Category IDs must be unique.');\n ids.add(category.id);\n return { id: category.id, name: category.name.trim() };\n });\n}\n\nconst prepare = function prepareTaxonomy({ taxonomyJson }) {\n if (typeof taxonomyJson !== 'string') throw new Error('Provide the category taxonomy as JSON text.');\n let parsed;\n try { parsed = JSON.parse(taxonomyJson); } catch { throw new Error('The category taxonomy must be valid JSON.'); }\n return { taxonomy: validateTaxonomy(parsed) };\n}\n\nreturn [{json:prepare($input.first().json)}];\n" } }, { "id": "wordpress-post-titles", "name": "Prepare Post Titles", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 220, 0 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "function normalizePosts(posts) {\n if (!Array.isArray(posts)) throw new Error('WordPress must return a post array.');\n const ids = new Set();\n return posts.map(post => {\n if (!post || !Number.isSafeInteger(post.id) || post.id < 1 || typeof post.title?.rendered !== 'string') throw new Error('Every WordPress post needs an ID and rendered title.');\n if (ids.has(post.id)) throw new Error('Repeated post IDs were returned; keep the site stable while listing and try again.');\n ids.add(post.id);\n return { id: post.id, title: post.title.rendered };\n });\n}\n\n\nconst raw=$input.all().map(item=>item.json);const posts=normalizePosts(raw.length===1&&Object.keys(raw[0]).length===0?[]:raw);const taxonomy=$('Validate Category Taxonomy').first().json.taxonomy;const categoryList=taxonomy.map(category=>category.id+' = '+category.name).join('\\n');return posts.length?posts.map(post=>({json:{...post,categoryList,empty:false}})):[{json:{empty:true}}];\n" } }, { "id": "wordpress-any-posts", "name": "No Posts Found", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [ 440, 0 ], "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict", "version": 2 }, "conditions": [ { "id": "empty-post-list", "leftValue": "={{ $json.empty }}", "rightValue": true, "operator": { "type": "boolean", "operation": "true", "singleValue": true } } ], "combinator": "and" }, "options": {} } }, { "id": "wordpress-validate-all-categories", "name": "Validate All Categories", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 900, 0 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "const validate = function validateCategory({ id, completion, allowedIds }) {\n if (typeof completion !== 'string' || !/^[1-9]\\d*$/.test(completion.trim())) throw new Error('The model must return exactly one positive category ID.');\n const categoryId = Number(completion.trim());\n if (!Number.isSafeInteger(categoryId) || !allowedIds.includes(categoryId)) throw new Error('The model selected a category outside the configured taxonomy.');\n if (!Number.isSafeInteger(id) || id < 1) throw new Error('The source post ID is invalid.');\n return { id, categoryId };\n}\n\nconst posts=$('Prepare Post Titles').all().map(item=>item.json),items=$input.all(),allowedIds=$('Validate Category Taxonomy').first().json.taxonomy.map(category=>category.id);if(items.length!==posts.length)throw new Error('Classification count differs from the post count.');return items.map((item,index)=>({json:validate({id:posts[index].id,completion:item.json.text,allowedIds}),pairedItem:{item:index}}));\n" } }, { "id": "wordpress-category-result", "name": "Categorized Post Summary", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1300, 0 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "const categories=$('Validate All Categories').all().map(item=>item.json);return [{json:{categories,count:categories.length}}];" } }, { "id": "wordpress-category-empty", "name": "Empty Post Summary", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 680, -200 ], "parameters": { "mode": "runOnceForAllItems", "jsCode": "return [{json:{categories:[],count:0}}];" } } ], "active": false, "pinData": {}, "settings": { "executionOrder": "v1" }, "connections": { "When clicking ‘Test workflow’": { "main": [ [ { "node": "Set Category Taxonomy", "type": "main", "index": 0 } ] ] }, "Set Category Taxonomy": { "main": [ [ { "node": "Validate Category Taxonomy", "type": "main", "index": 0 } ] ] }, "Validate Category Taxonomy": { "main": [ [ { "node": "Get All Wordpress Posts", "type": "main", "index": 0 } ] ] }, "Get All Wordpress Posts": { "main": [ [ { "node": "Prepare Post Titles", "type": "main", "index": 0 } ] ] }, "Prepare Post Titles": { "main": [ [ { "node": "No Posts Found", "type": "main", "index": 0 } ] ] }, "No Posts Found": { "main": [ [ { "node": "Empty Post Summary", "type": "main", "index": 0 } ], [ { "node": "Classify All Post Titles", "type": "main", "index": 0 } ] ] }, "Classify All Post Titles": { "main": [ [ { "node": "Validate All Categories", "type": "main", "index": 0 } ] ] }, "Validate All Categories": { "main": [ [ { "node": "Wordpress", "type": "main", "index": 0 } ] ] }, "Wordpress": { "main": [ [ { "node": "Categorized Post Summary", "type": "main", "index": 0 } ] ] }, "OpenAI Chat Model": { "ai_languageModel": [ [ { "node": "Classify All Post Titles", "type": "ai_languageModel", "index": 0 } ] ] } } }