Why my Cordova app uses TYPO3 as its backend
Typo3 App-Entwicklung

Why my Cordova app uses TYPO3 as its backend

Yannick Aister 4 min read

The idea

TYPO3 is a powerful CMS – but it can do more than just serve web pages. With a custom Extbase controller, TYPO3 can act as a lean REST backend that returns JSON. The Cordova app on the other side consumes that data via the Fetch API and presents it to the user.

The result: a hybrid app with a single, editorially managed backend – no separate Node server, no additional system to maintain.

TYPO3 side: custom API controller with Extbase

First, register a new plugin in your extension's ext_localconf.php:

 

use Vendor\MyExtension\Controller\ApiController;
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;

ExtensionUtility::configurePlugin(
    'MyExtension',
    'Api',
    // all allowed actions
    [ApiController::class => 'list, show'],
    // non-cacheable actions (for an API: all of them)
    [ApiController::class => 'list, show'],
    ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
);

 

The fifth parameter PLUGIN_TYPE_CONTENT_ELEMENT is required from TYPO3 13 onwards – without it you'll get a deprecation warning in v13 and a hard failure in v14.

The plugin also needs to be registered in Configuration/TCA/Overrides/tt_content.php:

 

use TYPO3\CMS\Extbase\Utility\ExtensionUtility;

ExtensionUtility::registerPlugin(
    'MyExtension',
    'Api',
    'API Endpoint'
);

 

Then the controller itself. The key part: we skip Fluid rendering entirely and return JSON directly:

 

namespace Vendor\MyExtension\Controller;

use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ApiController extends ActionController
{
    public function listAction(): ResponseInterface
    {
        $items = $this->itemRepository->findAll();

        $data = [];
        foreach ($items as $item) {
            $data[] = [
                'uid'   => $item->getUid(),
                'title' => $item->getTitle(),
                'text'  => $item->getText(),
            ];
        }

        return $this->jsonResponse(json_encode($data));
    }
}

 

jsonResponse() has been available directly on the ActionController since TYPO3 11 and automatically sets the correct Content-Type header.

Exposing the URL via TypoScript

The API action needs a publicly accessible URL. The cleanest approach is a dedicated page type. Create a page in the TYPO3 page tree and assign it a custom typeNum via TypoScript:

 

api = PAGE
api {
    typeNum = 1000

    config {
        disableAllHeaderCode = 1
        additionalHeaders.10.header = Content-Type: application/json
        additionalHeaders.10.replace = 1
    }

    10 < tt_content.myextension_api.20
}

 

Important: in TYPO3 13 the TypoScript path changed. The old tt_content.list.20.myextension_api is deprecated – the new path is directly tt_content.myextension_api.20. You'll need to place the plugin once as a content element on the API page in the backend. The API is then accessible at e.g. yourdomain.com/api/. A route enhancer can abstract away the ?type=1000 into a clean URL.

Configuring CORS

For the Cordova app to call the API, TYPO3 needs to send the correct CORS headers. This can be handled directly in the controller response:

 

return $this->jsonResponse(json_encode($data))
    ->withHeader('Access-Control-Allow-Origin', '*')
    ->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')
    ->withHeader('Access-Control-Allow-Headers', 'Content-Type');

Note: Access-Control-Allow-Origin: * is fine during development. In production, restrict the origin to your app domain or a fixed value.

Cordova side: fetching data with the Fetch API

On the app side things are refreshingly straightforward. A simple fetch call is all you need:

 

const API_URL = 'https://yourdomain.com/api/';

async function loadItems() {
    try {
        const response = await fetch(API_URL);

        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }

        const items = await response.json();
        renderItems(items);

    } catch (error) {
        console.error('API error:', error);
    }
}

function renderItems(items) {
    const list = document.getElementById('item-list');
    list.innerHTML = items.map(item => `
        <li>
            <h3>${item.title}</h3>
            <p>${item.text}</p>
        </li>
    `).join('');
}

document.addEventListener('deviceready', loadItems);

 

The deviceready event is important – Cordova fires it once all native plugins have been initialised. Network requests made before this event can fail in certain environments.

Common pitfalls

  • TYPO3 cache – the API page gets cached. For dynamic data either disable caching (config.no_cache = 1) or clear it selectively using cache tags
  • Content Security Policy in your Cordova app's config.xml – external domains must be explicitly whitelisted
  • HTTP vs. HTTPS – Cordova apps block unencrypted connections on iOS by default
  • Missing repository – the Extbase controller needs a correctly configured repository, otherwise findAll() returns nothing

TL;DR

  1. A TYPO3 Extbase controller with jsonResponse() returns clean JSON
  2. Expose the action via a dedicated TYPO3 page and TypoScript
  3. Set CORS headers directly on the response
  4. Consume in Cordova via Fetch API – only after deviceready
  5. Don't forget TYPO3 cache handling and the CSP in Cordova's config.xml

Leave a comment