TinyMCE AI on-premises: editor-side integration

This page covers the editor-side configuration that connects TinyMCE to the on-premises AI service. It assumes:

For general framework setup (installing wrappers, component structure, server-side rendering (SSR) patterns), see the existing integration guides:

The on-premises AI integration adds the options documented below to the standard TinyMCE init configuration.

Required editor options

Option Description

plugins

Must include tinymceai.

toolbar

Include one or more of tinymceai-chat, tinymceai-review, tinymceai-quickactions.

tinymceai_service_url

The origin of the AI service (no trailing slash, no path), for example https://ai.yourcompany.com.

tinymceai_token_provider

A function returning Promise<{ token: string }>. See tinymceai_token_provider below.

Minimal example

The following vanilla JavaScript example contains every on-premises-specific option. The same init options apply identically inside the React, Vue, Angular, and Svelte wrapper components.

<!DOCTYPE html>
<html>
<head>
  <script src="/path/to/tinymce/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body>
  <textarea id="editor"><p>Hello world.</p></textarea>
  <script>
    tinymce.init({
      selector: '#editor',
      license_key: 'T8LK:...',
      plugins: 'tinymceai',
      toolbar: 'undo redo | bold italic | tinymceai-chat tinymceai-review tinymceai-quickactions',
      height: 500,
      tinymceai_service_url: 'https://ai.yourcompany.com',
      tinymceai_token_provider: () => {
        return fetch('/api/ai-token', {
          method: 'POST',
          credentials: 'include'
        })
          .then((r) => r.json())
          .then((data) => ({ token: data.token }));
      }
    });
  </script>
</body>
</html>

Replace /path/to/tinymce/ with the location of the self-hosted TinyMCE assets. See Self-hosted installation for download and setup instructions.

tinymceai_token_provider

A function that returns a Promise resolving to an object with a token property containing the JWT string.

Expected return shape
{ token: 'eyJhbGciOiJIUzI1NiIs...' }
Example provider
tinymceai_token_provider: () => {
  return fetch('/api/ai-token', { method: 'POST' })
    .then((r) => r.json())
    .then((data) => ({ token: data.token }));
}
Behavior Detail

Automatic refresh

The plugin calls the provider on initialization and again when the cached token nears expiry (60-second safety margin). Do not cache the JWT inside the provider.

Error handling

If the function rejects or the endpoint returns a non-OK response, the plugin surfaces an error in the editor UI.

Token lifetime

Tokens should be short-lived (5-15 minutes recommended). See JWT authentication for signing key, payload structure, and lifetime guidance.

Authenticating the token request

The tinymceai_token_provider fetches a JWT from the application back end. How that back end authenticates the browser request depends on the application architecture.

If the page and the token endpoint share an origin (or a parent domain), the browser sends session cookies automatically:

fetch('/api/ai-token', { method: 'POST', credentials: 'include' })

For cross-origin token endpoints, the back end must respond with Access-Control-Allow-Origin: <editor-origin> (not *) and Access-Control-Allow-Credentials: true, and the session cookie must be set with SameSite=None; Secure.

Bearer header

If the application already holds a session JWT (injected at render time, or from an auth library), forward it as a header:

fetch('/api/ai-token', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${sessionJwt}` }
})

This pattern avoids cookies entirely and works well for cross-origin setups.

Cross-origin requests to the AI service

When tinymceai_service_url points to a different origin from the page (the common production case), the AI service must return Cross-Origin Resource Sharing (CORS) headers permitting the editor origin. The service reads the ALLOWED_ORIGINS environment variable for this.

To verify CORS from a terminal:

curl -i -X OPTIONS https://ai.yourcompany.com/v1/conversations \
  -H 'Origin: https://app.yourcompany.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type'

The response should include Access-Control-Allow-Origin: https://app.yourcompany.com. If it shows * or no CORS header, update ALLOWED_ORIGINS on the AI service container and restart.

Content Security Policy (CSP)

If the application sets a Content-Security-Policy header, allow the AI service origin in connect-src:

Content-Security-Policy:
  connect-src 'self' https://ai.yourcompany.com;
  script-src 'self';

If using the Tiny CDN instead of self-hosted assets, also add https://cdn.tiny.cloud to script-src.

Common integration errors

Symptom Likely cause Fix

Editor loads but no AI buttons appear

plugins does not include tinymceai, or TinyMCE is version 7.x or earlier

Set plugins: 'tinymceai' and confirm the script URL uses /tinymce/8/. Verify the API key has the AI feature enabled.

POST /api/ai-token returns 401

The token endpoint rejects the fetch

Confirm the fetch sends the session cookie (credentials: 'include') or Authorization header that the back end expects.

AI responses hang then time out

Reverse proxy is buffering Server-Sent Events (SSE)

Disable proxy buffering. See Production deployment.

Browser console shows a CORS error on /v1/conversations

ALLOWED_ORIGINS does not include the editor origin

Update ALLOWED_ORIGINS and restart the AI service.

tinymceai_token_provider called in a tight loop

Token endpoint returns invalid JSON or non-200

Validate: curl -X POST http://localhost:3000/api/ai-token should return {"token":"eyJ..."} with HTTP 200.

For other issues, see Troubleshooting.