Skip to content

Fix blank plugin icons in the Plugins window#209

Merged
AllTerrainDeveloper merged 1 commit into
trunkfrom
fix-plugins-image-icons
May 14, 2026
Merged

Fix blank plugin icons in the Plugins window#209
AllTerrainDeveloper merged 1 commit into
trunkfrom
fix-plugins-image-icons

Conversation

@AllTerrainDeveloper
Copy link
Copy Markdown
Collaborator

@AllTerrainDeveloper AllTerrainDeveloper commented May 14, 2026

Closes #201

Problem

In the Installed tab of the native Plugins window, only ~6 of 34 plugin
rows showed an icon — the rest rendered a blank grey square (the empty
container before the dashicon fallback painted, or the dashicon
itself).

Root cause

desktop_mode_plugins_window_field_icon_url derived the wp.org icon
URL from $row['textdomain']:

$slug = sanitize_key( $row['textdomain'] ?? '' );
return 'https://ps.w.org/' . $slug . '/assets/icon.svg';

But the wp.org asset directory at ps.w.org/<slug>/assets/ is keyed on
the plugin's folder name (the .org repo slug), not its text
domain. These coincide only by accident:

  • woocommerce/woocommerce.php has text domain woo → 404
  • wordpress-seo/wp-seo.php has text domain yoast-seo → 404
  • Plugins with no Text Domain: header at all → null → no icon attempted

A secondary issue: the URL always pointed at icon.svg, but many
wp.org plugins ship only icon-128x128.png / icon-256x256.png. Even
when the slug was right, an SVG-only URL would 404 for those plugins.

There was no race condition, no transient, no fetch error — just a
wrong slug source plus a single-format URL.

Fix

PHP — includes/plugins-window/rest-fields.php

Resolve the slug from dirname( $plugin_file ) (the folder name).
Fall back to textdomain for single-file plugins like hello.php
where the folder is ..

JS — new helper src/plugins-window/icon-fallback.ts

attachIconFallback( img, url, onExhausted ) wires an <img> to walk
a candidate chain on load failure:

icon.svg → icon-256x256.png → icon-128x128.png → onExhausted()

Only applied when the URL matches the ps.w.org/<slug>/assets/
shape — custom URLs (set via the desktop_mode_plugins_window_icon_url
filter) are still one-shot. Wired into both consumers:

  • src/plugins-window/installed-view.ts (table icon)
  • src/plugins-window/installed-detail.ts (expanded-row hero icon)

Also corrected deriveSlug() in installed-detail.ts, which fed the
wp.org detail panel (fetchPluginInfo, reviews tab, etc.) and had the
same textdomain-first bug.

Tests

Updated tests/phpunit/tests/pluginsWindowRegistration.php to cover
the new behaviour:

  • Derives from folder name (was: from textdomain — only coincidentally passing for akismet).
  • Folder wins over a mismatched textdomain (woocommerce vs woo).
  • Works when no textdomain is declared (the dominant cause of the regression).
  • Single-file plugins fall back to textdomain; null when neither is available.

Verification

  • npm run build — clean
  • npm run lint — clean
  • ./node_modules/.bin/tsc --noEmit — clean
  • npm run test:js — 137 files / 1208 tests pass
  • PHPUnit --filter='PluginsWindowRegistration' — 29 tests / 54 assertions pass (incl. 5 icon-url cases)

Files changed

  • includes/plugins-window/rest-fields.php
  • src/plugins-window/icon-fallback.ts (new)
  • src/plugins-window/installed-view.ts
  • src/plugins-window/installed-detail.ts
  • tests/phpunit/tests/pluginsWindowRegistration.php
  • assets/js/desktop[.min].js (build output)
Open WordPress Playground Preview

@github-actions
Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@AllTerrainDeveloper AllTerrainDeveloper merged commit fa67e78 into trunk May 14, 2026
5 checks passed
@AllTerrainDeveloper AllTerrainDeveloper deleted the fix-plugins-image-icons branch May 14, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing plugin images

1 participant