AI Label integration
Integrate your TYPO3 extension with AI Foundation AI Label so AI-generated content is recorded, reviewable in the backend module, and labelled on the frontend when rules require it.
Prerequisites
- AI Foundation (
ns_t3af) installed - Your extension persists content on applicable tables (
pages,tt_content,sys_file_metadata, or tables registered in AI Label settings) - AI requests go through AI Foundation (
AiServiceInterface) so capture correlation ids are available
Overview
Three integration points:
- Capture (automatic) — AI Foundation stores each generation in
tx_nst3af_ailabel_generation. - Bind (your code) — when you save a record, link the generation to that row.
- Optional direct origin — report involvement without a prior capture.
Applicable tables
Built-in:
pagestt_contentsys_file_metadata
Additional tables: add via AI Label → Settings → Applicable tables,
$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ns_t3af']['ailabelApplicableTables'],
or listen to CollectApplicableTablesEvent:
use NITSAN\NsT3AF\AiLabel\Event\CollectApplicableTablesEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener]
final class RegisterNewsTableForAiLabel
{
public function __invoke(CollectApplicableTablesEvent $event): void
{
$event->addTable('tx_news_domain_model_news');
}
}
Schema columns for extra tables are added by ApplicableTableSchemaListener
as CREATE TABLE fragments. After changing extra tables, run
Maintenance → Analyze Database Structure so the columns exist.
Bind after save (recommended)
Use AiLabelBindHelper at the point where your extension knows the final
record uid (after DataHandler or repository save):
use NITSAN\NsT3AF\AiLabel\Service\AiLabelBindHelper;
// After DataHandler / repository save — pass your extension key as $source
AiLabelBindHelper::bindContentRecord($uid, 'my_extension');
AiLabelBindHelper::bindPageRecord($uid, 'my_extension');
AiLabelBindHelper::bindFileMetadata($metaUid, 'my_extension');
AiLabelBindHelper::bindRecord('tx_news_domain_model_news', $uid, 'my_extension');
Every child bind stores involvement ai_generated. Editors may change that
later in the AI Label module. Visitor badges still require confirmation.
Parameters:
$uid— live record uid (file metadata uid forbindFileMetadata)$source— short extension identifier stored as recording source (use your extension key, for examplemy_extension)$altTextOnly(bindFileMetadataonly) — skip bind when only file metadata text changed (alt / title / description). Do not use this flag when the binary media itself was AI-generated (images, TTS audio). ns_t3aa metadata writes always passaltTextOnly: trueso photos are not stamped as AI media when only accessibility text was generated.
If no capture correlation id is in the current request (for example a follow-up
HTTP request after async file processing), the helper writes origin with
recordOrigin() instead of bindGeneration().
Direct origin reporting
When capture correlation is unavailable:
use NITSAN\NsT3AF\AiLabel\Domain\Involvement;
use NITSAN\NsT3AF\Api\AiLabelRecorderInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$recorder = GeneralUtility::makeInstance(AiLabelRecorderInterface::class);
$recorder->recordOrigin(
'tt_content',
$uid,
Involvement::AiGenerated,
'my_extension',
aiSystem: 'gpt-4',
aiVendor: 'openai',
);
Public API interface: Classes/Api/AiLabelRecorderInterface.php.
Convenience methods (also reset confirmation):
$recorder->markGenerated('tt_content', $uid, 'my_extension');
$recorder->markModified('tt_content', $uid, 'my_extension');
$recorder->clearInvolvement('tt_content', $uid, 'my_extension');
These throw \InvalidArgumentException when $table is not an applicable
table.
Involvement values
| Value | Use when |
|---|---|
not_reviewed | Default; no editor decision |
no_ai | Editor asserts no AI involvement |
ai_generated | Content substantially created by AI |
ai_modified | AI changed existing content |
origin_unknown | Role of AI unclear |
suggestion | System detected/suggested; awaiting person |
Frontend rendering
Include the TypoScript that hooks into fluid_styled_content (visitor badges
on content elements). Listing a Site Set as a dependency does not load its
TypoScript automatically — import the file as well.
Site Set (TYPO3 v13.4+): add nitsan/ns-t3af-label to your sitepackage
set's dependencies, and import the setup:
dependencies:
- nitsan/ns-t3af-label
@import 'EXT:ns_t3af/Configuration/TypoScript/setup.typoscript'
Classic TypoScript templates: include static template
“AI Foundation labels”, or @import the same file.
Fluid badge (namespace ail is registered by AI Foundation):
<html xmlns:ail="http://typo3.org/ns/NITSAN/NsT3AF/AiLabel/ViewHelpers"
data-namespace-typo3-fluid="true">
<ail:label record="{data}" table="tt_content" />
<ail:label file="{file}" />
</html>
Fluid Styled Content ships an image partial override that already calls
<ail:label file="{file}" /> on each rendered file.
Assign state without rendering the badge:
<ail:recordState record="{data}" table="tt_content" as="labelState" />
<f:if condition="{labelState.showLabel}">…</f:if>
<ail:fileState file="{image}" as="labelState" />
DataProcessor alias nst3af-label (variable labelState by default):
tt_content {
dataProcessing {
1550 = nst3af-label
1550 {
as = labelState
}
}
}
Or inject FrontendLabelRenderer / FrontendLabelStateFactory in PHP.
Auto-confirm
Settings tab drives auto-confirm via AutoConfirmSettingsService:
- Confirm when OUR extension recorded it —
ns_t3ai,ns_t3aa,ns_t3affor media, text, or both. - Confirm when DETECTED on upload — recording source
detected_upload(IPTC Digital Source Type already signals AI). - Hold — public-interest text stays manual (also EXTCONF
ailabelHoldList).
EXTCONF ailabelAutoConfirmSources remains an extra allow-list (used even
when the Settings own-toggle is off). Auto-confirm never fills the responsible
person field.
Check AutoConfirmSettingsServiceTest and AiLabelRuleMatrixTest before
relying on auto-confirm in production.
Module settings consumed on the frontend
AiLabelSettingsService drives visitor badge appearance:
labelSize,labelWording— passed toFrontendLabelRenderer(show_site_language= text only;icon_only= icon only)markImageFile === overlay— enables image partial wrapper and overlay badgemarkImageFile === written_in— stamps processed image copies (ImageMagick)labelPosition— overlay CSS class and processed-file stamp cornermachineReadable—iptc/iptc_jsonld/offlabelUnknownOrigin— visitor badge for confirmed unknown-origin mediasecondInfoLayer— expandable<details>on the badge with human-readable involvement wording only (machine reason codes stay in evidence export / backend lists)- When
markImageFile === overlay, media CTypes (image,textmedia,textpic) skip the content-element After/All drop-in badge so the image overlay is the single visitor mark
Deep links
File list module: ProcessFileListActionsListener adds AI Label link to
the media tab with folder pre-selected.
Maintainer references
- Agent summary:
context/features/ai-label.md - Deep spec:
context/specs/FEATURE_AiLabel.md - Unit tests:
Tests/Unit/AiLabel/
Run tests:
cd packages/ns_t3af
composer test -- --filter AiLabel