undkonsorten / easychat
EasyChat – TYPO3 standalone AI Chatbot - A TYPO3 chatbot/assistant without third party tools. Only TYPO3 + LLM endpoint needed (ChatGpt, Mistral...). Privacy focused (GDPR). Chats stored in TYPO3 only.
Package info
github.com/undkonsorten/easychat
Type:typo3-cms-extension
pkg:composer/undkonsorten/easychat
Requires
- php: ^8.2
- symfony/ai-agent: ^0.14
- symfony/ai-chat: ^0.14
- symfony/ai-generic-platform: ^0.14
- symfony/ai-platform: ^0.14
- symfony/ai-store: ^0.14
- typo3/cms-backend: ^13.4 || ^14.3
- typo3/cms-core: ^13.4 || ^14.3
- typo3/cms-extbase: ^13.4 || ^14.3
- typo3/cms-frontend: ^13.4 || ^14.3
- typo3/cms-install: ^13.4 || ^14.3
Requires (Dev)
- ergebnis/composer-normalize: ^2.45
- friendsofphp/php-cs-fixer: ^3.64
- php-parallel-lint/php-parallel-lint: ^1.4
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5 || ^12.0
- saschaegerer/phpstan-typo3: ^2.2 || ^3.1
- ssch/typo3-rector: ^3.16
- symfony/ai-qdrant-store: ^0.14
- symfony/ai-similarity-search-tool: ^0.14
- typo3/coding-standards: ^0.9
- typo3/testing-framework: ^9.7
Suggests
- lochmueller/index: ^2.3
- symfony/ai-qdrant-store: ^0.14
- symfony/ai-similarity-search-tool: ^0.14
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-15 14:43:47 UTC
README
EasyChat is a lightweight open source chatbot for TYPO3 websites without third party chat tools.
All you need is TYPO3 and an LLM endpoint.
EasyChat is focused on privacy and data protection, since all chat conversations are only stored in your TYPO3 database.
🚀 Features
-
🗨 Chatbot frontend
- based on the open source chat framework Deep Chat (Supports: Vanilla JS, Vue, React, Angular etc.)
-
🤖 LLM Endpoints for
- (self-hosted) open source LLMs (e.g. gpt‑oss) and
- standard LLMs (ChatGPT, Claude Opus, Mistral etc.)
- Chat memory (Chatbot can remember old questions)
-
🔒 Data protection:
- Chat session data is saved only within TYPO3 (on your own server)
- Optional privacy consent before the chat starts
- Conversations are tracked by a server-issued HttpOnly cookie; IP addresses are never stored
- Automated cleaning of user data (scheduler task)
-
🛡 Cost control & abuse protection:
- Chat endpoint built into TYPO3 — no API keys or secrets in the page
- Rate limits per visitor, per session and for new sessions
- Caps on message length, answer length (
max_tokens) and the history sent to the model
-
⛁ TYPO3 content as knowledge base (RAG, Vector Database)
- TYPO3 Content to RAG via ext:index
- Connector (to Qdrant vector database)
🛠️ Setup guide (4 Steps)
1. Install the TYPO3 Extension
Install the TYPO3 ChatBot Extension EasyChat via composer:
composer require undkonsorten/easychat
After installation a database compare is necessary (via Install Tool or TYPO3 Console) to create new tables.
2. Configure the LLM provider
Now you need to connect TYPO3 to your LLM provider via API. Create a new database record "Configuration" in TYPO3.
Then fill out the fields of the Configuration record.
The LLM Settings (URL, API key, model, system prompt) are always required. The Vector DB settings
(shown here with Qdrant and EXT:index configurations) are only needed if the chatbot should answer from your own
content (RAG); leave Vector db on None otherwise. See
How content elements, configurations and index configurations connect.
Sample configurations for your LLM
- Mistral (Free plan available | How to get an API key?)
- Name:
Mistral (mistral-tiny) - Model (of the LLM):
mistral-tiny - URL (of the API endpoint):
https://api.mistral.ai - API Key:
your-api-key-abc123xyz-... - System Message (Prompt):
You are a support chatbot ...
- Name:
More samples (OpenAI, mittwald, Groq, Ollama …) → Sample configurations
3. Setup the Content Element
Now place the chatbot content element on a page.
- Open a TYPO3 page
- Add/create a new content element "Chatbot".
- In its Plugin tab, choose one of the previously created EasyChat configuration records.
- This choice decides everything the chatbot uses: the LLM, the system prompt, the limits, and — if the configuration has a vector database — which vector database/collection it searches and therefore which Index configurations (EXT:index) it knows about. See How content elements, configurations and index configurations connect.
- Pick the consent mode and texts, save.
The widget talks to a chat endpoint built into the extension, <language base>/_easychat/chat (for example
/_easychat/chat or /en/_easychat/chat). No URL, API key or secret has to be configured or ends up in the page: the
server identifies the chatbot by the content element and issues the session cookie itself. If a proxy or firewall in
front of TYPO3 filters requests, allow POST to that path. The leading underscore cannot occur in a page slug, so the
endpoint never collides with a page; do not create redirects or static routes at that path.
You can place as many chatbots as you like — on different pages or on the same page — each selecting its own configuration.
4. Limits (optional)
Every chat message costs tokens, so EasyChat caps what one visitor can spend. The defaults are fine for most sites.
| Where | Setting | Default | Meaning |
|---|---|---|---|
| Configuration record, Limits | Maximum message length | 2000 characters | Longer visitor messages are rejected before anything is sent to the model. |
| Configuration record, Limits | Maximum answer length | 1024 tokens | Sent as max_tokens; caps the cost of one answer. |
| Configuration record, Limits | History window | 20 messages | How many of the latest messages of the conversation are sent along with a new question. The full conversation is still stored. |
| Extension configuration | rateLimitPerIpPerMinute |
20 | Chat requests per minute per IP address. |
| Extension configuration | rateLimitPerSessionPerHour |
60 | Messages per hour per conversation cookie. |
| Extension configuration | rateLimitNewSessionsPerIpPerHour |
10 | How often one address may start over without its cookie. |
| Extension configuration | rateLimitIpExcludeList |
(empty) | Addresses never rate limited (cmpIP syntax, like [SYS][devIPmask]). |
0 disables a rate limit. Visitors over a limit get a friendly "Too many requests" answer with a Retry-After
header. See Extension Configuration for the operational details.
✨ YOU ARE DONE!!! 👊 CONGRATULATIONS 🎉
Session storage in TYPO3
How are chat sessions stored?
Two cookies are involved:
easychat_session_id— the consent marker. The widget's JavaScript sets it once the visitor agreed (or right away in the without consent mode; in the external consent mode your cookie manager sets it). Its value is only a marker; the server stores a hash of it with every conversation.easychat_session— the conversation cookie. The chat endpoint issues it on the first message: a random 64-character id,HttpOnly,Secureon https,SameSite=Strict, with the Cookie expiration lifetime chosen in the content element. JavaScript cannot read it, so a script injected into the page cannot steal a conversation.
One conversation is one row in tx_easychat_domain_model_session, identified by the session id and the chatbot
configuration: a visitor who talks to two chatbots on your site gets two rows under one cookie, and returning to a
chatbot continues its conversation. The whole conversation — the system prompt plus every question and answer — is
stored in that row. Only the latest History window messages are sent to the model with a new question.
Revoking consent and accepting again gives the visitor a new marker, and with it a new conversation; the same happens when the marker cookie expires.
What is not stored: IP addresses. They are only used in memory to apply the rate limits, keyed by a hash that needs the site's encryption key and changes daily.
With the EasyChat backend module you can
- Watch, review, delete and export chat sessions
- Export as CSV: Click Export as CSV on the session list or Export this session as CSV on a single session's detail view
Cleaner task: Delete old chat sessions
For data protection we recommend setting up the 🗑 cleaner task in order to delete old chat sessions.
Steps:
- Choose the task
Execute console commands (scheduler) - Schedulable command:
easychat:delete-sessions: Deletes sessions older than given date interval. - Set the scheduler interval
- Save!
- Then define the
keepDateIntervalin the ISO 8601 durations format: 1 Day =P1D, 2 Weeks =P2W, 3 Months =P3M, 1 Year =P1Y, 1 Year and 2 Months =P1Y2M
Extension Configuration
EasyChat has a small set of global options in the Extension Configuration
(Admin Tools → Settings → Extension Configuration → easychat, or in
config/system/settings.php):
| Key | Default | Meaning |
|---|---|---|
storagePid |
1 |
Page/folder UID where chat sessions (tx_easychat_domain_model_session) are stored and read. This applies both to the chat endpoint that saves conversations and to the backend module that lists them — they always use the same value. |
itemsPerPage |
50 |
Number of sessions per page in the backend module list. |
rateLimitPerIpPerMinute |
20 |
Chat requests per minute per IP address (sliding window). 0 disables the limit. |
rateLimitPerSessionPerHour |
60 |
Chat messages per hour per conversation cookie. 0 disables the limit. |
rateLimitNewSessionsPerIpPerHour |
10 |
New conversation cookies per hour per IP address, so dropping the cookie does not evade the session limit. 0 disables the limit. |
rateLimitIpExcludeList |
(empty) | Comma separated IP addresses or ranges that are never rate limited, same syntax as [SYS][devIPmask]. |
// config/system/settings.php 'EXTENSIONS' => [ 'easychat' => [ 'storagePid' => '1', 'itemsPerPage' => '50', 'rateLimitPerIpPerMinute' => '20', 'rateLimitPerSessionPerHour' => '60', 'rateLimitNewSessionsPerIpPerHour' => '10', 'rateLimitIpExcludeList' => '', ], ],
We recommend pointing storagePid at a dedicated SysFolder rather than the root page.
Rate limits in production:
- The limiter state lives in TYPO3's
ratelimitercache, which is a file cache per server by default. Behind a load balancer each node counts on its own, so the effective limit is nodes × limit. Configure a shared backend (for example Redis) forSYS.caching.cacheConfigurations.ratelimiterif that matters to you. - Behind a reverse proxy TYPO3 must know the proxy (
[SYS][reverseProxyIP],[SYS][reverseProxyHeaderMultiValue]), otherwise all visitors share the proxy's address and one limit. - IP addresses are never written anywhere: the limiter key is an HMAC of the address with the site's encryption key and the current date.
- Provider and vector store errors are no longer returned to the browser; visitors see a generic message. The details
are logged under the
Undkonsorten.Easychatlogger — seeTYPO3_MINIMUM_LOGLEVELinext_localconf.phpfor the optional file writer.
Knowledge base (RAG)
EasyChat can answer questions using your own TYPO3 content and files as a knowledge base instead of (or in addition to) the LLM's general knowledge, by embedding your pages/files into a vector store (like Qdrant).
The knowledge indexing itself is delegated to and configured via the TYPO3 Extension Index, a generic TYPO3 content-crawling framework. EasyChat listens to the indexer and pushes the crawled content into the vector store(s) of any matching EasyChat Configuration record.
Index can be configured to read
- content elements,
- plugin content (e.g. FAQs, News) but also
- Files (manuals, documentation in PDF, XLS etc.)
Quick setup
composer require symfony/ai-qdrant-store- Create an EXT:index configuration on your root page and the two scheduler tasks
index:queueandmessenger:consume. - On your EasyChat configuration set Vector db to
Qdrant, fill in the connection fields and select the index configuration(s). - Make sure the Chatbot content element selects exactly this EasyChat configuration.
More indexer setup hints here →
How content elements, configurations and index configurations connect
Everything hangs off the EasyChat configuration record that a Chatbot content element selects:
Content element (chatbot / User)
│
▼
EasyChat configuration
├─ LLM (model, API url/key, system message)
├─ Limits (message length, max_tokens, history window)
├─ Vector DB (Qdrant host/port, collection, API key, embeddings model)
└─ Index configurations (EXT:index)
│
▼
content written into that collection
- At chat time the endpoint resolves the content element the widget names, loads the EasyChat configuration it selects, and the similarity search queries exactly the vector database/collection configured there.
- At index time EasyChat looks up, for every crawled page or file, all EasyChat configurations whose
Index configurations field contains the index configuration that is running, and writes the content
into each of their collections. Configurations with Vector db
noneare ignored.
So the Index configurations field on the EasyChat configuration is the one place that decides what a chatbot knows, and the content element decides which configuration (and thus which knowledge base) a chatbot uses.
Multiple Chatbots
You can run multiple chatbots with different system prompts, LLMs or knowledge bases simultaneously in one TYPO3 installation: create one EasyChat configuration per chatbot and let each Chatbot content element select its own. A visitor keeps a separate conversation with every chatbot under one session cookie.
This is especially useful for testing.
Theming & Templates
Chat Frontend: Deep Chat
EasyChat comes along with Deep Chat - an open source chat web component in the frontend.
For simplicity we integrated Deep Chat as a plain Vanilla JS web component, but it can be used with many other frameworks (e.g. React, Vue, Svelte, Angular). EasyChat is able to communicate with popular AI providers, but can also connect to your own servers - in our example with TYPO3.
DeepChat is an example implementation. Feel free to use another chatbot frontend. The default dummy template is located at
/Resources/Private/Templates/ChatFrontend.html.
Whatever frontend you use, it posts {"messages": [{"role": "user", "text": "..."}], "contentElement": <uid>} as
JSON to {endpointUrl} (same origin, with credentials so the session cookie is stored and sent) and gets {"text": "...", "role": "ai"} back (ai is deep-chat's role for answers). The
template receives endpointUrl and contentElementUid as view variables. Errors come as {"error": "..."} with a
matching status code (400, 403, 404, 413, 415, 429 with Retry-After, 502, 503); with deep-chat set
errorMessages='{"displayServiceErrorMessages": true}' to show them.
Styled version
If you want to use our suggested default styles for ChatBot & Cookie Consent you need to include the TypoScript templates in /Configuration/Styling/.
How to add your own styles 💐
You can either overlay the default template or the styled template, by setting your own template paths.
# overlay the easychat styled version
plugin.tx_easychat.view {
partialRootPaths.10 = EXT:my-sitepackage/Resources/Private/_Default/Easychat/Partials/
templateRootPaths.10 = EXT:my-sitepackage/Resources/Private/_Default/Easychat/Templates/
}
Be aware,
- there are many ways to inject styles into a web component like
<deep-chat> - keep also in mind the styles for the chatbot trigger button and the consent module
How to Change the texts
You can edit some of the content directly in the frontend.
You can override texts used in the template via locallang.xml or via TypoScript.
plugin.tx_easychat {
_LOCAL_LANG {
default.easychat_nagscreenMessage = Hast du Fragen zu unserem Angebot?
default.easychat_dataProtectionConsentAcceptedMessage = Einwilligung erteilt
}
}
Development & testing
Tests and code checks run in containers via Build/Scripts/runTests.sh (docker or podman), no local PHP needed.
→ See Documentation/Development-and-Testing.md for all commands, Composer scripts and CI details.
Credits
🙏 This TYPO3 Extension was built by the Berlin-based digital agency undkonsorten.
- Eike Starkmann (Product Owner & Inspirator, TYPO3 Development)
- Lars Hayer (Frontend, Theming)
- Thomas Alboth (Product Owner & Documentation)
- Jule Nott (UI Design)
- Felix Althaus & J. (Critical Thinking)
License
GNU General Public License, version 2
Upgrading
From 0.3.x to 0.4.0
- TYPO3 13.4 and 14.3: TYPO3 12.4 is no longer supported, stay on EasyChat 0.3.x there.
- symfony/ai 0.14: EasyChat now requires symfony/ai 0.14 (was 0.1). If your project requires
symfony/ai-qdrant-storeorsymfony/ai-similarity-search-toolitself, raise them to^0.14(composer require symfony/ai-qdrant-store:^0.14 symfony/ai-similarity-search-tool:^0.14 -W). Own code using symfony/ai directly has to follow its UPGRADE.md. - No re-index needed: point ids and payloads in the vector store stay the same.
- Chat sessions: sessions saved by 0.2.x still load and can be continued. New assistant messages
are stored with an additional
partsfield. - Backend module: on TYPO3 14 the session module is found under Content (formerly Web).
- API changes:
PointRemoverInterfaceandQdrantPointRemoverare removed, points are deleted through the store'sremove().VectorTargethas no$removerany more, andStoreFactory::create()returnsStoreInterface&ManagedStoreInterfaceinstead of the QdrantStoreclass.
From 0.2.x to 0.3.0
This release closes a security audit: the TYPO3 reaction secret used to be rendered into every page, and the chat endpoint had no limits.
- Reactions removed: the chat endpoint is now a frontend route of the extension (
<language base>/_easychat/chat), and Chatbot content elements select the EasyChat configuration directly instead of a reaction URL and secret. Run the upgrade wizard Migrate EasyChat chatbots from reactions to configurations (Admin Tools → Upgrade → Upgrade Wizard, orvendor/bin/typo3 upgrade:run undkonsortenEasychatReactionToConfigurationMigration) before anyone re-saves a Chatbot content element — saving drops the old reaction URL the wizard needs. Elements it cannot map are listed; select their configuration by hand. Afterwards delete the reactions of type Reaction for easychat.typo3/cms-reactionsis no longer required by EasyChat. - Database compare required: new columns on
tx_easychat_configuration(limits) andtx_easychat_domain_model_session(configuration,client_marker), a new index on the session table, and thesys_reaction.easychat_configurationcolumn goes away. - New endpoint: if a proxy, WAF or static file cache in front of TYPO3 filters requests, allow
POSTto/_easychat/chatunder every language base. The underscore keeps the route out of the page-slug namespace. - Template overrides:
settings.urlandsettings.apiKeyno longer exist. Build the deep-chatconnectattribute from the new view variablesendpointUrlandcontentElementUid(withcredentials: 'include'), and addrequestBodyLimits='{"maxMessages": 1}'anderrorMessages='{"displayServiceErrorMessages": true}'(seeResources/Private/Templates/ChatFrontend.html). - Sessions: conversations are now tracked by a server-issued HttpOnly cookie
easychat_session; the JavaScript cookieeasychat_session_idonly marks consent. Conversations started with 0.2.x are not continued, visitors start fresh. Existing session rows stay readable in the backend module. - Limits: new per-configuration fields Maximum message length (2000), Maximum answer length (1024 tokens) and History window (20 messages), and new rate limits in the extension configuration (20 requests/minute per IP, 60 messages/hour per session, 10 new sessions/hour per IP). Check them against your traffic; behind a load balancer read the notes under Extension Configuration.
- Errors: provider and vector store errors are no longer sent to the browser. Enable the log writer
(
TYPO3_MINIMUM_LOGLEVEL) to see them. - API changes:
ChatReactionis gone. The chat turn lives inChat\ChatService, agent wiring inFactories\AgentFactory;SessionRepository::setup()takesconfigurationandclientMarkerand one conversation is keyed by session id and configuration.
From 0.1.x to 0.2.0
- Database compare required: new columns on
tx_easychat_configurationand the new tabletx_easychat_index_point. - Vector dimensions are configurable: Qdrant collections used to be created with a hardcoded size of 4096. The new field Embedding dimensions (
vector_db_dimensions) defaults to 1536. If you already use a vector store, set it to the size of your existing collection (4096 for collections created by 0.1.x), or drop the collection and re-index. - New dependency:
typo3/cms-installis now required. - API change:
StoreFactory::create()takes a new requiredint $dimensionsargument and throws an exception for unsupported store types. - Sessions: new sessions are stored on the configured storage PID. The session table is now visible in the list module, and its fields are read-only.
Planned Features
To Do
- Add Redis as a vector store
- More LLM settings (like temperature)
- Voting for good/bad answers
- Pre suggested questions
Implemented
- ✅ Version 0.3.0
Connect EasyChat configuration and content element directly, without a reaction— done, see Setup the Content Element - ✅ Version 0.2.0
Website scraping/indexing via TYPO3 for the knowledge base (via vector database)— done, see Knowledge base (RAG)
Contact
Any more ideas, questions, suggestions? Feel free to 📧 contact us.
Contact us via our website, GitHub or TYPO3 Slack.


