Description
CGW EU AI Act Labeler helps you implement the transparency obligations of Article 50 of the EU AI Act on your WordPress site. It gives your editorial team a simple way to record how much artificial intelligence was involved in a piece of content, and it outputs the matching notice to visitors where they will actually see it.
The plugin provides the technical implementation. It does not decide which of your content requires a disclosure, and it does not replace legal advice for your individual situation.
Four disclosure levels
- No AI involvement
- AI-assisted
- AI-generated
- AI-generated, human-reviewed
Each level has its own text, color and icon, and every one of them can be customized in the settings.
Where notices appear
Article 50(5) of the EU AI Act requires the information to be provided in a clear and distinguishable manner, at the latest at the time of the first interaction or exposure. A single blanket notice in your footer does not achieve that, so the plugin places notices at the content itself:
- Media (images, video, audio) receive a colored caption with an icon directly below the element, so the media and its disclosure are perceived together. This works for classic markup, Gutenberg blocks and galleries, and the caption is never inserted inside a link or a
pictureelement. - Text (posts, pages, custom post types) shows the notice above the content by default, so it is seen before reading.
- Featured images get a notice box at the start of the content, because the theme renders the image itself.
Four notice formats are available: banner, inline text, badge and modal dialog. A live preview in the settings shows the result immediately, without saving.
AI media analysis with a transparent score
The plugin can analyze your media library for signs of AI generation, entirely on your own server. Every signal it finds carries a weight, and the sum produces a score from 0 to 100 together with a confidence level of none, low, medium or high:
- Strong signals: a structured provenance field (
digitalSourceType, for exampletrainedAlgorithmicMedia) or a generator entry from a C2PA manifest or XMP field. - Medium signals: software, encoder or producer fields naming a known AI tool, generation parameters in PNG text chunks, explicit AI statements in IPTC fields, and agreement between several independent fields.
- Weak signals: file name, media title, description and alt text. Their combined weight is capped, so they can never produce an automatic disclosure on their own.
- Contradictions: if the provenance field points to a conventional capture, the score is reduced.
Media is only labeled automatically from a configurable threshold upwards (70 by default) and only when at least one structured metadata signal is present. Every result is shown with its score, confidence level and the signals behind it, so you can always see why a suggestion was made. Existing disclosures are never overwritten.
Metadata is parsed per format for JPEG, PNG, WebP, GIF, PDF, audio and video, rather than searching raw file text. Uploads are analyzed automatically, and the media library can be scanned in batches at any time.
Dashboard and overview
- Compliance dashboard with a label score from 0 to 100, a traffic light rating and charts.
- Central AI content screen listing every labeled post, page, media item and custom post type, with a filter per level.
- A dashboard card explaining which notice is placed where, and why.
Shortcodes
If you prefer to control placement yourself, set the position to “Shortcode (manual placement)” and use:
[cgw_eu_ai_act_labeler]– notice for the current content[cgw_eu_ai_act_labeler type="page"]– only output for a given content type, ideal for templates[cgw_eu_ai_act_labeler media="1"]– notice for AI-labeled media inside the content[cgw_eu_ai_act_labeler format="badge"]– force banner, inline, badge or modal[cgw_eu_ai_act_labeler id="42"]– notice for a specific item[cgw_eu_ai_act_labeler text="…"]– use your own text
Media captions always remain in place, even in manual mode. Sites upgraded from an earlier version keep the former shortcode names as aliases so existing content continues to work; new installations register only [cgw_eu_ai_act_labeler].
Privacy
By default the plugin makes no connections to third parties. It does not send your content, media or metadata anywhere, it loads no external scripts or fonts from a CDN, and it sets no cookies. All libraries, including Chart.js, are bundled locally. Dismissed notices are remembered in the browser’s sessionStorage for the current session only.
The only exception is the optional external visual AI detection, which is disabled by default and only becomes active after you enable it and enter your own API credentials. See “External services” below for what is sent, when, and to whom.
For developers
Available filters: wpaal_scanner_signals, wpaal_scanner_score, wpaal_auto_label_threshold, wpaal_should_auto_label, wpaal_known_ai_generators, wpaal_media_caption_text, wpaal_media_notice_text, wpaal_compact_media_caption, wpaal_overlay_unsafe_widgets, wpaal_external_detection_providers. The action wpaal_shortcode_conflict fires if a legacy shortcode alias is already taken.
Developed by Thomas Frenken for CGW GmbH.
External services
The plugin works entirely on your own server. It optionally connects to one of the following third-party services for visual AI image detection. This feature is disabled by default, requires your own account and API credentials with the provider, and is only used once you enable it under Settings Automatic AI detection and select a provider. When enabled, it supplements the local metadata analysis with a visual assessment of the image content. Its result alone never triggers an automatic disclosure; it only raises the analysis score and is shown in the scan results for manual review.
Sightengine
Used for visual AI-generated image detection (model “genai”). When enabled and selected, the plugin sends a request to https://api.sightengine.com/1.0/check.json each time a media file is analyzed, that is on upload and during a media scan. The request contains the public URL of the image in your media library together with your Sightengine API user and API secret. Sightengine fetches the image from that URL and returns a probability that the image is AI-generated. No other data about your site, your users or your visitors is sent.
- Terms of use: https://sightengine.com/policies/terms
- Privacy policy: https://sightengine.com/policies/privacy
AI or Not
Used for visual AI-generated image detection. When enabled and selected, the plugin uploads the image file itself to https://api.aiornot.com/v2/image/sync each time a media file is analyzed, that is on upload and during a media scan. The request contains the image file and your AI or Not API key. The service returns a verdict (AI or human) with a confidence value. No other data about your site, your users or your visitors is sent.
- Terms of service: https://aiornot.com/terms-of-service
- Privacy policy: https://aiornot.com/privacy-policy
Both providers usually charge per request. Please review their terms and pricing before enabling the feature, and make sure that sending your images to the provider is compatible with your own privacy obligations.
Screenshots




Installation
- Upload the plugin through Plugins Add New, or copy the
cgw-eu-ai-act-labelerfolder to/wp-content/plugins/. - Activate the plugin in the Plugins screen.
- Go to EU AI Act Labeler Settings and choose your notice format, position and content types.
- Optional: run EU AI Act Labeler Media scan to review your existing media library.
FAQ
-
Does the plugin send data to any external service?
-
Not by default. Every analysis runs on your own server and all assets are served locally; the plugin makes no outbound requests on its own. Only if you deliberately enable the optional external visual AI detection under Settings Automatic AI detection and enter your own API credentials are images sent to the provider you selected. The section “External services” describes exactly what is sent and links to the providers’ terms and privacy policies.
-
Does this make my site legally compliant?
-
No. The plugin supports the technical side of labeling. Which content requires a disclosure, and how a notice must be worded in your specific case, remains a legal assessment. Please consult qualified advice if you are unsure.
-
Why was my AI image not detected automatically?
-
The detection relies on metadata inside the file. If a file carries no provenance metadata at all, for example a screenshot, a video still or an image that a platform has stripped, no reliable statement is possible on your own server. Label those items manually.
-
Why does a file called “openai-logo.png” not get labeled?
-
Because a file name alone is deliberately treated as a weak signal. Weak signals are capped so they can never reach the threshold on their own. This avoids false positives on files that merely mention an AI tool by name.
-
Does the plugin validate C2PA signatures?
-
No. It detects whether a C2PA manifest is present and reads the entries it contains, but it performs no cryptographic validation of the signature. A manifest on its own does not prove AI generation, because cameras also use C2PA as a proof of authenticity.
-
Can I place the notices myself?
-
Yes. Set the position to “Shortcode (manual placement)” and use the shortcodes listed above. Captions on AI-labeled media stay in place so those disclosures cannot be lost by accident.
-
Is my existing data kept when I upgrade from an earlier version (1.x or 2.x)?
-
Yes. Settings, disclosure levels and stored scan results are preserved, because the database keys never changed. What changed is the plugin folder: version 2.0.0 used
wp-eu-ai-act-labeler, version 1.x usedwp-ai-act-labeler. WordPress does not treat a folder change as an update, so install this version alongside the old one. On first load it deactivates the earlier version automatically and shows a notice until you delete the old folder under Plugins Installed Plugins. -
In which languages is the plugin available?
-
English and German are included. Further translations are welcome through translate.wordpress.org.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“CGW EU AI Act Labeler” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “CGW EU AI Act Labeler” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
3.1.0
- The primary shortcode is now [cgw_eu_ai_act_labeler], consistent with the plugin prefix. Sites upgraded from an earlier version keep [wp_eu_ai_act_labeler] and [ai_act_label] as aliases so existing content keeps working; new installations register only the new name.
- The readme now documents the optional external visual AI detection services (Sightengine, AI or Not) in a dedicated “External services” section, including what data is sent, when, and links to the providers’ terms and privacy policies. The privacy statements in the readme and in the plugin were corrected accordingly: the plugin makes no external connections unless this optional feature is enabled.
- Removed the Plugin URI header, which pointed to a page that does not exist yet.
- Translation files: the compiled PHP translation file is no longer shipped; the bundled German .po/.mo files remain so the German interface works immediately.
3.0.0
- Renamed to “CGW EU AI Act Labeler”. The WordPress.org plugin directory does not accept plugin names or slugs that begin with “wp”, so the plugin slug, folder, main file and text domain are now cgw-eu-ai-act-labeler. The internal prefix (wpaal_) and all database keys are unchanged, so settings, disclosure levels and scan results are preserved.
- An earlier version still installed under the old folder name (wp-eu-ai-act-labeler or wp-ai-act-labeler) is deactivated automatically the first time this version loads, so the two never run side by side. An admin notice reminds you to delete the old folder.
2.11.0
- New: compact label for small media. Images whose width or height is 200 px or less now receive a circular, icon-only label placed on the media instead of the full caption, which was wider than such thumbnails and broke sidebar and post list layouts. The full disclosure remains available as a tooltip and for screen readers. The size limit is configurable under Settings Media notices (0 disables the compact form), a filter allows per-media decisions, and a second live preview shows the compact form. Media that are merely displayed small by CSS are detected in the browser as well.
- Fixed: broken layouts in Elementor Pro post lists (Posts and Archive Posts widgets) with the thumbnail on the left. The label wrapper was placed around the image link, which is a flex item with a fixed width, so the thumbnail collapsed to nothing and the label ended up over the title and date. The label is now positioned inside the widget’s own thumbnail container without a wrapper.
- Improved: whenever a block element sits between a link and its image (card layouts such as a link wrapping a div with the image), the image itself is now the reference frame for the overlay label instead of the link, so links that carry layout responsibility are no longer wrapped.
- Fixed: a label placed after a picture element inside a link no longer carries a tabindex attribute; the content model of a link forbids focusable descendants.
2.10.0
- The media library list view now has the same disclosure level filter as the central overview. It sits in the toolbar between the date selection and the “Filter” button and additionally offers “Not labeled”, which lists all media that still need to be reviewed. The grid view has no such toolbar and is unaffected.
2.9.1
- The disclosure level labels next to the input fields now show the icon as well, so the icon switch can be judged there directly. The same icon also appears in the disclosure column of the content list, which keeps the badge look identical in the frontend and the backend.
- The icon switch introduced in 2.9.0 only worked in one direction: switching off hid the icons immediately, switching on did nothing until saving, because the icon was not present in the previewed markup at all. In the backend the icon markup is now always rendered and only its visibility is switched, so the preview follows in both directions. The frontend continues to omit the icon entirely when the switch is off.
- The setting is now presented as a switch instead of a checkbox. The underlying checkbox is kept, so keyboard operation, form submission and screen reader output are unchanged.
2.9.0
- New switch under Settings Style & info settings to turn the icons in the notices on or off. It applies to every frontend format – banner, inline, badge, modal and the media caption – and takes effect in both previews immediately, without saving first. Icons stay on by default.
- Notices in the frontend are no longer set in bold. The disclosure text is no longer wrapped in a strong element, and the bold font weight has been removed from the badge and the media caption.
2.8.4
- Follow-up to 2.8.3: in card listings the label sat slightly below the lower edge of the image. Such images often carry a margin of their own – WooCommerce sets “margin: 0 0 1em” on the category image to space it from the heading – and that margin counted towards the height of the positioning frame. It is now kept out of that height while the spacing to the heading is preserved, so the label sits exactly inside the image. Images in normal text flow are unaffected.
2.8.3
- The label now stays inside the image frame when a link wraps a whole card. In listings such as the WooCommerce category overview, image and heading share one link; the overlay wrapper spanned the entire card, so a label positioned “bottom left” ended up underneath the heading instead of on the image. In this case the image itself is now the reference frame. Classic linked images are unaffected.
- Where the caption ends up inside a link, it no longer carries a tabindex attribute. The HTML content model of a link forbids focusable descendants; the label text becomes part of the link name and remains available to screen readers.
2.8.2
- Product images in WooCommerce galleries are now labeled. Those images carry no wp-image- class and their src points to a resized variant such as “image-600×600.jpg”, which WordPress cannot resolve back to the attachment – so the medium was never recognized and stayed unlabeled. Resized variants are now resolved to their original attachment.
- Media detection now also evaluates the data-src and data-large_image attributes. This covers lazy loading extensions, which replace the src with a base64 placeholder, as well as the WooCommerce gallery zoom view.
2.8.1
- The disclosure level labels shown next to the input fields under Settings Style & info settings (and in the filename marker section) now follow color, transparency, corner radius and border changes immediately, just like the two previews. Previously they kept showing the saved state until the settings were saved.
2.8.0
- Labeled media are now disclosed outside single views as well. Until now the entire frontend output was tied to single post and page views, so an image marked as AI-generated stayed completely unlabeled on archive, shop category, taxonomy and search pages – the stylesheet was not even loaded there. The media caption now follows the medium wherever it is displayed. The notice for the post text itself remains limited to single views and to the selected content types.
- Added support for page-builder templates: Elementor renders theme builder templates (archive and single templates, headers, footers) through its own filter instead of “the_content”, so media placed in them was never reached. Such content is now labeled as well, without producing duplicate labels on Elementor-built single views.
- Color and transparency changes in the settings are now shown immediately in both previews instead of only after saving. Editing the colors of a level also switches the preview to that level, so the change is actually visible.
2.7.0
- Simplified the badge color settings introduced in 2.6.0 from three color fields per disclosure level down to two: the existing accent color now once again drives the badge border, icon and text, while the background keeps its own color plus a dedicated transparency slider (true alpha transparency, so the background behind the badge shows through instead of being tinted lighter).
2.6.0
- Background and text color of every badge-shaped element (the “Badge” notice format, the overlay media caption, and the disclosure-level admin labels) can now be configured directly and independently for each disclosure level under Settings Style & info settings, instead of being derived automatically from the level color.
- Removed the automatic background-transparency/contrast system introduced in 2.5.0 in favor of the new direct background/text color controls, which give full manual control without relying on an automatic calculation.
2.5.1
- Fixed a follow-up to the 2.4.1 fix: on widgets that position their own image inside a fixed-height container (such as the Elementor Hotspot widget), the media caption is now always positioned on top of the image using that widget’s own container, instead of being placed in the normal text flow where it had no room and ended up overlapping the bottom edge of the image.
2.5.0
- Text and icon color of every badge-shaped element (the “Badge” notice format, the overlay media caption, and the disclosure-level admin labels) now switch automatically between the level color and white, based on which gives better contrast against the current background transparency setting. This keeps the disclosure readable no matter how the background transparency is configured, instead of risking text and background blending into each other at high transparency values.
- Raised the default badge background transparency from 10% to 60% – on busy background photos, the previous near-white default made the disclosure hard to notice (customer feedback).
2.4.1
- Fixed: with the overlay caption style, media captions injected into widgets that position their own image absolutely (e.g. hero/hotspot-style widgets in some page builders) could break that widget’s layout — the image would shrink to a small size while its container kept its full reserved height, leaving a large blank area. Affected images now receive the caption in the regular (non-overlay) style instead, which does not add a positioning wrapper.
2.4.0
- Added a badge appearance editor under Settings Style & info settings: border radius, border width, border style (solid, dashed or none) and background transparency are now configurable, and apply to every badge-shaped element throughout the plugin – the “Badge” notice format, the overlay media caption, and the disclosure-level labels shown in the admin lists – so they all share the same look everywhere.
- Renamed the “Notice texts & colors per level” section to “Style & info settings” to reflect its broader scope, and moved the “Shortcodes” section to the end of the settings page, right before the save button.
2.3.0
- Added optional external (third-party) visual AI image detection. Disabled by default; once enabled in Settings Automatic AI detection, you choose a provider (Sightengine or AI or Not) and enter its API credentials, and the image itself is sent to that provider for analysis. Because visual detectors can misclassify heavily edited or composited real photos as AI-generated, this signal alone can never trigger an automatic disclosure by itself – it only raises the scan score and always remains visible in the scan results for manual review.
- Widened the hex color code field in “Notice texts & colors per level” so the full value is visible instead of being cut off.
- Fixed the media scan progress bar gradient: it now runs from #0e2144 to #1f509d as intended, instead of fading into gold, which produced a desaturated, grayish patch in the middle of the bar.
2.2.1
- Added a hex color input next to each color picker in “Notice texts & colors per level”, so a color code can be typed or pasted directly instead of only using the swatch.
- Changed the media scan progress bar color from #0e2144 to #1f509d.
2.2.0
- Added configurable filename markers as a new, deliberate way to disclose AI involvement: if a media file name starts or ends with a marker you define per level (not case-sensitive), it is labeled automatically and independently of the metadata score. Default markers are provided (e.g. “KIU”/”KIG”/”KIGM” on German sites, “AIA”/”AIG”/”AIGR” otherwise) and can be changed or disabled per level in Settings Automatic AI detection.
2.1.3
- Fixed: on pages built with a page builder (Elementor, Divi, Bricks and similar), the media caption’s stylesheet was sometimes not loaded, so the caption appeared as unstyled plain text instead of the configured caption or overlay design. Page builders typically assemble the final page content only while rendering, not in the stored post content, which a preliminary content check used to miss. The plugin now loads its stylesheet whenever a labeled single view is displayed, independent of how the page content is assembled.
2.1.2
- Renamed the media caption placement setting to “Image placement” for clarity.
- Added a live preview to the media notices settings, showing a sample image with the caption exactly as it would appear on the site (below the image or as an overlay in the selected corner), updated instantly as you change the settings.
- The overlay caption on images now uses the same badge style (rounded pill, colored border and text) as the “Badge” notice format, instead of its previous solid dark background.
2.1.1
- Fixed: the plugin’s own interface still followed the profile language of the logged-in administrator instead of the website’s language in some cases. Since WordPress 6.7, translations are loaded on demand and the responsible internal locale is shared across all plugins; the fix now translates this plugin’s strings independently of that shared state.
- Fixed: the legal reference text on media captions (“Disclosure under Article 50 of the EU AI Act”) was still shown permanently next to the label instead of only on hover/focus; only the disclosure level itself remains visible now, the reference is reachable via the same tooltip pattern used elsewhere in the plugin.
- The display settings are now split into two separate cards, “Content notices” and “Media notices”, so the format/position of text notices and the placement of media captions (including the overlay option) can be configured independently and are easier to find.
2.1.0
- The plugin language now always follows the website’s configured language (German for “Deutsch” and “Deutsch (Sie)”, English for everything else), instead of the profile language of the currently logged-in administrator.
- Added bulk actions to the Media Library list view: apply an AI disclosure level to multiple selected files at once, or remove it.
- On the classic attachment edit screen, the AI disclosure field now appears in its own panel next to the image instead of full-width below Alt Text/Caption/Description.
- The scan analysis details (score and detected signals) are now shown as a tooltip on an info icon instead of a permanently visible paragraph.
- Added an optional overlay display: the media caption can now be placed directly on the image or video (5px margin, selectable corner) instead of below it as a caption.
- Fixed: a C2PA manifest value for digitalSourceType could run together with an unrelated, immediately following manifest field (e.g. “trainedAlgorithmicMediaparameters”) because the raw byte scan had no reliable separator between them. Detection is now bounded to the known IPTC digitalSourceType vocabulary, which also broadens recognized values beyond the previous, narrower fallback.
2.0.1
- Fixed: the media scan stopped after the very first batch on any library with more than 25 media items. The scan token was passed through sanitize_key(), which lowercases it, so every follow-up request was rejected as an expired session. Tokens now keep their original case.
- Fixed: a scan that stopped in the browser left its lock in place, so the next attempt was rejected with “A media scan is already running” while the progress bar stayed empty. The lock is now released whenever a scan fails, it expires after two minutes instead of five, and the screen offers to take over a stuck scan.
- Fixed: each request now works within a ten second time budget and resumes from its cursor, so long requests no longer hit the server time limit on larger media libraries.
- Fixed: a single unreadable or damaged file no longer aborts the whole scan; such files are counted and reported separately.
- An interrupted scan can be resumed where it stopped, and the scan screen detects this state when the page loads.
- Invalid server responses now produce a clear message instead of a technical error.
- Batch size reduced from 40 to 25 media items per request; leaving the page releases the scan lock.
2.0.0
- Renamed to “WP EU AI Act Labeler” for the public release. Plugin slug and text domain are now wp-eu-ai-act-labeler.
- All interface strings are now maintained in English, with a complete German translation bundled. Further languages can be contributed through translate.wordpress.org.
- Package prepared for the WordPress Plugin Directory: English readme, documented third-party licenses, development files excluded from the release build.
- Settings, disclosure levels and stored scan data are preserved when upgrading from version 1.x; only the plugin folder name changes.
1.5.0
- Transparent scoring system for AI detection: weighted signals, a score from 0 to 100 and four confidence levels. Weak hints such as a file name can never trigger a label on their own, and contradicting metadata lowers the score.
- Threshold for automatic labeling configurable in the settings and per attachment via filter.
- New filters: wpaal_scanner_signals, wpaal_scanner_score, wpaal_auto_label_threshold, wpaal_should_auto_label and wpaal_known_ai_generators.
- Scan results are stored in a structured form. Already labeled media can be re-evaluated without overwriting existing labels.
- Metadata is parsed per format instead of searching raw file text, so an article about AI tools no longer produces a false positive.
- Security: hardened file access restricted to the uploads directory, stream wrappers rejected, reads limited so large files are never loaded completely into memory.
- Security: every AJAX endpoint verifies both a nonce and a capability, input is validated against allow lists, and scan data is stored as JSON.
- Fixed: dismissing one notice suppressed every other notice on the site.
- Fixed: modal dialogs shared identical element IDs; focus handling and Escape behavior corrected.
- Fixed: captions could be inserted inside links; they are now placed using block attributes and the WordPress HTML API.
- The media scan uses cursor pagination, can be canceled and is locked against parallel runs.
- Statistics use COUNT(DISTINCT) and the cache is cleared on more events.
- Admin assets load only on the screens that need them. Chart.js updated to 4.5.1.
- Minimum requirement raised to WordPress 6.5.
1.4.0
- New “About this plugin” card on the dashboard.
- New primary shortcode [wp_eu_ai_act_labeler]; the former [ai_act_label] remains available as an alias.
- Clearer wording in the settings.
1.3.2
- Fixed: the media scan reported already labeled media again.
1.3.1
- Fixed: AI images carrying C2PA, XMP or IPTC metadata were not detected.
- Two-stage assessment for unambiguous versus uncertain findings.
1.3.0
- Live preview of the transparency notices in the settings.
- New position “Shortcode (manual placement)”.
1.2.0
- Media notices appear as a caption directly below the image, video or audio element.
- New dashboard card explaining the placement rules of the EU AI Act.
- Default position for text notices is now above the content.
1.1.0
- Automatic check on media upload.
- AI notice for images inside content, independent of the label of the post itself.
- Colors per disclosure level configurable.
1.0.0
- Initial release with four disclosure levels, four notice formats, shortcode, compliance dashboard and AI media scan.
