# How to Integrate Replit with TYPO3

- Tool: Replit
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with TYPO3, install and configure TYPO3's RESTful API extension (typo3/cms-headless or EXT:headless), generate an API token in TYPO3's backend, store it in Replit Secrets (lock icon 🔒) as TYPO3_API_TOKEN, and call the TYPO3 REST API with a Bearer auth header. TYPO3 is the dominant enterprise CMS in Germany and Central Europe, widely used in government and healthcare.

## TYPO3 REST API Integration from Replit

TYPO3 is the enterprise CMS of choice across German-speaking Europe and beyond — it powers a significant share of German government websites, healthcare portals, university sites, and large corporate web presences. For developers building integrations with TYPO3-powered organizations, programmatic access to TYPO3 content is a common requirement. Unlike WordPress or Ghost, TYPO3 does not ship with a REST API in its core — you need to install a headless extension.

The most widely used TYPO3 headless extension is EXT:headless (packaged as wirl-pl/headless on Packagist), which exposes TYPO3 page content as structured JSON. Another option is EXT:rest-api which provides a configurable REST API. The extension choice affects the API structure your Replit integration will work with. Organizations running TYPO3 for public-facing content often prefer EXT:headless for its clean JSON output; those using TYPO3 as a data platform may use the REST API extension.

TYPO3's content model is different from other CMS platforms. Content lives in a page tree structure — pages contain content elements (text, images, files, forms), and pages are organized in a hierarchical tree. The headless API exposes this structure as JSON, with page content as arrays of typed content elements. Understanding this model is essential for correctly interpreting the API responses your Replit integration receives.

## Before you start

- A Replit account with a Node.js or Python Repl ready
- A TYPO3 installation (version 11.5 LTS or 12.x recommended) with admin access
- A TYPO3 headless extension installed (EXT:headless via Composer: wirl-pl/headless)
- A configured API endpoint URL and authentication credentials for your TYPO3 setup

## Step-by-step guide

### 1. Install and Configure a TYPO3 Headless Extension

Log into your TYPO3 server via SSH or your hosting control panel. TYPO3 extensions are installed via Composer. Install the EXT:headless extension by running: composer require wirl-pl/headless. Then activate it in TYPO3's Extension Manager (Admin Tools → Extensions).

Alternatively, if your TYPO3 installation uses the TYPO3 Extension Repository (TER), search for 'headless' in the Extensions module and install EXT:headless from there.

After installation, create a Site Configuration that includes headless settings. In TYPO3's Sites module (Configuration → Sites), edit your site and add the headless API base path — typically /api/. This makes page content available at /api/{pageUid} or /api/?id={pageUid}.

For authentication, EXT:headless supports API key authentication. Create an API key in the TYPO3 Backend by navigating to System → API Keys (or the equivalent module provided by the headless extension). Note your API key and the header format — typically X-Auth-Token or Authorization: Bearer.

For TYPO3's built-in REST API (without a headless extension), some older configurations use the core TYPO3 API which accepts user credentials via HTTP Basic Auth. Check your TYPO3 version's documentation for the exact API endpoint structure.

If you do not have direct server access and cannot install extensions, contact your TYPO3 administrator to request headless API access and ask for the API base URL and authentication credentials.

```
# Install EXT:headless via Composer on the TYPO3 server
# (Run this on the TYPO3 server, not in Replit)
composer require wirl-pl/headless

# Flush TYPO3 caches after installation
./vendor/bin/typo3 cache:flush
```

**Expected result:** EXT:headless is installed and active. The TYPO3 site serves JSON responses at the /api/ path. An API key exists for authentication.

### 2. Store TYPO3 Credentials in Replit Secrets

Click the lock icon (🔒) in the left Replit sidebar to open the Secrets pane. Add the following secrets:

TYPO3_API_TOKEN: your TYPO3 API key or Bearer token.

TYPO3_BASE_URL: your TYPO3 site's base URL without trailing slash (e.g., https://yoursite.com).

TYPO3_API_PATH: the API base path (e.g., /api or /json — depends on your headless extension configuration).

The full API URL for a page request is typically: {TYPO3_BASE_URL}{TYPO3_API_PATH}?id={pageId} or {TYPO3_BASE_URL}{TYPO3_API_PATH}/{pageId}. Verify the exact format with your TYPO3 admin.

For authentication, EXT:headless typically uses a custom header: X-Auth-Token: {apikey}. Some configurations use Authorization: Bearer {token}. Confirm the expected header name with your TYPO3 configuration.

For TYPO3 instances using HTTP Basic Auth (common in older setups or staging environments), store TYPO3_USERNAME and TYPO3_PASSWORD instead of an API token.

```
// check-typo3-secrets.js
const required = ['TYPO3_API_TOKEN', 'TYPO3_BASE_URL', 'TYPO3_API_PATH'];
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(`Missing secret: ${key}. Set it in Replit Secrets (lock icon 🔒).`);
  }
}
console.log('TYPO3 config OK.');
console.log('Base URL:', process.env.TYPO3_BASE_URL);
console.log('API path:', process.env.TYPO3_API_PATH);
```

**Expected result:** TYPO3_API_TOKEN, TYPO3_BASE_URL, and TYPO3_API_PATH appear in Replit Secrets. The check script prints the URL and path without errors.

### 3. Fetch TYPO3 Page Content (Node.js)

Install required packages in the Shell tab: npm install axios express. TYPO3's headless API typically returns JSON responses where the page content is in a content object containing arrays of content elements organized by colPos (column position in TYPO3's backend layout).

A typical EXT:headless response includes: page metadata (id, title, description, slug), seo data, and content as an object with colPos keys (colPos0, colPos1, etc.) — each containing an array of content elements. Each content element has a type (text, textpic, image, bullets, etc.), a uid, and type-specific data fields.

Common TYPO3 content element types: text (headline + body text), textpic (text with image), image (images only), bullets (bullet list), header (headline only), menu (navigation menu), form (forms), and custom extension elements.

For the page tree, EXT:headless provides an endpoint (varies by version) that returns the page hierarchy. Use this to discover available page UIDs and navigate the site structure programmatically.

Error handling: TYPO3 headless APIs typically return HTTP 200 even for pages that do not exist, but with empty content arrays. Check if the response contains actual content rather than relying solely on HTTP status codes.

```
// typo3.js — TYPO3 Headless API integration for Node.js on Replit
const axios = require('axios');
const express = require('express');

const app = express();
app.use(express.json());

const BASE_URL = process.env.TYPO3_BASE_URL;
const API_PATH = process.env.TYPO3_API_PATH;
const API_TOKEN = process.env.TYPO3_API_TOKEN;

const typo3Api = axios.create({
  baseURL: BASE_URL,
  headers: {
    'X-Auth-Token': API_TOKEN,  // EXT:headless v3+ uses this header
    // Some versions use: 'Authorization': `Bearer ${API_TOKEN}`
    'Accept': 'application/json'
  }
});

// Get page content by TYPO3 page UID
app.get('/api/page/:pageId', async (req, res) => {
  const pageId = parseInt(req.params.pageId);
  if (!pageId) return res.status(400).json({ error: 'Valid page ID required' });
  
  try {
    // EXT:headless endpoint format — may vary by version
    const response = await typo3Api.get(`${API_PATH}`, {
      params: { id: pageId, type: 834 }  // type=834 is EXT:headless JSON typeNum
    });
    
    const data = response.data;
    
    // Extract page metadata and content elements
    const page = {
      id: data.page?.id || pageId,
      title: data.page?.title,
      description: data.page?.description,
      slug: data.page?.slug,
      seo: data.seo || {},
      // colPos0 is the main content column in most TYPO3 layouts
      content: data.content?.colPos0 || data.content?.main || []
    };
    
    res.json(page);
  } catch (err) {
    res.status(err.response?.status || 500).json({ error: err.message });
  }
});

// Get site navigation / page tree
app.get('/api/navigation', async (req, res) => {
  const { rootPageId = 1, levels = 2 } = req.query;
  try {
    const response = await typo3Api.get(`${API_PATH}`, {
      params: {
        id: rootPageId,
        type: 834,
        tx_headless_request: JSON.stringify({ navigation: true, levels })
      }
    });
    res.json({ navigation: response.data.navigation || [] });
  } catch (err) {
    res.status(err.response?.status || 500).json({ error: err.message });
  }
});

// Get list of content elements from a page filtered by type
app.get('/api/page/:pageId/content', async (req, res) => {
  const { pageId } = req.params;
  const { type: filterType } = req.query;
  try {
    const response = await typo3Api.get(`${API_PATH}`, {
      params: { id: pageId, type: 834 }
    });
    
    const allContent = [
      ...(response.data.content?.colPos0 || []),
      ...(response.data.content?.colPos1 || [])
    ];
    
    const filtered = filterType
      ? allContent.filter(el => el.type === filterType)
      : allContent;
    
    res.json({ pageId, content: filtered, count: filtered.length });
  } catch (err) {
    res.status(err.response?.status || 500).json({ error: err.message });
  }
});

app.listen(3000, '0.0.0.0', () => console.log('TYPO3 headless server running on port 3000'));
```

**Expected result:** GET /api/page/1 returns the TYPO3 homepage content as JSON with page metadata and content elements. GET /api/navigation returns the site's page tree.

### 4. Python Integration for TYPO3 API

For Python Replit projects, install requests and flask: pip install requests flask. The Python integration follows the same patterns — create a requests.Session with the TYPO3 auth header set, and call the API endpoints.

For parsing TYPO3 content element responses, write a helper that traverses the colPos keys in the content object and flattens all elements into a single list. This simplifies content processing regardless of which column each element was placed in.

For headless frontend rendering with Python, process content elements by type: text elements have header and bodytext fields, textpic elements have header, bodytext, and gallery (images) fields, and bullets elements have bodytext as HTML list markup. Transform each element type into the appropriate output for your rendering target.

Deploy as Autoscale for web APIs that serve TYPO3 content to frontend clients. Use Reserved VM for background content sync jobs that periodically pull and cache TYPO3 content in a local database for fast serving.

```
# typo3_api.py — TYPO3 Headless API for Python on Replit
import os
import requests
from flask import Flask, request, jsonify

BASE_URL = os.environ['TYPO3_BASE_URL']
API_PATH = os.environ['TYPO3_API_PATH']
API_TOKEN = os.environ['TYPO3_API_TOKEN']

session = requests.Session()
session.headers.update({
    'X-Auth-Token': API_TOKEN,
    'Accept': 'application/json'
})

app = Flask(__name__)

def get_page_content(page_id: int) -> dict:
    """Fetch TYPO3 page content by page UID."""
    response = session.get(
        f'{BASE_URL}{API_PATH}',
        params={'id': page_id, 'type': 834}
    )
    response.raise_for_status()
    return response.json()

def extract_content_elements(data: dict) -> list:
    """Flatten all colPos content elements from a TYPO3 page response."""
    content = data.get('content', {})
    elements = []
    for col_key in sorted(content.keys()):  # colPos0, colPos1, etc.
        elements.extend(content[col_key])
    return elements

@app.route('/api/page/<int:page_id>')
def get_page(page_id):
    try:
        data = get_page_content(page_id)
        return jsonify({
            'id': page_id,
            'title': data.get('page', {}).get('title'),
            'description': data.get('page', {}).get('description'),
            'slug': data.get('page', {}).get('slug'),
            'content': extract_content_elements(data),
            'seo': data.get('seo', {})
        })
    except requests.HTTPError as e:
        return jsonify({'error': str(e)}), e.response.status_code

@app.route('/api/pages/crawl')
def crawl_pages():
    """Crawl multiple TYPO3 pages and return their titles."""
    start = int(request.args.get('start', 1))
    end = int(request.args.get('end', 20))
    results = []
    for pid in range(start, min(end + 1, start + 50)):  # Max 50 pages per request
        try:
            data = get_page_content(pid)
            page = data.get('page', {})
            if page.get('title'):  # Skip non-existent pages
                results.append({'id': pid, 'title': page['title'], 'slug': page.get('slug')})
        except Exception:
            pass  # Skip pages that return errors
    return jsonify({'pages': results, 'count': len(results)})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
```

**Expected result:** GET /api/page/1 returns the TYPO3 root page with its content elements. GET /api/pages/crawl returns titles and slugs for pages in the given ID range.

## Best practices

- Store TYPO3_API_TOKEN, TYPO3_BASE_URL, and TYPO3_API_PATH in Replit Secrets (lock icon 🔒) — never expose credentials in code
- Verify the correct API authentication header name with your TYPO3 admin — EXT:headless versions differ on whether to use X-Auth-Token or Authorization: Bearer
- Always make TYPO3 API calls from your Replit server, not from browser JavaScript, to avoid CORS issues and keep credentials server-side
- Cache TYPO3 page content in your Replit server's memory or database — TYPO3 editorial content changes infrequently, so caching reduces load on the TYPO3 server
- Handle the case where content arrays are empty — TYPO3 returns HTTP 200 for pages with no content, so check the content length explicitly
- Use the page tree / navigation endpoint to discover page UIDs programmatically rather than hardcoding them
- Deploy as Autoscale for APIs serving TYPO3 content to frontends; use Reserved VM for background content sync and caching jobs
- When processing TYPO3 content elements, handle each type explicitly — text, textpic, and image elements have different field structures

## Use cases

### Headless TYPO3 Frontend

Use TYPO3 as a content management backend while serving the public frontend from a Replit application. Content editors work in TYPO3's familiar backend, and your Replit app fetches page content via the API to render it with a custom design — a modern React frontend, a mobile app backend, or an email newsletter generator.

Prompt example:

```
Build an Express server that fetches TYPO3 page content for a given page UID, transforms the content elements array into HTML, and serves it as a custom-rendered page.
```

### Content Migration and Sync

Read content from TYPO3's API to migrate it to another platform or create backup exports. Parse TYPO3 content elements, extract text, headlines, images, and links, and transform them into the target format. Useful for organizations modernizing away from TYPO3.

Prompt example:

```
Write a script that reads all TYPO3 pages and their content elements from the API, exports them to a JSON file with page hierarchy preserved, and generates a migration report.
```

### Content Monitoring and Reporting

Monitor TYPO3 page content for staleness, broken links, or content audit requirements. Crawl the TYPO3 page tree via the API, check last-modified dates, identify pages that have not been updated in N months, and generate compliance reports for content governance.

Prompt example:

```
Create a content audit endpoint that fetches the TYPO3 page tree, identifies pages not modified in the last 6 months, and returns a report with page titles, UIDs, and last modification dates.
```

## Troubleshooting

### HTTP 200 response but content array is empty

Cause: The page exists in TYPO3 but either has no published content elements, or the headless TypoScript is not configured to expose that page's content, or the requested page is hidden/restricted.

Solution: Log into TYPO3's backend and verify the page has published content elements in the correct column position. Check the TypoScript configuration to ensure the headless extension is configured to include content from that page's template. Verify the page is not hidden or restricted by access groups.

```
// Log raw API response to understand structure
const response = await typo3Api.get(`${API_PATH}`, { params: { id: pageId, type: 834 } });
console.log('Raw response keys:', Object.keys(response.data));
console.log('Content keys:', Object.keys(response.data.content || {}));
```

### 401 Unauthorized or 403 Forbidden despite correct credentials

Cause: The API token header name is wrong — EXT:headless versions use different header names. Or the token was generated for a different site configuration in TYPO3.

Solution: Check your EXT:headless version number and verify the correct auth header format in the extension documentation. Try both X-Auth-Token and Authorization: Bearer formats. Verify the token is configured for the correct TYPO3 site (if TYPO3 hosts multiple sites).

```
// Try both auth header formats
const response = await axios.get(url, {
  headers: { 'X-Auth-Token': token }  // EXT:headless v3+
  // headers: { 'Authorization': `Bearer ${token}` }  // alternative
});
```

### typeNum 834 returns HTML instead of JSON

Cause: The EXT:headless TypoScript is not configured in the TYPO3 template, or the typeNum for JSON output in this installation is different from 834.

Solution: Contact your TYPO3 administrator to verify the headless extension is configured and the correct typeNum is set. The typeNum can be anything — check the TypoScript configuration for 'typeNum' and 'lib.headless' entries to find the correct value.

### CORS error when calling TYPO3 API from browser-side JavaScript

Cause: TYPO3 does not add CORS headers by default. Cross-origin requests to TYPO3 must be proxied through your Replit server.

Solution: Always call the TYPO3 API from your Replit server-side code, not from browser JavaScript. Your browser calls your Replit Express/Flask endpoint, which calls TYPO3 internally. This server-side proxy avoids CORS and keeps API credentials server-side.

## Frequently asked questions

### How do I connect Replit to TYPO3?

Install a TYPO3 headless extension like EXT:headless (wirl-pl/headless) on your TYPO3 server, configure it to expose content as JSON, generate API credentials, store them in Replit Secrets as TYPO3_API_TOKEN and TYPO3_BASE_URL, and call the TYPO3 API from your Replit server with the appropriate authentication header.

### Does TYPO3 have a REST API?

Not in core. TYPO3's REST API requires a third-party extension such as EXT:headless (for headless CMS JSON output) or EXT:rest-api (for full REST CRUD). Once the extension is installed and configured, the API is accessible at a configurable path with authentication support.

### What is the difference between TYPO3 and WordPress?

TYPO3 is the dominant enterprise CMS in German-speaking Europe, with strong multilingual content management, granular permissions, and enterprise scalability baked in. WordPress is a general-purpose CMS with a larger global ecosystem and simpler setup. TYPO3's API requires extension installation; WordPress has a built-in REST API in core.

### What is typeNum 834 in TYPO3?

typeNum is a TYPO3 TypoScript concept that maps URL parameters to different output formats. EXT:headless registers typeNum 834 to return JSON content instead of HTML. When you add type=834 to a TYPO3 URL, the headless extension intercepts the request and returns structured JSON instead of the regular HTML page. The typeNum value can be customized per installation.

### What deployment type should I use on Replit for TYPO3 integrations?

Use Autoscale for APIs that serve TYPO3 content to frontend clients on demand. Since TYPO3 editorial content changes infrequently, a caching layer in your Replit server significantly reduces load. Use Reserved VM for background content sync jobs that periodically pull and index TYPO3 content into a faster database for your application.

---

Source: https://www.rapidevelopers.com/replit-integration/typo3
© RapidDev — https://www.rapidevelopers.com/replit-integration/typo3
