JPKCom Post Filter — Anleitung & Tipps

Facettierte Taxonomie-Filter für Beiträge, Seiten und Custom Post Types mit JPKCom Post Filter — SEO-freundliche URLs, AJAX, Shortcodes, Blocks und Page-Builder-Support.

JPKCom Post Filter ergänzt jede WordPress-Archivseite um facettierte Taxonomie-Filter — für Beiträge, Seiten und beliebige Custom Post Types. Jeder Filterzustand steckt in einer SEO-freundlichen URL wie /blog/filter/marketing+web-design/wordpress/, ist also teilbar und bookmarkbar; gefiltert wird per AJAX, mit vollwertigem Fallback ohne JavaScript.

Anleitung

Voraussetzungen

  • WordPress 7.0 oder neuer (getestet bis WordPress 7.1)
  • PHP 8.3 oder neuer
  • Keine Abhängigkeiten: kein ACF, kein Bootstrap, kein jQuery nötig

Page-Builder-Unterstützung ist optional — die Gutenberg-Blocks, Elementor-Widgets und Oxygen-Elemente werden nur geladen, wenn der jeweilige Builder aktiv ist.

Installation

  1. Lade das Verzeichnis jpkcom-post-filter nach /wp-content/plugins/ hoch (oder installiere die ZIP via Plugins → Installieren → Plugin hochladen).
  2. Aktiviere das Plugin unter Plugins → Installierte Plugins.
  3. Wähle unter Post Filter → General die Post-Types, die gefiltert werden sollen.
  4. Lege unter Post Filter → Filter Groups die Taxonomien als Filter-Dimensionen an.
  5. Öffne Einstellungen → Permalinks und klicke auf Änderungen speichern, um die Rewrite-Regeln neu zu schreiben.

Filter-Gruppen konfigurieren

Jede Filter-Gruppe ordnet eine Taxonomie einer URL-Position zu; die Reihenfolge der Gruppen bestimmt die Reihenfolge der URL-Segmente. Pro Gruppe legst du Taxonomie, Label, betroffene Post-Types, numerische Sortierung (Order) und einen Ein/Aus-Schalter (Enabled) fest.

Du kannst direkt aus der Filter-Groups-Seite neue Taxonomien registrieren (Option „Register as new WordPress taxonomy"): Slug, Rewrite-Slug, hierarchisch/flach, öffentlich, Admin-Spalte und REST-API-Sichtbarkeit (für Gutenberg nötig) sind einstellbar.

Achtung: Löschst du eine Filter-Gruppe, die eine eigene Taxonomie registriert hat, wird diese Taxonomie deregistriert — alle Term-Zuweisungen an Beiträge gehen dabei unwiderruflich verloren.

Ausgabe: Auto-Inject, Shortcodes, Blocks

Es gibt drei Wege, Filter und Liste auszugeben:

  • Auto-Inject (General → Auto-Inject Filter): Das Plugin umschließt die Archiv-Schleife des Themes automatisch mit der Filter-UI — ohne Shortcode oder Template-Eingriff. Es hängt sich dafür an loop_start/loop_end auf Archiv-/Blog-Seiten (nicht auf Einzelbeiträgen, Suche oder Seiten). In Block-Themes wie Twenty Twenty-Five druckte das die Filterleiste bis 1.7.0 vor <!DOCTYPE html>; seit 1.7.1 legt das Plugin dort sein Markup um die Beitragsvorlage des Query-Blocks, der die Hauptabfrage erbt. Die Query-Paginierung des Themes weicht dann der des Plugins, und ohne Treffer stehen Filterleiste und Hinweis an der Stelle des „Keine Ergebnisse“-Blocks. Klassische Themes bekommen genau dieselbe Ausgabe wie vorher; umschalten lässt sich der Weg über den Filter jpkcom_postfilter_inject_via_blocks.
  • Shortcodes: [jpkcom_postfilter_filter], [jpkcom_postfilter_list] und [jpkcom_postfilter_pagination]. Der interaktive Shortcode-Builder unter Post Filter → Shortcodes erzeugt fertige Snippets.
  • Builder: je drei Gutenberg-Blocks, Elementor-Widgets und Oxygen-Elemente (Post Filter, Post List, Post Pagination).
[jpkcom_postfilter_filter post_type="portfolio" layout="dropdown"]
[jpkcom_postfilter_list post_type="portfolio" layout="cards" limit="12"]
[jpkcom_postfilter_pagination post_type="portfolio"]

Wichtig: Shortcodes (und Blocks/Widgets/Elemente) hängen immer am Archiv des konfigurierten Post-Types. Sie funktionieren nur auf einer Archiv-Template-Seite oder auf der unter Einstellungen → Lesen als „Beitragsseite" gesetzten Seite. Auf einer beliebigen Seite (z. B. /test/) springen Filter-Klicks und AJAX zur Archiv-URL zurück — nutze dort lieber Auto-Inject.

Layout & Design

Unter Post Filter → Layout & Design stehen sechs Tabs bereit (Global, Filter, Posts, Pagination, Color Schemes, Advanced). Du wählst aus vier Filter-Layouts (Bar, Columns, Sidebar, Dropdown), drei Listen-Layouts (Cards, Rows, Minimal) und vier Farbschemata (Default, Dark, Contrast, Monochrome). Im Tab Advanced legst du u. a. den Stylesheet-Modus (Full / Variables only / Disabled), den Plus/Minus-Modus, die Sichtbarkeit des Reset-Buttons sowie eigenes Custom CSS fest.

Crawler & SEO: eine URL je Filterauswahl

Seit 1.5.0 hat jede Filterauswahl genau eine Adresse. Bis dahin nahm das Plugin jede Schreibweise einer Auswahl an und lieferte sie mit HTTP 200 aus: b+a, a+b und a+a+b waren drei eigenständige Seiten mit jeweils eigenem Canonical, und jeder Term-Link der Filterleiste zeigte ohne Grenze auf „aktuelle Auswahl + dieser Term“ in Klick-Reihenfolge. Crawler, die Links folgen, fanden so aus wenigen Termen Millionen Adressen; auf einer Produktivsite kamen rund 840 000 Filter-Anfragen am Tag bei WordPress an, jede URL einmalig, jede ein vollständiger Seitenaufbau. Jetzt ist der URL-Raum endlich: Bei zehn Termen in jeder von zwei Gruppen sind 20 Ein-Term-Seiten crawlbar statt zig Millionen Kombinationen. Von Inhalten, vom Archiv und von den Ein-Term-Seiten wird dabei kein Crawler ausgesperrt.

Was sich nach dem Update auf einer laufenden Site ändert:

  • Eine Schreibweise, 301 für alle anderen: Die Terme einer Gruppe werden sortiert und doppelte entfernt. Jede andere Schreibweise — b+a, a+a, Großbuchstaben, ein angehängtes _, …/page/1/ — beantwortet das Plugin mit einer 301 auf die kanonische URL. Bestehende Links und indexierte Adressen funktionieren über diese Weiterleitung weiter.
  • 410 statt gekürzter Seite: Eine URL jenseits der Limits — mehr Terme pro Gruppe als Max. Filters per Group, mehr Gruppen als Max Filter Combinations, mehr Segmente als Filter-Gruppen — bekommt eine 410 mit kurzer Fehlerseite, statt gekürzt gerendert zu werden. Terme, die es nicht (mehr) gibt, werden weggeleitet, auf die restliche Auswahl oder aufs Archiv. Beides passiert, bevor die Datenbank abgefragt wird. Mit WPML zählt dabei ein Term jeder Sprache als vorhanden. Seit 1.6.0 zählt Max. Filters per Group die Terme so, wie sie in der URL stehen, also bevor Duplikate entfernt werden: a+a+b bekommt bei einem Limit von 2 eine 410 statt einer 301 auf a+b. URLs, die das Plugin selbst baut, sehen nie so aus. Der Standard des Limits ist bei Neuinstallationen seit 1.6.0 2 statt 3; bestehende Installationen behalten beim Update 3, ein selbst gespeicherter Wert bleibt unangetastet.
  • Index nur, wo er hingehört: Filterseiten mit unbekannten Termen oder ohne Treffer sind noindex, nofollow ohne Canonical; dasselbe steht zusätzlich im Header X-Robots-Tag. Von 1.5.0 bis 1.6.1 galt das standardmäßig (Indexable Filter Pages = 1) auch für alle Seiten mit mehr als einem Term, während Ein-Term-Seiten index, follow mit Canonical auf sich selbst blieben. Seit 1.7.0 zeigt das Canonical standardmäßig auf das Archiv des Terms bzw. bei Kombinationen auf die ungefilterte Blogseite — Details unter Canonical-Ziele weiter unten.
  • Andere Term-Links ohne JavaScript: Für Clients ohne JavaScript — in der Praxis Crawler — verlinkt ein Term-Button auf seine eigene Ein-Term-Seite. Terme, deren Link nirgends sinnvoll hinführen würde, erscheinen als <button type="button"> statt <a>, optisch gleich. Mit JavaScript merkst du nichts davon: Das Skript kombiniert Terme weiter frei. Ohne JavaScript wechselt ein Klick jetzt auf diesen Term, statt ihn hinzuzufügen; das alte Verhalten (jetzt begrenzt und sortiert) bekommst du mit Filter Links for Crawlers = „Combinations within the limits“.
  • Ein Canonical statt zwei: Mit Rank Math oder Yoast stand bis 1.4.7 auf jeder Filterseite neben dem Canonical des SEO-Plugins, das auf die Blogseite zeigt, ein zweites des Plugins. Seit 1.5.0 reicht das Plugin sein Canonical über das SEO-Plugin durch, es gibt genau eins. Rank Math lässt auf noindex-Seiten außerdem sein Canonical und die prev/next-Links weg.
  • robots.txt: Das Plugin hängt an die virtuelle robots.txt von WordPress Disallow: …/filter/*+ an — gesperrt sind also nur Segmente mit mehr als einem Term, für jeden Crawler gleich —, dazu Zeilen für das Fragment-Segment und die internen Query-Variablen jpkcom_filter_*. Mit WPML stehen die Regeln für jedes Sprachpräfix drin (/filter/*+, /en/filter/*+). Das wirkt nur, wenn der Webserver /robots.txt an WordPress weiterreicht; für eine physische Datei im Webroot zeigt die Einstellungsseite die Zeilen zum Hineinkopieren.
  • Paginierung: Seite 1 wird nicht mehr als …/page/1/ verlinkt, ein Duplikat der Seite, das bis 1.4.7 jede paginierte Liste enthielt.
  • Interne Query-Variablen nur aus der URL-Regel: ?jpkcom_filter_path=… (und die Fragment- und Post-Type-Variable) im Query-String machte bis 1.4.7 jede beliebige URL zu einer Filter- oder Fragment-Antwort, an allen pfadbasierten Regeln vorbei. Seit 1.5.0 übernimmt das Plugin sie nur noch aus der passenden Rewrite-Regel.

Die Einstellungen stehen unter Post Filter → General → Crawlers & SEO. Jede lässt sich auch per Konstante in der wp-config.php festschreiben, das Feld erscheint dann gesperrt:

Option Standard Konstante Wirkung
Filter Links for Crawlers Single terms only JPKCOM_POSTFILTER_LINK_MODE (single, combo, none) Ziel der Term-Links ohne JavaScript: Ein-Term-Seite, Kombination innerhalb der Limits oder kein Link
Indexable Filter Pages 1 JPKCOM_POSTFILTER_INDEX_MAX_TERMS Filterseiten bis zu so vielen Termen sind index, follow; 0 = keine
Canonical: Single Term Term archive JPKCOM_POSTFILTER_CANONICAL_SINGLE (archive, filter, off) Canonical einer Ein-Term-Seite: Archiv des Terms, die Filterseite selbst oder gar keins (seit 1.7.0)
Canonical: Multiple Terms Archive base JPKCOM_POSTFILTER_CANONICAL_MULTI (home, filter, off) Canonical einer Seite mit zwei oder mehr Termen: ungefilterte Blogseite bzw. Post-Type-Archiv, die Filterseite selbst oder gar keins (seit 1.7.0)
Max. Terms in Total 0 (aus) JPKCOM_POSTFILTER_MAX_TERMS_TOTAL Obergrenze über alle Gruppen zusammen, Filterleiste und Server setzen sie durch
Strict Filter URLs an JPKCOM_POSTFILTER_STRICT_URLS 301 auf die kanonische Schreibweise, Ablehnung unmöglicher Pfade vor der Datenbankabfrage
Invalid Combinations 410 JPKCOM_POSTFILTER_REJECT_STATUS Status für Pfade jenseits der Limits, 410 oder 404
Unknown Terms in URLs Redirect JPKCOM_POSTFILTER_UNKNOWN_TERMS_ACTION (redirect, noindex, reject) Weiterleiten auf die Auswahl ohne sie, Seite mit noindex zeigen oder ablehnen
Query Strings on Filter URLs aus JPKCOM_POSTFILTER_STRIP_QUERY 301 von /filter/x/?a=b auf /filter/x/; benötigte Parameter behältst du über den Filter jpkcom_postfilter_strip_query_allowlist
robots.txt Rules Disallow combinations JPKCOM_POSTFILTER_ROBOTS_TXT (deep, all, off) Kombinationen sperren, den ganzen Endpunkt sperren oder keine Regeln

Steht Indexable Filter Pages auf 0, sperrt „Disallow combinations“ den ganzen Endpunkt; ab 2 schreibt es nur noch die Zeilen für Fragment-Segment und Query-Variablen, weil sich die indexierbaren Seiten per Platzhalter nicht mehr abgrenzen lassen. Seit 1.5.0 lassen sich auch die bestehenden Limits per Konstante festschreiben: JPKCOM_POSTFILTER_MAX_FILTERS_PER_GROUP und JPKCOM_POSTFILTER_MAX_FILTER_COMBOS. Jeder dieser Werte läuft zusätzlich durch einen Filter jpkcom_postfilter_{key}, etwa jpkcom_postfilter_filter_link_mode; weitere Hebel sind u. a. jpkcom_postfilter_request_verdict, jpkcom_postfilter_robots_directives, jpkcom_postfilter_canonical, jpkcom_postfilter_term_href, jpkcom_postfilter_term_link_rel und jpkcom_postfilter_robots_txt_rules.

Achtung bei eigenen Filter-Templates: Überschreibst du partials/filter/*.php im Theme, funktioniert das weiter — dein Override erzeugt aber die alten, unbegrenzten Links, bis er wie die mitgelieferten Templates jpkcom_postfilter_term_link_attributes() aufruft.

Canonical-Ziele (seit 1.7.0)

Eine Ein-Term-Seite wie /blog/filter/seo/ zeigt dieselben Beiträge wie das Archiv /category/seo/. Seit 1.7.0 zeigt ihr Canonical deshalb standardmäßig dorthin, das einer Kombination auf die ungefilterte Blogseite. Die beiden Felder Canonical: Single Term und Canonical: Multiple Terms (Tabelle oben) steuern das. Mit den Standardwerten gilt:

  • Ein Term: Canonical auf das Archiv des Terms (/category/…/, /tag/…/ oder das Archiv einer eigenen Taxonomie), die Seite bleibt index, follow. Das gilt nur, wenn dieses Archiv öffentlich ist, den gefilterten Post-Type listet, in Rank Math oder Yoast nicht auf noindex steht und dort kein manuelles Canonical hat, das woandershin zeigt — sonst bleibt das Canonical auf der Filterseite. Rank Math setzt Tag-Archive ab Werk auf noindex; Tag-Filterseiten behalten dort also ihr eigenes Canonical.
  • Zwei oder mehr Terme: Canonical auf die ungefilterte Blogseite bzw. das Post-Type-Archiv, und die Seite ist index, nofollow statt noindex, nofollow. Der Grund: Ein noindex neben einem Canonical, das woandershin zeigt, widerspricht sich, und Suchmaschinen können das noindex auf das Ziel übertragen. Steht die Blogseite selbst auf noindex, hat sie ein manuelles Canonical woandershin oder hat die Site eine statische Startseite ohne Beitragsseite, ändert sich für diese Seiten nichts. Mit den Standard-Regeln der robots.txt bleiben Kombinationen innerhalb einer Gruppe ohnehin gesperrt, Crawler sehen ihr Canonical also nie.
  • Seite 2 und folgende: Seit 1.7.1 hat eine paginierte Ein-Term-Seite ein Canonical auf sich selbst (…/filter/seo/page/2/), wie Google es für paginierte Seiten empfiehlt; Seite 1 zeigt weiter auf das Archiv bzw. die Blogseite. Seite 2 und folgende einer Kombination sind damit wieder noindex, nofollow ohne Canonical, wie in 1.6.1. In 1.7.0 zeigte das Canonical einer paginierten Filterseite noch auf Seite 1 ihres Ziels.
  • Hinter der letzten Seite: /blog/filter/seo/page/99/ antwortet seit 1.7.1 mit 404, wie /blog/page/99/ und /category/seo/page/99/. Vorher schickte der Canonical-Redirect von WordPress sie per 301 auf die ungefilterte Blogseite. Filterseiten, die es gibt, behalten die Weiterleitungen von WordPress (etwa bei fehlendem Schrägstrich am Ende).
  • Rank Math und Yoast: Seit 1.7.1 erben Filterseiten kein noindex mehr von der Blogseite. Eine Ein-Term-Seite bleibt index mit Canonical auf das Term-Archiv, auch wenn die Blogseite selbst auf noindex steht. Zwei Einstellungen gewinnen weiterhin: Suchmaschinen davon abhalten, diese Website zu indexieren in WordPress und für Seite 2 und folgende Rank Maths noindex archive subpages sowie, auf Filterseiten des Blogs (nicht eines Post-Type-Archivs), noindex paginated pages. Eine Suche auf einer Filterseite (?s=) bleibt noindex, ein Antwort-Link auf einen Kommentar (?replytocom=) noindex, nofollow. Die rel="prev"/rel="next"-Links von Yoast erscheinen auf Filterseiten nicht mehr — sie zeigten in den ungefilterten Blog.

Filter URL in beiden Feldern stellt die Canonical-Ziele von vor 1.7.0 wieder her: Ein-Term-Seiten verweisen auf sich selbst, über Kombinationen entscheidet Indexable Filter Pages. Off lässt das Canonical ganz weg, dann kommt auch von Rank Math oder Yoast keins. Leere nach dem Update den Page-Cache: Zwischengespeicherte Filterseiten tragen das alte Canonical und die alten Robots-Angaben, bis sie neu gerendert werden.

Nutzt du ein anderes SEO-Plugin als Rank Math oder Yoast, oder listet dein Theme in einem Term-Archiv weniger als WordPress selbst, schließt du ein Archiv über jpkcom_postfilter_canonical_archive_indexable bzw. jpkcom_postfilter_canonical_base_indexable als Ziel aus. Die Filter laufen erst, wenn das Archiv die Prüfungen des Plugins bestanden hat; ein Archiv, das das Plugin verworfen hat (etwa weil Kategorie-Archive nach WordPress-Logik nur post listen), machen sie nicht zum Ziel. Der Filter jpkcom_postfilter_canonical bekommt seit 1.7.0 zusätzlich die Art des Ziels (archive, home oder self) und die aktiven Filter; gibt dein Callback seit 1.7.1 einen leeren String zurück, hat die Seite kein Canonical und behält das noindex, das ihrer Termzahl entspricht.

Eigenes Paginierungs-Template: Die mitgelieferte partials/pagination/pagination.php fragt seit 1.7.0 jpkcom_postfilter_should_nofollow() statt jpkcom_postfilter_should_noindex(). Ein älterer Override im Theme funktioniert weiter, setzt seine Seitenlinks auf Seite 1 einer Kombination (die mit Canonical auf die Blogseite index, nofollow ist) aber nicht mehr auf nofollow, bis er diese Zeile übernimmt.

Server-Regeln: unmögliche URLs vor PHP abweisen (seit 1.6.0)

Das Plugin weist Filter-URLs jenseits seiner Limits ab — aber erst, nachdem WordPress gestartet ist. Auf einer Produktivsite kosteten die abgewiesenen URLs nach 1.5.0 jede noch einen vollständigen WordPress-Start, zusammen rund 70 % der verbliebenen PHP-Last. Post Filter → Server Rules erzeugt aus den aktuellen Einstellungen Regeln, mit denen der Webserver diese URLs abweist, bevor PHP läuft, und Fluten von Filter-Anfragen bremst. Je einen Tab gibt es für nginx (einfacher Server-Block oder ISPConfig), Apache (Server-Konfiguration), Apache (.htaccess), Caddy/FrankenPHP, Ferron 3, Ferron 2.8 und OpenLiteSpeed; der Tab des Servers, auf dem PHP läuft, ist vorausgewählt. Jeder Tab enthält, soweit der Server es zulässt:

Teil Was er tut
Ablehnen vor PHP beantwortet Filter-URLs mit mehr Termen pro Gruppe oder mehr Segmenten, als das Plugin annimmt, mit demselben Status 410 (oder 404), dazu X-Robots-Tag: noindex, nofollow, X-Jpkpf-Reject, X-Jpkpf-Rule: <hash> und Cache-Control: no-store. Für Ferron 2.8 gibt es keine getestete Regel, auf OpenLiteSpeed fehlen die Header
robots.txt wie /robots.txt bei WordPress ankommt, damit die robots.txt-Zeilen des Plugins ausgeliefert werden
Rate-Limit nur für Filter-URLs ohne jedes Cookie: eine Obergrenze gleichzeitiger Renderings, eine siteweite Rate, eine Rate je IP und ein eigenes Budget für die großen Suchmaschinen, darüber 429 mit Retry-After. Abgeleitet aus zwei Werten, die du einträgst: PHP-Worker (Standard 8) und Renderzeit einer Filterseite (Standard 0,4 s). In .htaccess und auf OpenLiteSpeed nicht möglich, auf Ferron reduziert
Prüfen nach dem Einbau curl-Befehle, mit denen du die Regel testest
Page-Cache wann du ihn leeren musst

Die Regeln weisen nie eine URL ab, die das Plugin mit den aktuellen Einstellungen ausliefert. Von Hand eingesetzte Regeln ändern sich aber nicht von selbst, deshalb gilt die Reihenfolge auf der Seite: Verschärfst du die Einstellungen, änderst du zuerst das Plugin, leerst den Page-Cache und passt dann den Server an; lockerst du sie, zuerst den Server, dann das Plugin. Nach jedem Speichern (oder Import), das die Filter-URL-Einstellungen lockert, erinnert ein Hinweis an von Hand eingesetzte Server-Regeln — rot, wenn die letzte Prüfung sie als aktiv oder veraltet gefunden hat. Solange das Plugin den .htaccess-Block selbst verwaltet, entfällt der Hinweis.

Check now schickt ein paar Anfragen ohne Cookies an deine eigene Site und zeigt, ob robots.txt bei WordPress ankommt, ob die Server-Regel installiert und aktuell ist und — als roten Alarm — ob sie strenger ist als das Plugin. Das Rate-Limit testet die Prüfung nicht, denn das würde das Budget aufbrauchen, das sich alle Besucher ohne Cookie teilen. Erreicht die Site sich selbst nicht (HTTP-Authentifizierung, Firewall, Wartungsmodus), meldet das Ergebnis, dass nicht geprüft werden konnte, nie, dass die Regel fehlt. Seit 1.7.1 gilt das auch für die Prüfung, ob die Regel strenger ist als das Plugin: Eine Weiterleitung, die nicht vom Plugin kommt (auf https, auf einen anderen Hostnamen, von einem Proxy), meldet sie als nicht geprüft statt als nicht strenger. Eine solche Weiterleitung kann vor den Server-Regeln passieren und beweist nichts über sie.

Verwalteter .htaccess-Block (nur Apache). Auf Apache, als Einzelsite, mit beschreibbarer .htaccess und ohne DISALLOW_FILE_MODS schreibt das Plugin nach einem Klick auf Set up seinen Block selbst — direkt vor den Block von WordPress, zwischen # BEGIN JPKCom Post Filter und # END JPKCom Post Filter — und hält ihn ab dann bei jedem Speichern im wp-admin und beim Import mit den Einstellungen im Gleichschritt. Ein lockerndes wp option update unter WP-CLI lockert den Block, bevor die Änderung gespeichert wird; Verschärfungen über WP-CLI, wp-config.php-Konstanten oder Code-Filter folgen beim nächsten Aufruf einer Admin-Seite (bis dahin ist der Block nur lockerer als das Plugin, nie strenger). Ein Speichern, das lockern würde, weist das Plugin ab, solange der Block nicht nachziehen kann; die Fehlermeldung nennt Grund und Abhilfe. Remove oder das Deaktivieren des Plugins löscht den Block. Ohne diesen Klick schreibt das Plugin die Datei nie, und einen selbst eingefügten Block (erkennbar an den (manual)-Markern) fasst es nicht an. Auf LiteSpeed/OpenLiteSpeed und Multisite gibt es den verwalteten Block nicht.

Wird DISALLOW_FILE_MODS erst gesetzt, nachdem der Block eingerichtet ist (oder kommt die Site erst danach in ein Netzwerk), stoppen Set up, Verschärfungen und das Nachziehen beim Admin-Aufruf. Alles, was nur lockert, schreibt die noch beschreibbare Datei weiter: ein lockerndes Speichern, das Löschen der Optionen General oder Filter Groups, Remove, das Deaktivieren und die Reparatur nach einem Versionswechsel unter WP-CLI. Willst du das Plugin ganz aus der Datei heraushalten, klicke Remove; das geht auch mit gesetzter Konstante.

Seit 1.7.1 kommen drei Absicherungen dazu:

  • Nur ein Speichern auf einmal: Ein zweites Speichern von General oder Filter Groups (oder ein Import), während ein anderes den Block gerade zwischen Lockern und Verschärfen hat, weist das Plugin mit der Bitte ab, ein paar Sekunden zu warten und erneut zu speichern; gespeichert wird dabei nichts. Ein Import hält die Sperre vom ersten bis zum letzten Einstellungsblock und wird nie halb angewendet. Set up wird in dieser Zeit ebenfalls mit der Bitte abgewiesen, es erneut zu versuchen; die automatischen Block-Updates nach einem Versionswechsel oder einer geänderten Beitragsseite bzw. Permalink-Struktur schreiben dann nichts und werden später nachgeholt.
  • Nicht lesbare .htaccess: Ein lockerndes Speichern wird abgewiesen, und Deaktivieren oder Remove vergessen den Block nicht mehr — vorher behandelte das Plugin die unlesbare Datei wie eine ohne Block. Die Server-Rules-Seite zeigt die Datei als nicht lesbar.
  • Optionen löschen: wp option delete jpkcom_postfilter_general löscht jetzt auch die zugehörige Datei im Einstellungs-Cache; vorher lief das Plugin bis zum nächsten Speichern mit den gelöschten Werten weiter. Mit verwaltetem Block wird er zuerst für die Standardwerte gelockert. Geht das nicht (schreibgeschützte Datei, gerade laufendes Speichern, Datenbankfehler), weist das Plugin das Löschen von General oder Filter Groups ab: wp option delete endet mit Fehler und Exit-Code 1, jeder andere Aufruf mit HTTP 409. Löschst du die Zeile direkt in der Datenbank, greift das nicht.

Sehr viele Filter-Gruppen — 500 auf 1.6.0: Das Ablehnungsmuster wächst mit der Zahl der Filter-Gruppen und der Terme pro Gruppe. Ab 71 Filter-Gruppen bei 20 Termen pro Gruppe (ab 37, wenn die letzte Gruppe keine Taxonomie hat) kann PCRE es nicht mehr kompilieren: nginx verweigert dann die Konfiguration, und ein verwalteter .htaccess-Block lässt die ganze Site mit 500 antworten. Seit 1.6.1 lässt das Plugin eine solche Regel — und ebenso jede, die länger als 4000 Zeichen würde oder Caddy bzw. Ferron 3 überfordert — aus jedem Snippet, den curl-Befehlen, Check now und dem verwalteten Block weg, für jeden Server gleich, und die Server-Rules-Seite sagt, welche Regel und warum. Das ist lockerer, nie strenger — das Plugin weist diese URLs weiter selbst ab. Bis 8 Filter-Gruppen und 5 Terme pro Gruppe ließ 1.6.1 alle Regeln, den verwalteten Block und den Regel-Hash byteweise wie in 1.6.0. Hängt eine Site noch an einem solchen 1.6.0-Block, reicht seit 1.7.1 wp plugin update jpkcom-post-filter: Der nächste wp-Befehl läuft mit der neuen Version und schreibt den Block neu, aber nur, wenn der neue Block nachweislich ganze Regeln weglässt und sonst nichts ändert. Die Zeilen von Hand zu entfernen ist nicht mehr nötig. Warnt die Server-Rules-Seite bei deinen Einstellungen, dass eine Regel ausgelassen ist, ändert sich mit 1.7.1 außerdem der Regel-Hash: Check now meldet von Hand eingesetzte Regeln dann als veraltet, setze sie neu ein. Für alle anderen Einstellungen bleiben Regel-Hash und Ablehnungsregeln byteweise dieselben wie in 1.7.0.

Mit ModSecurity auf Apache schreiben seit 1.7.1 weder die 429-Antworten des ModSecurity-Rate-Limits noch die vom Server abgewiesenen Filter-URLs einen Eintrag ins Audit-Log; die eine Zeile eines 429ers im Error-Log bleibt. Gemessen mit der von Debian empfohlenen modsecurity.conf, mit und ohne OWASP CRS: vorher 1,7 MB Audit-Log je 1000 beantworteter 429er und 1 MB je 1000 Server-Ablehnungen, danach nichts. Dafür setzt du das ModSecurity-Snippet neu ein; Ablehnungs-Snippet und Limits bleiben unverändert.

Beiträge als Daten: die Abilities API

Seit Version 1.3.0 registriert das Plugin zwei nur lesende Abilities. Die Abilities API ist ein WordPress-Kernregister maschinenlesbarer Fähigkeiten — KI-Assistenten, MCP-Clients und REST-Automatisierung können damit Fragen zu deinen Beiträgen beantworten, ohne das Frontend auszulesen:

Ability Antwort
jpkcom-post-filter/list-filters nach welchen Taxonomien und Termen ein Post-Type gefiltert werden kann, mit Beitragszahl je Term
jpkcom-post-filter/query-posts eine gefilterte, seitenweise Query samt Treffern und teilbarer Filter-URL

Der Sinn des Paares: Die erste Ability nimmt der zweiten das Raten ab. Ein Assistent schlägt die echten Taxonomie-Schlüssel und Term-Slugs nach und filtert dann mit Werten, die es gibt. Ein Filter, der eine Taxonomie nennt, die deine Site nicht hat, wird mit einer Fehlermeldung samt Liste der gültigen abgewiesen — er liefert nie still das komplette Archiv zurück.

Beide sind unter /wp-json/wp-abilities/v1/ erreichbar und antworten auf GET:

GET /wp-json/wp-abilities/v1/abilities/jpkcom-post-filter/query-posts/run
      ?input[post_type]=post&input[filters][category][]=allgemein&input[per_page]=3

query-posts nimmt post_type, filters, page, per_page (1–50) und search an. Terme innerhalb einer Taxonomie werden mit ODER verknüpft, verschiedene Taxonomien mit UND — dieselbe Logik wie die Filterleiste auf deiner Site. Ein Term-Slug, der auf nichts passt, ist kein Fehler: Die Query läuft, und der Slug erscheint unter unknown_terms, sodass ein Aufrufer einen Tippfehler von einem leeren Ergebnis unterscheiden kann.

Wichtig für die Interpretation: filter_url kommt leer zurück, sobald keine Frontend-Adresse genau die Beiträge zeigen würde, die daneben stehen. Das sind sechs Fälle: ein Post-Type ohne Archivseite; eine Filter-Kombination jenseits deiner konfigurierten Limits; eine Seite nach der ersten mit einer anderen Seitengröße als der Site-eigenen „Beiträge pro Seite"; eine Seite hinter der letzten; seit 1.3.1 ein angewendeter search-Begriff, denn eine Filter-URL kann keinen Suchbegriff tragen und würde eine größere, andere Liste zeigen; und seit 1.5.0 ein Term-Slug, den es nicht gibt, weil die Website diese URL auf eine andere Auswahl umleitet (außer Unknown Terms in URLs steht auf noindex oder Strict Filter URLs ist aus). Die Ergebnisse sind in allen diesen Fällen vollständig; nur der Link fehlt.

Was ausgegeben wird und an wen:

  • Als Grundregel ausschließlich veröffentlichte Beiträge: Der Beitragsstatus steht fest im Code, ein Aufrufer kann ihn nicht als Parameter setzen. Das ist aber keine Garantie, und die Entwickler-Doku des Plugins sagt das seit 1.3.1 ausdrücklich: post_status ist eine gewöhnliche Query-Var, und wer die Query vorher anfasst — ein anderes Plugin über pre_get_posts ohne is_main_query()-Schutz oder der dokumentierte Filter jpkcom_postfilter_query_args — verändert, was zurückkommt. Ein zweites Lese-Gate hinter der Query gibt es hier nicht (das Schwester-Plugin jpkcom-acf-jobs hat eines). Auf einer Site mit solchen Eingriffen also nicht darauf verlassen, dass Entwürfe unerreichbar sind.
  • Nur Post-Types, die du fürs Filtern freigeschaltet hast, und nur Taxonomien, die du als Filter-Gruppen konfiguriert hast.
  • Ausführen erfordert einen angemeldeten Benutzer mit der Fähigkeit read. Die bloße Liste der Abilities samt Beschreibungen und Parameter-Schemata sieht dagegen jeder angemeldete Benutzer, auch ein Abonnent — so schirmt WordPress jede Ability ab, das ist nichts Plugin-Spezifisches.

Abschalten in der wp-config.php:

define( 'JPKCOM_POSTFILTER_ABILITIES', false );

Fähigkeit anheben, Abilities aber behalten:

add_filter( 'jpkcom_postfilter_ability_capability', static fn(): string => 'edit_posts' );

Willst du sie aus MCP-Clients heraushalten, die REST-Route aber behalten (oder umgekehrt), ist jpkcom_postfilter_ability_meta der richtige Hebel.

Tipps & Tricks

  • Cache-Leerung wirkte nicht vollständig — behoben in 1.3.1: Das Leeren des Caches räumte die schnellste Schicht nie mit ab. Speichern eines Beitrags, Bearbeiten eines Terms, Speichern der Einstellungen und beide „Clear cache"-Buttons im Admin meldeten Erfolg und ließen veraltete Query-Ergebnisse trotzdem stehen — bis sie von selbst abliefen, standardmäßig bis zu einer Stunde. Das betrifft jede Site mit APCu, unabhängig davon, ob du die Abilities nutzt — die tote Lösch-Schleife lag in genau diesem Zweig.
  • URL-Endpunkt ändern: Standardmäßig steckt der Filter unter /filter/. Den Pfad-Bestandteil änderst du unter General → URL Endpoint und klickst danach auf Flush Rewrite Rules (oder speicherst die Permalinks neu).
  • Verhalten ohne Filter-Terme: Über Bare Endpoint Behaviour stellst du ein, was bei Aufruf von /filter/ ohne Terme passiert — 404, Redirect zur Blog-Startseite oder eine eigene URL.
  • Mehrere Filter-Instanzen pro Seite: Jeder Satz aus Filter/Liste/Pagination wird über das post_type-Attribut gepaart (data-jpkpf-post-type). So lassen sich verschiedene Post-Types auf einer Seite unabhängig filtern.
  • Fragment-Antworten statt ganzer Seiten: Seit Version 1.2.0 beantwortet das Plugin einen Filter-Klick nur noch mit den austauschbaren Zonen — Theme-Header, Navigationsmenüs, Sidebar-Widgets und die komplette Asset-Pipeline entfallen (auf einer Testinstallation 60–72 % weniger Transfer und 5–19 % weniger Serverzeit). Die Anfrage läuft über ein eigenes URL-Segment /jpkpf-fragment/ statt über einen Query-Parameter, damit ein Page-Cache, der unbekannte Parameter abschneidet, keinem normalen Besucher ein nacktes Fragment ausliefern kann. Die dafür nötigen Rewrite-Regeln werden nach einem Versionswechsel einmalig automatisch neu geschrieben — ein manuelles Permalink-Speichern ist nach dem Update also nicht nötig.
  • HTML-Minifier und Fragmente — behoben in 1.4.4: Mit einem HTML-minifizierenden Optimierungs-Plugin lieferte jeder Filter-Klick eine leere Antwort, und die Liste meldete „Keine Beiträge gefunden." — auf einer Site, auf der weiterhin alle Beiträge passten. Das Fragment wurde an HTML-Kommentar-Markern aus der gerenderten Seite geschnitten, und genau die entfernt ein Minifier; Autoptimize tut das in seinen Werkseinstellungen, und sein Output-Buffer läuft vor dem des Plugins. Auf der Prüfinstallation gemessen: 18 300 Bytes ohne Autoptimize, 0 Bytes mit, wieder 18 300 mit abgeschalteter HTML-Optimierung. Seit 1.4.4 sind die Marker leere <template>-Elemente — Elemente entfernt kein Minifier, und in den Browser gelangen die Marker so oder so nicht. Zusätzlich bitten Fragment-Anfragen die bekannten Optimierer, sie in Ruhe zu lassen: über Autoptimizes eigenen Filter und die anbieterübergreifende DONOTMINIFY-Konvention. Weitere Anbieter trägst du aus einem mu-Plugin über jpkcom_postfilter_fragment_noptimize_filters nach — die Registrierung passiert schon beim Laden des Plugins, ein reguläres Plugin käme zu spät. Und falls eine Fragment-Antwort doch einmal unbrauchbar ankommt, lädt das Skript die Seite jetzt vollständig neu, statt fälschlich „keine Beiträge" zu melden.
  • Vier Cache-Schichten: Object-Cache, Transients, APCu und ein PHP-Datei-Cache für Einstellungen halten die Ausgabe schnell. Caches werden bei save_post/deleted_post bzw. Term-Änderungen automatisch invalidiert; manuell leerst du sie unter Post Filter → Cache. Seit 1.5.0 speichert der Query-Cache nur Auswahlen mit einem Term und ohne unbekannte Slugs (Konstante JPKCOM_POSTFILTER_QUERY_CACHE_MAX_TERMS, Filter jpkcom_postfilter_cache_query) — unter Crawler-Last war jede tiefe Kombination ein Schreibvorgang, der nie wieder gelesen wurde. Bis 1.7.0 ließen sich die Checkboxen Object Cache, Transient Cache und Settings File Cache auf der Cache-Seite nicht abschalten: Eine leere Checkbox schickt nichts, und das Plugin las das als Standard „an“. Seit 1.7.1 geht es; der Hauptschalter JPKCOM_POSTFILTER_CACHE_ENABLED in der wp-config.php schaltete alle Caches schon vorher gemeinsam ab. Ebenfalls seit 1.7.1 schreibt nur noch das Speichern die Datei im Einstellungs-Cache, nicht mehr jedes Lesen — eine Anfrage, die die Einstellungen kurz vor einem Speichern geladen hatte, konnte sonst ihre veraltete Kopie in eine fehlende Cache-Datei schreiben, etwa nach Clear Settings File Cache.
  • Multisite und APCu — behoben in 1.5.0: Alle Sites eines Netzwerks teilten sich ein Verzeichnis für den Einstellungs-Cache, sodass die zuletzt speichernde Site URL-Endpunkt, Filter-Gruppen und Limits aller anderen bestimmte; jetzt hat jede Site ein eigenes Unterverzeichnis (site-{id}), die alten gemeinsamen Dateien entfernt das Update. Und der Query-Cache in APCu nutzte auf jeder Site eines Netzwerks und in jeder WordPress-Installation desselben PHP-FPM-Pools dieselben Schlüssel, eine Site konnte also die zwischengespeicherten Beiträge einer anderen zeigen. Die Schlüssel gelten jetzt je Site. Einzelne Sites ohne geteilten Pool betrifft beides nicht.
  • CSS sauber überschreiben: Alle Styles nutzen CSS-Custom-Properties mit Präfix --jpkpf-. Setze sie im Theme-Stylesheet, in den Variablen-Feldern unter Layout & Design oder über das Custom-CSS-Feld. Für volle Kontrolle den Stylesheet-Modus auf „Disabled" stellen.
  • Templates pro Theme überschreiben: Der Loader prüft Child-Theme → Parent-Theme → mu-plugins/jpkcom-post-filter-overrides/templates/ → Plugin-templates/. Kopiere z. B. templates/partials/list/list-cards.php nach themes/dein-theme/jpkcom-post-filter/partials/list/list-cards.php.
  • Einstellungen migrieren: Unter Post Filter → Import / Export exportierst du alle Einstellungen (general, layout, cache, filter_groups) als JSON und spielst sie auf einer anderen Umgebung wieder ein.
  • Konstanten in wp-config.php: u. a. JPKCOM_POSTFILTER_URL_ENDPOINT, JPKCOM_POSTFILTER_CACHE_ENABLED, JPKCOM_POSTFILTER_DEBUG und JPKCOM_POSTFILTER_MAX_FILTER_COMBOS lassen sich überschreiben — Letztere stand schon länger in der README, das Plugin wertet sie aber erst seit 1.5.0 aus. Ebenfalls seit 1.5.0 gibt es, zusätzlich zu den Konstanten unter Crawler & SEO, vier reine Konstanten: JPKCOM_POSTFILTER_ROBOTS_HEADER (false unterdrückt den X-Robots-Tag-Header), JPKCOM_POSTFILTER_LINK_MAX_TERMS (Tiefe der Links im Modus combo, 0 = nur die Limits), JPKCOM_POSTFILTER_PAGECACHE_MAX_TERMS (Standard 0 = aus; Filterseiten mit mehr Termen bekommen DONOTCACHEPAGE) und JPKCOM_POSTFILTER_QUERY_CACHE_MAX_TERMS (Standard 1). Die Checkbox Debug mode unter General zeigt seit 1.7.1 nur noch an: Sie hat nie etwas bewirkt, Debug-Logging und Debug-Templates folgen allein JPKCOM_POSTFILTER_DEBUG (Standard: WP_DEBUG).
  • Gutenberg-Blocks aus Quellcode bauen: Die Block-Editor-Skripte brauchen einen Build (npm install && npm run build, Ausgabe nach blocks/build/). Fehlt das Build-Verzeichnis, überspringt das Plugin die Block-Registrierung.
  • Deutsche Übersetzung: Der Übersetzungs-Katalog war zuletzt bei 1.1.2 erzeugt worden und lief danach zwölf Releases lang hinter dem Code her, sodass alles seither Hinzugekommene auf einer deutschen Site englisch erschien. Seit 1.4.1 deckt er wieder das ganze Plugin ab, seit 1.4.2 sind 46 fehlende deutsche Übersetzungen ergänzt — Block-Titel samt Beschreibungen und Suchbegriffen, die Elementor- und Oxygen-Widget-Labels, mehrere Einstellungs-Hinweise und die Sicherheitsmeldungen des Updaters. Die Texte der Abilities bleiben bewusst englisch: Sie werden von KI-Clients und Automatisierung gelesen, nicht im Admin-Bereich, und ihr genauer Wortlaut ist das, woran ein Aufrufer eine falsche Anfrage in einem Anlauf korrigiert.
  • Barrierefrei & SEO-tauglich: Filter-Terme sind echte <a>-Links (Reload ohne JS) oder, wo ein Link nirgends sinnvoll hinführt, <button type="button">; die Ergebnis-Zone nutzt aria-live, Toggle-Buttons aria-pressed. Seit 1.5.0 sagt ein Screenreader mit Auto-Inject und der Beitragsschleife des Themes nach einem Filter-Klick die Trefferzahl an — vorher meldete er nach jedem erfolgreichen Klick „Keine Beiträge gefunden“, weil die Beiträge des Themes keine Marker des Plugins tragen. Seit 1.7.1 zählt die Ansage auch die Beiträge der Beitragsvorlage eines Block-Themes mit (dort nannte sie keine Zahl) und nur die Beiträge der Liste selbst, nicht die eines Query-Blocks innerhalb eines Beitrags. Crawlbar und indexierbar sind das Archiv und die Ein-Term-Seiten mit genau einem Canonical, standardmäßig auf das Term-Archiv (siehe Canonical-Ziele oben).
  • Updates & Sicherheit: Das Plugin aktualisiert sich sicher über GitHub mit SHA256-Prüfsumme; Update-Hinweise erscheinen unter Dashboard → Aktualisierungen (seit 1.4.6 mit dem JPKCom-Logo als Plugin-Icon) und auf der Plugins-Seite. Seit 1.4.5 trägt es außerdem den Header Update URI: Die Update-Schnittstelle von wordpress.org liefert für dieses Plugin dann keine Updates mehr, ein dort veröffentlichtes Plugin mit demselben Slug kann sich also nicht als Update ausgeben. Seit 1.4.7 (Updater 1.5.0) bekommt das Plugin Updates auch im deaktivierten Zustand angeboten — sofern ein anderes JPKCom-Plugin mit Updater 1.5.0 oder neuer aktiv ist, auf Multisite netzwerkweit oder auf der Hauptseite: Ein geladener Updater bedient alle installierten JPKCom-Plugins mit Update-Zeile, Icon und „Details anzeigen“, die SHA256-Prüfung gilt dabei genauso. Die gespeicherte Update-Liste enthält keine JPKCom-Download-Adressen mehr; sie kommen nur zurück, solange ein JPKCom-Updater geladen ist, sodass sich ohne ihn kein ungeprüftes Paket installieren lässt. Die Prüfung verifiziert außerdem Pakete, die ein anderes Plugin bereitstellt, und erkennt jede GitHub-Adresse einer Release-Datei. Ein Git- oder anderer Versionskontroll-Checkout und ein umbenannter Ordner (etwa jpkcom-post-filter-main) werden nie über WordPress aktualisiert — das Update würde den Checkout bzw. den Ordner löschen. Von neuen Versionen erfahren sie trotzdem, ein umbenannter Ordner allerdings nur, solange das Plugin darin aktiv ist. Update-Infos holt der Updater nur für Konten, die Plugins aktualisieren dürfen, im Update-Cron und in WP-CLI, nie für anonyme Besucher.

Weiterführende Informationen