https://github.com/troychaplin/attached-media-audit
A plugin that audits media usage on a WordPress site
https://github.com/troychaplin/attached-media-audit
Last synced: about 1 month ago
JSON representation
A plugin that audits media usage on a WordPress site
- Host: GitHub
- URL: https://github.com/troychaplin/attached-media-audit
- Owner: troychaplin
- Created: 2026-06-13T13:27:57.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-06-16T04:22:47.000Z (about 2 months ago)
- Last Synced: 2026-06-16T05:24:23.369Z (about 2 months ago)
- Language: PHP
- Size: 2.32 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README

# Attached: Media Audit
A WordPress plugin that audits your media library — showing which files are used, where they appear, and which are safe to clean up.
## Features
- **Background scanner** indexes every post's media references via WP-Cron, with live progress feedback
- **React admin UI** built with `@wordpress/dataviews` — sortable columns, filters, pagination, and bulk actions
- Detects references in **Gutenberg blocks**, **classic editor** HTML, **featured images**, and **post meta**
- Flags images with **missing alt text** in content (separate from the Media Library alt field)
- **Used In** popover shows every post referencing an attachment with a direct edit link
- Row actions: **Edit**, **View**, **Download**, **Delete Permanently**
- **Clear Index** resets scan data; **Scan Now** triggers a fresh full scan
## Requirements
- WordPress 6.6+
- PHP 8.0+
- Node.js + pnpm (for development builds)
## Installation
1. Copy the `attached-media-audit` folder into `wp-content/plugins/`.
2. Run `pnpm install && pnpm build` from the plugin root.
3. Activate the plugin in **Plugins → Installed Plugins**.
4. Navigate to **Media → Media Audit**.
5. Click **Scan Now** to index your media library.
## Admin UI
Found under **Media → Media Audit**. The table shows all attachments with the following columns:
| Column | Notes |
|---|---|
| Preview | Thumbnail or file-type icon |
| File Name | Links to edit; hover reveals row actions |
| Type | Image / Video / Audio / Document — filterable |
| Location | Block / Classic Editor / Featured Image / Post Meta — filterable |
| Usage | Used / Unused — filterable |
| Used In | Count of referencing posts; click to open a popover list |
| Size | File size in human-readable format — sortable |
| Alt Text | "No alt" badge when an image is embedded without alt text |
| Date | Upload date — sortable |
### Scan states
- **Scan required** — the index has not been built yet (or was cleared)
- **Unused** — the index is built and this file has no detected references
- **N posts** — the file is referenced by N posts; click to see them
## Architecture
### Scanner
The scanner runs as a WP-Cron job in batches of 50 posts. It indexes four reference types:
| Type | Source |
|---|---|
| `block` | Gutenberg block attributes (`core/image`, `core/cover`, `core/gallery`, etc.) |
| `classic` | `
` and `` tags in classic editor HTML |
| `featured_image` | `_thumbnail_id` post meta |
| `postmeta` | Other meta keys returning attachment IDs (configurable via `media_audit_scanned_meta_keys` filter) |
Alt text detection for block images reads the rendered `
` in `innerHTML` via `WP_HTML_Tag_Processor`, not the block's JSON attributes (which don't store alt for `core/image`).
### REST API
```
GET /wp-json/attached-media-audit/v1/media
```
Parameters: `page`, `per_page`, `search`, `orderby` (`title|date|usage|file_size`), `order` (`asc|desc`), `type_filter`, `ref_filter`, `usage_filter` (`used|unused`).
Returns server-paginated results with `X-WP-Total` and `X-WP-TotalPages` headers.
### Database
Table: `{prefix}media_audit_index`
| Column | Type | Description |
|---|---|---|
| `id` | `bigint` | Auto-increment primary key |
| `attachment_id` | `bigint` | Foreign key to `wp_posts.ID` |
| `source_post_id` | `bigint` | Post where the reference was found |
| `reference_type` | `varchar(20)` | `block`, `classic`, `featured_image`, or `postmeta` |
| `missing_alt` | `tinyint(1)` | 1 when the image is used in content without alt text |
| `last_scanned` | `datetime` | When this row was last written |
File sizes are cached in post meta (`_Attached_Media_Audit_filesize`) on the first scan to support fast SQL `ORDER BY`.
### DB versioning
The DB version is tracked in the `Attached_Media_Audit_db_version` option. Bumping `ATTACHED_MEDIA_AUDIT_VERSION` in `attached.php` triggers `dbDelta()` automatically on the next page load via `Plugin::maybe_upgrade_db()`.
## Development
```bash
pnpm install
pnpm build # production build
pnpm start # watch mode
```
**Entry:** `src/media-audit/index.js`
**Output:** `build/media-audit-admin.{js,css,asset.php}`
**Webpack:** extends `@wordpress/scripts` defaults via `webpack.config.js`
### Key source files
```
includes/
db/class-index-table.php — schema, get_attachments_rest(), replace_for_post()
rest/class-media-controller.php — REST endpoint, prepare_item()
scanner/class-block-parser.php — Gutenberg block attachment extraction
scanner/class-classic-parser.php — Classic editor HTML parsing
scanner/class-meta-parser.php — Post meta scanning
scanner/class-post-scanner.php — Orchestrates parsers, writes to index
scanner/class-batch-runner.php — WP-Cron batching, progress tracking
admin/class-ajax-handler.php — AJAX actions (scan, progress, locations, clear)
admin/class-admin-menu.php — Asset enqueue, wpMediaAudit JS global
class-plugin.php — Bootstrap, DB upgrade check, hooks
src/media-audit/
App.js — DataViews component, field definitions
hooks/useMediaAudit.js — REST fetch with AbortController
hooks/useScanProgress.js — AJAX polling, scan state machine
components/ScanToolbar.js — Scan Now + Clear Index + progress bar
components/ThumbnailCell.js — Image preview or dashicon fallback
components/TitleCell.js — Filename + hover row actions
components/UsedInCell.js — Toggleable Popover listing source posts
styles.scss
```
## Hooks
**`media_audit_scanned_meta_keys`** — Filter the list of post meta keys the scanner checks for attachment IDs.
```php
add_filter( 'media_audit_scanned_meta_keys', function( array $keys ): array {
$keys[] = 'my_custom_image_field';
return $keys;
} );
```
## License
GPL-2.0-or-later — see [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html)