AI Writer for CKEditor 

Extension key

ok_ai_writer

Package name

oliverkroener/ok-ai-writer

Version

2.0

Language

en

Author

Oliver Kroener <ok@oliver-kroener.de>

License

This document is published under the Open Publication License.

Rendered

Wed, 18 Feb 2026 21:24:35 +0000


AI Writer adds AI-powered text generation, translation, and placeholder text insertion directly into CKEditor in the TYPO3 backend. Editors can generate SEO-optimized HTML content, translate into 7 languages, and insert Lorem Ipsum — all using Azure OpenAI or OpenAI (ChatGPT) without leaving the rich text editor. Supports per-site configuration with encrypted credential storage via a dedicated backend module.

Introduction 

Learn what AI Writer does, its features, and system requirements.

Installation 

Install the extension via Composer and activate it in your TYPO3 instance.

Configuration 

Set up extension settings, register CKEditor toolbar buttons, and configure Azure OpenAI or OpenAI API credentials.

Usage 

Generate and refine AI content, translate into 7 languages, or insert Lorem Ipsum placeholder text.

Architecture 

Understand the request flow, file structure, provider modes, and extension internals.

Contact 

Get in touch with the author for support, questions, or contributions.

Introduction 

What is AI Writer? 

AI Writer (ok_ai_writer) is a TYPO3 extension that brings AI-powered text generation directly into CKEditor. Editors can generate, preview, and iteratively refine SEO-optimized HTML content using Azure OpenAI or OpenAI (ChatGPT) — all without leaving the rich text editor.

The extension provides three CKEditor 5 plugins:

AI Text Generator
Opens a chat-style dialog where editors describe the content they need. The AI generates well-structured HTML that can be refined through follow-up prompts before inserting it into the editor.
AI Translate
Translates the entire editor content into a selected target language while preserving all HTML structure and formatting. Supports 7 languages.
Lorem Ipsum
Opens a dialog to select the number of paragraphs (1–20) and inserts Lorem Ipsum placeholder text — useful during development and layout prototyping.

Features 

  • Conversational AI dialog — Chat-style interface with full conversation history for iterative content refinement.
  • SEO-optimized output — The AI generates well-structured HTML with semantic headings (<h2><h4>) and paragraphs.
  • Dual provider support — Works with both Azure OpenAI and OpenAI (ChatGPT) APIs. Switch between providers via extension configuration.
  • Per-site configuration — Dedicated backend module (Web > AI Writer) allows administrators to configure different API credentials per TYPO3 site, with automatic fallback to global extension configuration.
  • Encrypted credential storage — Per-site API keys are encrypted at rest using Sodium encryption derived from TYPO3's encryptionKey.
  • Centralized or per-user credentials — API credentials can be configured server-side by an administrator (displayed blinded to editors) or per-user in the browser's localStorage when developer mode is enabled.
  • Token usage tracking — Displays cumulative input/output token counts per session so editors stay aware of API consumption.
  • AI translation — Translate entire editor content into 7 languages (Deutsch, English US/UK, Español, Français, Italiano, Türkçe) while preserving HTML structure.
  • Dark mode support — All dialogs automatically adapt to the TYPO3 backend's dark mode setting, detecting the data-color-scheme attribute with prefers-color-scheme media query fallback.
  • Localized UI — Ships with English and German translations.
  • Lorem Ipsum helper — Additional CKEditor plugin for quick placeholder text insertion (1–20 paragraphs) during development.
  • Backend proxy — API requests are routed through the TYPO3 backend, avoiding browser CORS restrictions.

Requirements 

Component Version
TYPO3 12.4 LTS, 13.x, or 14.x
PHP 8.1+
CKEditor typo3/cms-rte-ckeditor ^12.4 || ^13 || ^14
AI Provider Azure OpenAI or OpenAI (ChatGPT)

Installation 

Install via Composer 

composer require oliverkroener/ok-ai-writer
Copied!

This pulls the extension and its dependencies into your TYPO3 project.

Install from a local path 

If you develop the extension locally (e.g. in a packages/ directory), add it as a path repository in your project's composer.json:

{
    "repositories": [
        {
            "type": "path",
            "url": "packages/*"
        }
    ],
    "require": {
        "oliverkroener/ok-ai-writer": "@dev"
    }
}
Copied!

Then run:

composer update oliverkroener/ok-ai-writer
Copied!

Activate the extension 

After installing, set up and activate the extension:

vendor/bin/typo3 extension:setup
vendor/bin/typo3 database:updateschema
vendor/bin/typo3 cache:flush
Copied!

Verify the installation 

After activation, confirm the extension is loaded:

vendor/bin/typo3 extension:list | grep ok_ai_writer
Copied!

You should see ok_ai_writer in the output. The CKEditor plugins are now available for configuration.

Configuration 

The extension supports two AI providers — Azure OpenAI and OpenAI (ChatGPT) — and offers two levels of configuration: a global extension configuration (fallback) and a per-site configuration via a dedicated backend module.

Global extension configuration 

Open Admin Tools > Settings > Extension Configuration > ok_ai_writer to set the global defaults:

devMode

devMode
type

boolean

Default

false

When enabled, editors can override API credentials in their browser (localStorage). When disabled, only the server-side credentials configured below are used.

mode

mode
type

select

Default

azure

The AI provider to use.

azure
Azure OpenAI — uses the api-key header for authentication.
openai
OpenAI (ChatGPT) — uses Authorization: Bearer header and sends the model parameter in the request body.

apiUrl

apiUrl
type

string

Default

(empty)

The API endpoint URL.

Azure example:

https://<resource>.openai.azure.com/openai/deployments/<model>/chat/completions?api-version=2024-02-01
Copied!

OpenAI example:

https://api.openai.com/v1/chat/completions
Copied!

apiKey

apiKey
type

string

Default

(empty)

The API key for the selected provider. This value is displayed blinded (masked) to editors in the CKEditor dialog — they can see that credentials are configured but cannot read the actual value.

model

model
type

string

Default

gpt-4o

The model to use in OpenAI mode (e.g. gpt-4o, gpt-4, gpt-3.5-turbo). This setting is ignored in Azure mode, where the model is part of the endpoint URL.

Per-site configuration (backend module) 

The extension provides a dedicated backend module at Web > AI Writer that allows administrators to configure different API credentials for each TYPO3 site. This is useful for multi-site setups where each site may use a different AI provider or API key.

To configure per-site credentials:

  1. Navigate to Web > AI Writer in the TYPO3 backend.
  2. Select a page from the page tree. The module automatically resolves the site root.
  3. Fill in the configuration fields (Developer Mode, API Mode, API URL, API Key, Model).
  4. Click Save.

Configuration resolution order:

  1. Per-site configuration — if a record exists in tx_okaiwriter_configuration for the current site root page ID and has a non-empty apiUrl, it is used.
  2. Global extension configuration — used as fallback when no per-site configuration is found.

Encrypted API key storage 

Per-site API keys are stored encrypted in the database using Sodium encryption. The encryption key is derived from TYPO3's $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'].

Developer mode 

When devMode is enabled:

  • Editors see a full settings panel in the AI dialog with editable fields for mode, endpoint URL, API key, and model.
  • Client-entered credentials are saved in localStorage and override the server-side configuration.
  • Server-configured values are shown as blinded hints below each field (e.g. http****************2001).
  • If no client credentials are entered, the server-side config is used as fallback.

Step 1: Register the CKEditor plugins 

The extension ships an RTE preset at EXT:ok_ai_writer/Configuration/RTE/AiWriter.yaml that imports both plugins. You can either use it directly or import it into your own preset.

Option B: Use the bundled RTE preset 

The extension registers an RTE preset called ok_ai_writer. To use it directly, add to your Page TSconfig:

RTE.default.preset = ok_ai_writer
Copied!

Toolbar button names 

Button name Description
aiText AI Text Generator (sparkle icon)
aiTranslate AI Translate (translate icon)
loremIpsum Lorem Ipsum placeholder text (text icon)

Step 2: Set up your AI provider 

Azure OpenAI 

  1. Create an Azure OpenAI resource in the Azure Portal.
  2. Deploy a model (e.g. gpt-4, gpt-4o, gpt-35-turbo).
  3. Copy the Endpoint and Key from the resource's "Keys and Endpoint" page.
  4. In the extension configuration (global or per-site), set:

    • mode = azure
    • apiUrl = https://<resource>.openai.azure.com/openai/deployments/<model>/chat/completions?api-version=2024-02-01
    • apiKey = your Azure API key

OpenAI (ChatGPT) 

  1. Create an account at platform.openai.com and generate an API key.
  2. In the extension configuration (global or per-site), set:

    • mode = openai
    • apiUrl = https://api.openai.com/v1/chat/completions
    • apiKey = your sk-... API key
    • model = gpt-4o (or another available model)

Usage 

AI Text Generator 

The AI Text Generator lets you create and iteratively refine content through a conversational interface.

Generating content 

  1. Place your cursor in a CKEditor field where you want to insert content.
  2. Click the AI Text toolbar button (sparkle icon).
  3. In the dialog, type a prompt describing the content you need, e.g. "Write an introduction about sustainable urban planning".
  4. Press Enter or click the send button.
  5. The generated HTML content appears in the preview area.

Refining content 

After the initial generation, you can refine the result with follow-up instructions:

  1. Type a follow-up instruction in the input field, e.g. "make it shorter", "more formal", or "add a call to action".
  2. The AI retains the full conversation context and adjusts the content accordingly.
  3. Repeat until you're satisfied with the result.

Inserting into the editor 

  1. Click Insert into Editor to insert the final content at the cursor
    position in CKEditor.

The dialog closes automatically and focus returns to the editor.

Keyboard shortcuts 

Shortcut Action
Enter Send prompt / generate text
Shift + Enter New line in the prompt input
Escape Close the dialog

Token tracking 

The dialog displays cumulative input and output token counts at the bottom right corner. This helps editors monitor API usage during a session.

AI Translate 

The AI Translate plugin translates the entire editor content into a selected target language while preserving all HTML structure and formatting.

Translating content 

  1. Ensure the CKEditor field contains the content you want to translate.
  2. Click the AI Translate toolbar button (globe icon).
  3. Select the target language from the language grid:

    • Deutsch
    • English (US)
    • English (UK)
    • Español
    • Français
    • Italiano
    • Türkçe
  4. The translation starts immediately. A loading indicator shows the progress.
  5. On success, the editor content is replaced with the translated version.

Lorem Ipsum 

The Lorem Ipsum plugin inserts placeholder text for layout prototyping.

  1. Place the cursor where you want the text inserted.
  2. Click the Lorem Ipsum toolbar button (text lines icon).
  3. In the dialog, select the number of paragraphs to insert (1–20, default 3).
  4. Click Insert or press Enter.

Architecture 

This section describes the internal architecture of the AI Writer extension for developers who want to understand, extend, or debug the extension.

Request flow 

Browser (CKEditor Plugin)
    │
    │  User triggers AI Text or AI Translate
    │
    ▼
TYPO3 AJAX Routes
    POST /typo3/ajax/ok-ai-writer/generate    (AI Text)
    POST /typo3/ajax/ok-ai-writer/translate    (AI Translate)
    Body: { messages[], siteRootPageId }         (production mode)
    Body: { endpoint, apikey, mode,              (dev mode — optional overrides)
            model, messages[], siteRootPageId }
    │
    ▼
AiTextController
    │  resolveCredentials() via ConfigurationService:
    │    1. Per-site DB config (tx_okaiwriter_configuration)
    │    2. Global extension config (fallback)
    │  In devMode: client values override resolved config
    │  Prepends system prompt (SEO writer or translator)
    │
    │  generateAction() — text generation (max 2000 tokens)
    │  translateAction() — translation (max 4000 tokens)
    │
    ├── mode=azure ──▶  Azure OpenAI API  (api-key header)
    │
    └── mode=openai ──▶  OpenAI API  (Bearer token + model in body)
    │
    │  Returns: choices[].message.content (HTML)
    │           usage.prompt_tokens / completion_tokens
    │
    ▼
Response flows back to CKEditor
    │  AI Text: displayed in preview, added to conversation history
    │  AI Translate: replaces editor content directly
    ▼
Editor clicks "Insert into Editor" (AI Text)
    Content inserted at cursor position
Copied!

Configuration resolution 

ConfigurationService.getConfiguration(siteRootPageId)
    │
    ├── siteRootPageId > 0
    │   └── AiWriterConfigurationRepository.findBySiteRootPageId()
    │       │  (tx_okaiwriter_configuration table)
    │       ├── row found with non-empty apiUrl → return per-site config
    │       └── no row or empty apiUrl → fall through
    │
    └── Global ExtensionConfiguration('ok_ai_writer')
        (ext_conf_template.txt values from settings.php)
Copied!

The AddLanguageLabels middleware resolves the current site root page ID from the request context (page module id parameter or form editing edit[table][uid] parameter) and passes it to the ConfigurationService to load the correct credentials for injection into the frontend JavaScript.

System prompts 

The controller prepends a system message (identical for both providers) that instructs the AI. Each action uses a tailored prompt:

AI Text generation:

  • Generate well-structured, SEO-optimized HTML content
  • Use semantic headings (<h2>, <h3>, <h4>) and paragraphs
  • Not include <h1> tags (the page already has one)
  • Not include markdown formatting, code fences, or explanations
  • Return only clean HTML

AI Translation:

  • Translate the provided HTML content to the requested language
  • Preserve all HTML tags, structure, attributes, and formatting exactly
  • Only translate the visible text content
  • Return only the translated HTML

Provider modes 

The controller supports two AI providers, selected via the mode extension configuration:

Azure OpenAI (mode = azure)
Authenticates via api-key HTTP header. The model is determined by the deployment name in the endpoint URL. No model parameter is sent in the request body.
OpenAI / ChatGPT (mode = openai)
Authenticates via Authorization: Bearer <key> header. The model parameter (e.g. gpt-4o) is included in the JSON request body.

Conversation mode 

The plugin supports two message modes:

Conversation mode (default)
The browser sends the full messages[] array (user + assistant turns). The controller prepends the system prompt and forwards the entire history to the AI API. This enables iterative refinement.
Legacy single-prompt mode
If no messages[] array is sent, the controller falls back to using a single prompt string. This mode exists for backwards compatibility.

Key files 

Classes/
├── Controller/
│   ├── AiTextController.php                  AJAX endpoint — proxies to AI API (generate + translate)
│   └── Backend/
│       └── ConfigurationController.php       Backend module — per-site config management
├── Domain/
│   └── Repository/
│       └── AiWriterConfigurationRepository.php  Per-site config DB layer (encrypted API keys)
├── Middleware/
│   └── AddLanguageLabels.php                 Injects XLIFF labels + site-aware config into backend JS
└── Service/
    ├── ConfigurationService.php              Config resolution (per-site → global fallback)
    └── EncryptionService.php                 Sodium encryption for API keys

Configuration/
├── Backend/
│   ├── AjaxRoutes.php                        Registers /ok-ai-writer/generate and /translate routes
│   └── Modules.php                           Registers Web > AI Writer backend module
├── Icons.php                                 Extension icon registration (SvgIconProvider)
├── JavaScriptModules.php                     ES module import map for CKEditor plugins
├── RequestMiddlewares.php                    Registers AddLanguageLabels middleware
├── RTE/
│   └── AiWriter.yaml                        CKEditor preset importing all three plugins
└── Services.yaml                             Symfony DI autowiring

Resources/
├── Private/
│   ├── Language/
│   │   ├── locallang.xlf                    English labels (CKEditor plugins)
│   │   ├── de.locallang.xlf                 German labels (CKEditor plugins)
│   │   ├── locallang_be_module.xlf          English labels (backend module)
│   │   └── de.locallang_be_module.xlf       German labels (backend module)
│   └── Templates/Backend/Configuration/
│       └── Edit.html                         Fluid template for backend module form
└── Public/
    ├── Icons/
    │   └── Extension.svg                    Extension icon
    └── JavaScript/
        ├── backend/
        │   └── form-dirty-check.js          Unsaved changes detection for backend module
        └── plugin/
            ├── ai-text.js                   CKEditor 5 AI Text plugin
            ├── ai-translate.js              CKEditor 5 AI Translate plugin
            └── lorem-ipsum.js               CKEditor 5 Lorem Ipsum plugin
Copied!

Component details 

AiTextController 

File

Classes/Controller/AiTextController.php

Routes

/ok-ai-writer/generate and /ok-ai-writer/translate (AJAX, POST)

Receives the conversation messages (or content to translate) from the browser. Resolves API credentials via ConfigurationService using the siteRootPageId from the request body. Prepends the appropriate system prompt, forwards to the configured AI provider via Guzzle HTTP client, and returns the JSON response.

  • Resolves credentials via ConfigurationService.getConfiguration() (per-site → global fallback)
  • In devMode: client-sent credentials override resolved config
  • Supports both Azure (api-key header) and OpenAI (Bearer token)
  • generateAction: accepts messages[] (conversation) or prompt (legacy), max_tokens: 2000
  • translateAction: accepts content (HTML) and language (target), max_tokens: 4000
  • Uses temperature: 0.3 for both actions
  • Timeout: 120 seconds
  • Returns API errors as HTTP 502 with the first 500 characters of the error response

ConfigurationController (backend module) 

File

Classes/Controller/Backend/ConfigurationController.php

Module

web_okaiwriter (Web > AI Writer)

Access

Admin only

Provides the per-site configuration management UI. Resolves the site root from the selected page, loads existing per-site configuration (if any), and renders a Fluid form for editing. On save, upserts the configuration record in the tx_okaiwriter_configuration table.

  • editAction: renders the configuration form with current values
  • saveAction: validates and saves per-site config, redirects back to edit
  • Uses page tree navigation component for page selection
  • Shows global fallback status when no per-site config exists
  • Warns when TYPO3 encryption key is missing

ConfigurationService 

File

Classes/Service/ConfigurationService.php

Resolves the effective AI Writer configuration by checking per-site DB config first, then falling back to global extension configuration.

EncryptionService 

File

Classes/Service/EncryptionService.php

Encrypts and decrypts API keys using Sodium (sodium_crypto_secretbox). Derives the encryption key from TYPO3's encryptionKey via sodium_crypto_generichash.

AiWriterConfigurationRepository 

File

Classes/Domain/Repository/AiWriterConfigurationRepository.php

Table

tx_okaiwriter_configuration

Database layer for per-site configuration. Supports find-by-site-root and upsert operations. API keys are encrypted before storage and decrypted on retrieval via EncryptionService.

AddLanguageLabels middleware 

File

Classes/Middleware/AddLanguageLabels.php

Stack

Backend middleware

Injects the extension's XLIFF translation labels into the TYPO3 backend page renderer so they are available via TYPO3.lang in JavaScript. Also injects site-aware extension configuration as inline settings (TYPO3.settings.ok_ai_writer) with blinded credential values.

Resolves the current site root page ID from the request context by checking the page module id parameter or form editing edit[table][uid] parameter, then looks up the pid for non-page records.

CKEditor plugins 

All three plugins are standard CKEditor 5 plugins registered via Configuration/JavaScriptModules.php and imported through the RTE preset YAML.

All three plugins detect the TYPO3 backend's dark mode setting via the data-color-scheme attribute on <html> (dark, light, or auto). For the auto value, they fall back to the browser's prefers-color-scheme media query. A shared getTheme() function returns the full color palette (backgrounds, text, borders, inputs, buttons, status indicators) used to style each dialog.

ai-text.js
Registers the aiText toolbar button. On click, creates a maximizable modal dialog with a chat-style interface. Handles conversation state, API calls via fetch(), token tracking, and content insertion into the editor.
ai-translate.js
Registers the aiTranslate toolbar button. On click, opens a compact dialog with a language selection grid (7 languages). Translates the full editor content via the /ok-ai-writer/translate route and replaces the editor content on success.
lorem-ipsum.js
Registers the loremIpsum toolbar button. On click, opens a dialog where the editor selects the number of paragraphs (1–20) to insert at the cursor position.

Localization 

The extension ships with two sets of language files:

CKEditor plugin labels:

File Language
Resources/Private/Language/locallang.xlf English
Resources/Private/Language/de.locallang.xlf German

Backend module labels:

File Language
Resources/Private/Language/locallang_be_module.xlf English
Resources/Private/Language/de.locallang_be_module.xlf German

CKEditor plugin labels are loaded into the backend via the AddLanguageLabels middleware and accessed in JavaScript through TYPO3.lang['label.key'].

To add a new translation, create a file named <language-code>.locallang.xlf (e.g. fr.locallang.xlf) following the XLIFF 1.2 format.

License 

This extension is licensed under the GNU General Public License v2.0 or later.

Author

Oliver Kroener

Email

ok@oliver-kroener.de

Website

oliver-kroener.de

Contact 

Author 

Oliver Kroeneroliver-kroener.de

Oliver is a software engineer and consultant specializing in Power Automate, AI, Microsoft Azure, TYPO3, and cloud solutions.

Support 

If you encounter issues or have questions about this extension:

Contributing 

Contributions are welcome. Please open a pull request or issue on GitHub.

License 

This extension is licensed under the GPL-2.0-or-later.