Skip to content

AI‑Assisted Content Drafting in Strapi: From LLM Generation to Human Review

Learn step‑by‑step how to integrate an LLM into Strapi, store AI‑generated drafts, use Revisionary for versioning, and build a review pipeline that keeps content safe and compliant.

By the Revisionary team · · 4 min read

MacBook Pro inside gray room
Photo by Blake Connally on Unsplash

AI can write first drafts in seconds, but publishing still needs human eyes. In Strapi you can combine an LLM endpoint, the Revisionary plugin, and Strapi’s built‑in review features to create a complete drafting pipeline: the LLM writes a draft, Revisionary records every change, editors approve or reject, and the final article is published with full auditability.

Why an AI‑assisted drafting pipeline matters

  • Speed – Generate outlines, meta data, or full articles in milliseconds.
  • Consistency – Centralise prompt templates so the brand voice stays uniform.
  • Governance – Keep every AI‑generated version in the audit log and allow a human to accept or reject before the content goes live.
  • Compliance – Traceability satisfies regulations that require human oversight of AI‑generated material.

The following guide shows how to build this pipeline with Strapi, Node.js, and the Revisionary plugin.

iridescent brain render on blue purple background
Photo by Milad Fakurian on Unsplash

1. Pick an LLM provider and secure the API key

The first step is to decide which large language model you will call. Most providers expose a simple HTTP / JSON endpoint (OpenAI, Anthropic, Cohere, etc.). Store the secret key in an environment variable so it never appears in source control.

bash
# .env file (never commit this file)
LLM_API_KEY=sk‑your‑secret‑key
LLM_ENDPOINT=https://api.openai.com/v1/chat/completions

2. Model the content type for AI drafts

Create a collection type called ai-draft (or extend your existing article type). The key fields are:

FieldTypeDescription
titleStringThe headline the LLM suggests.
bodyRich TextFull article content generated by the LLM.
promptTemplateJSONThe prompt used for this draft – stored for reproducibility.
statusEnumeration (draft, review, published)Workflow state.
authorRelation → admin::userWho triggered the generation.

Because Revisionary tracks every version of a content entry, you do not need extra tables to keep old drafts – the plugin records field‑level differences automatically.

3. Add a custom controller to call the LLM

Create a new API route that accepts a prompt, calls the LLM, and creates an ai-draft entry with the result. Place the code in src/api/ai-draft/controllers/generate.js.

javascript
// src/api/ai-draft/controllers/generate.js
'use strict';
const fetch = require('node-fetch');

module.exports = {
  async createDraft(ctx) {
    const { prompt, templateId } = ctx.request.body;
    if (!prompt) return ctx.badRequest('Prompt is required');

    // Build the request body for the LLM (OpenAI example)
    const requestBody = {
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: prompt }],
      temperature: 0.7,
    };

    const response = await fetch(process.env.LLM_ENDPOINT, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.LLM_API_KEY}`,
      },
      body: JSON.stringify(requestBody),
    });

    if (!response.ok) {
      const err = await response.text();
      return ctx.internalServerError('LLM request failed: ' + err);
    }

    const data = await response.json();
    const generated = data.choices?.[0]?.message?.content || '';

    // Create the draft entry – Revisionary will record the version automatically
    const draft = await strapi.entityService.create('api::ai-draft.ai-draft', {
      data: {
        title: 'AI draft – ' + new Date().toISOString().slice(0, 10),
        body: generated,
        promptTemplate: { prompt, templateId },
        status: 'draft',
        author: ctx.state.user.id,
      },
    });

    ctx.send({ draftId: draft.id, message: 'Draft created' });
  },
};

Route definition

Add the route in src/api/ai-draft/routes/generate.js:

javascript
module.exports = {
  routes: [
    {
      method: 'POST',
      path: '/ai-draft/generate',
      handler: 'generate.createDraft',
      config: {
        policies: [], // add auth policy if needed
        middlewares: [],
      },
    },
  ],
};

Now a front‑end can POST a prompt to /api/ai-draft/generate and receive the newly created draft ID.

4. Use Revisionary to keep every AI version

When the LLM is called again for the same article, you can update the existing entry. Revisionary will store the previous version, the field‑level diff, and the user who performed the change. This gives you a complete audit trail without extra code.

5. Build a review space for editors

Strapi’s Review feature (documented at /docs/everyday-use/review) provides a queue of entries whose status is set to review. Create a simple lifecycle hook that moves a draft to review after generation:

javascript
// src/api/ai-draft/content-types/ai-draft/lifecycles.js
module.exports = {
  async afterCreate(event) {
    const { result } = event;
    // If the draft was just generated, push it to the review queue
    await strapi.entityService.update('api::ai-draft.ai-draft', result.id, {
      data: { status: 'review' },
    });
  },
};

Editors open the Review space, see the draft, and can:

  • Accept – change status to published (or move to a regular article type).
  • Reject – keep the draft in draft state, add comments, or delete it.

Because Revisionary records every status change, you can later generate a compliance report that shows who approved each AI‑generated article.

6. Publish the final content

When an editor clicks Publish in Strapi, the entry’s status becomes published and the standard publishing workflow runs (webhooks, cache invalidation, etc.). The article now appears in the public API.

If you prefer to keep the AI draft separate from the final article, you can copy the fields into a new article entry:

javascript
async function promoteToArticle(draftId) {
  const draft = await strapi.entityService.findOne('api::ai-draft.ai-draft', draftId, {
    populate: '*',
  });
  await strapi.entityService.create('api::article.article', {
    data: {
      title: draft.title,
      body: draft.body,
      author: draft.author,
      // any additional mapping …
    },
  });
  // Mark draft as archived
  await strapi.entityService.update('api::ai-draft.ai-draft', draftId, {
    data: { status: 'archived' },
  });
}

7. Governance: audit, traceability, and compliance

Audit log

Revisionary writes every action—creation, update, restore, status change—to Strapi’s Audit log (/docs/reference/audit). This satisfies many regulatory requirements that demand a tamper‑evident record of AI‑generated content.

Prompt provenance

Storing the exact prompt (promptTemplate field) alongside the generated text lets you reproduce the output later, a best practice for the EU AI Act and similar frameworks.

Retention policies

Configure how long autosaves and version history are kept in /docs/settings/retention. For high‑risk publishing, you might retain drafts for 90 days; for low‑risk blogs, 30 days is enough.

Role‑based permissions

Limit who can trigger AI generation and who can approve it:

  • Content creator – permission to call /ai-draft/generate.
  • Editor – permission to change status from review to published.
  • Compliance officer – read‑only access to the audit log. Set these permissions under Settings → Roles → Plugins → Revisionary (/docs/getting-started/permissions).

8. Tips and best practices

PracticeReason
Version every promptGuarantees reproducibility and helps debug poor outputs.
Rate‑limit LLM callsPrevents runaway costs and protects the API from abuse.
Add a “human‑in‑the‑loop” flagStore a boolean humanApproved that must be true before publishing.
Sanitise AI outputStrip dangerous HTML tags or scripts before saving to the database.
Monitor token usageLog the number of tokens consumed per request to track expenses.
Test prompts in isolationUse a small script to iterate over variations before committing them to production.
a robot with a light saber
Photo by Growtika on Unsplash

9. Conclusion

Integrating an LLM into Strapi is straightforward: expose a generation endpoint, store the result in a versioned content type, and let Revisionary handle the audit trail. By wiring the draft into Strapi’s Review space and applying role‑based permissions, you get a safe, compliant pipeline where AI boosts productivity without sacrificing editorial control.


For teams that need guaranteed version history and a built‑in review queue, the Revisionary plugin provides the missing pieces for a production‑grade AI drafting workflow.

TagsAIcontent workflowStrapiLLMRevisionaryheadless CMS

The Revisionary team

Developers who build Revisionary, a version history plugin for Strapi 5. We write about Strapi, web development, AI and application security.

This article was written with the help of AI.

All articles