Skip to content

Content types: flush rewrite rules on rewrite-impacting changes#78058

Merged
tyxla merged 5 commits into
trunkfrom
try/content-types-flush-rewrite-rules
May 7, 2026
Merged

Content types: flush rewrite rules on rewrite-impacting changes#78058
tyxla merged 5 commits into
trunkfrom
try/content-types-flush-rewrite-rules

Conversation

@tyxla
Copy link
Copy Markdown
Member

@tyxla tyxla commented May 7, 2026

What?

Adds functionality to flush rewrite rules when there are affected changes to user-defined post types and taxonomies.

When a wp_user_post_type or wp_user_taxonomy write changes a field that affects rewrite rules, the rules are regenerated on the next request so that archive URLs, single-post permalinks, and taxonomy term URLs match the current registration.

Part of #77600.

Why?

User-defined post types and taxonomies materialize into register_post_type() / register_taxonomy() calls on init priority 20. Without flushing, WordPress's cached rewrite_rules option still reflects the previous registration, so:

  • A renamed CPT slug keeps serving (or 404'ing) under the old slug.
  • Toggling has_archive doesn't add or remove the archive URL.
  • Toggling public doesn't add or remove the single permalink rules.
  • Trashing/deactivating a CPT or taxonomy leaves orphaned rewrite rules pointing at a now-unregistered type.

The same story applies to taxonomy term URLs when a taxonomy slug or public flag changes.

How?

The flush is deferred rather than inline. Registration runs on init priority 20, before any REST handler executes - flushing inline would regenerate rules from the pre-update registration. Instead:

  1. The REST controllers (create_item / update_item / delete_item) compare pre/post state and, when a rewrite-impacting field changed, set an option flag (_gutenberg_user_content_types_flush_rewrite_rules, autoload off).
  2. A new init priority 30 handler - running after gutenberg_register_user_defined_post_types() and gutenberg_register_user_defined_taxonomies() at priority 20 - picks up the flag, deletes it, and calls flush_rewrite_rules( false ).

Note: we're doing a soft flush only: the standard .htaccess block doesn't depend on individual post type / taxonomy registrations, so a hard flush would gain nothing and write the file unnecessarily.

The changes consider the following conditions necessary to trigger a rewrite rule flush:

Post types Taxonomies
Status crosses publish boundary
Slug renamed while published
has_archive toggled while published -
public toggled while published
Trash or force-delete of a previously published record

Testing Instructions

  1. Enable the Content Types experiment under Gutenberg > Experiments.
  2. Under Settings > Post Types, add a post type with key book, label "Books", and Public on, Has archive on. Save.
  3. Visit /?post_type=book (or the pretty permalink /book/) - the archive should resolve.
  4. Edit the book post type, rename the slug to library. Save.
  5. Visit /library/ - the archive should now resolve under the new slug, and /book/ should 404.
  6. Toggle Has archive off. Save. The archive URL should stop resolving.
  7. Toggle Public off. Save. Single-post permalinks for any seeded posts of this type should stop resolving.
  8. Toggle the post type back to Draft (Active off). Save. The post type unregisters next request; URLs should 404.
  9. Repeat steps 2–5 under Settings > Taxonomies: create a taxonomy genre, attach to a post type, assign a term, visit /genre/<term>/, then rename the slug and confirm the new URL resolves and the old one doesn't.
  10. Editing only labels or descriptions on a published post type/taxonomy should not trigger a flush. The deferred-flush option key _gutenberg_user_content_types_flush_rewrite_rules should remain absent in wp_options after such edits.
  11. Verify tests pass: vendor/bin/phpunit --filter User_Content_Types_Rewrite_Flush_Test or npm run test:unit:php:base -- --filter User_Content_Types_Rewrite_Flush_Test

Testing Instructions for Keyboard

None

Screenshots or screencast

None

Use of AI Tools

Opus 4.7

Defers a soft flush_rewrite_rules() to the next init (priority 30, after
register_post_type/register_taxonomy at priority 20) via an option flag,
so the regenerated rules see the post-update registration state.

Triggers on: publish ↔ non-publish status transitions, slug rename
while published, has_archive or public toggle while published (post
types), public toggle while published (taxonomies), and trash or
force-delete of a previously published record. Label/supports/etc.
edits do not trigger.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tyxla tyxla requested a review from ntsekouras May 7, 2026 12:58
@tyxla tyxla self-assigned this May 7, 2026
@tyxla tyxla requested a review from spacedmonkey as a code owner May 7, 2026 12:58
@tyxla tyxla added [Type] Experimental Experimental feature or API. Content types (experimental) Affects the content types experiment labels May 7, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

Flaky tests detected in 63ade6f.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/25508364216
📝 Reported issues:

* Seeds a wp_user_taxonomy draft directly via `wp_insert_post`.
* See {@see seed_post_type_draft} for why we bypass the REST controller.
*/
private function seed_taxonomy_draft( $slug, $title, $config = array() ) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these helper could be combined since they are almost identical. For example here only post_type changes.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 7, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: ntsekouras <ntsekouras@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Comment thread phpunit/experimental/content-types/user-content-types-rewrite-flush-test.php Outdated
Copy link
Copy Markdown
Contributor

@ntsekouras ntsekouras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some nits, but LGTM. Thanks!

tyxla and others added 2 commits May 7, 2026 18:07
…axonomies

`publicly_queryable` flips `register_taxonomy()`'s effective `query_var`,
which swaps the `add_rewrite_tag` query string between `{slug}=` and
`taxonomy={name}&term=`. Without this, toggling it on a published
taxonomy left stale rewrite rules in place.

Effective-value comparison (treating an absent field as equal to
`public`) avoids a redundant flush when the field is set to its
implicit default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`hierarchical` flips `register_post_type()`'s rewrite-tag regex between
`(.+?)` (nested) and `([^/]+)` (flat) and swaps the query-var fallback
between `pagename=` and `name=`. Without this, toggling it on a
published post type left stale rules in place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tyxla
Copy link
Copy Markdown
Member Author

tyxla commented May 7, 2026

Thanks, @ntsekouras!

I've fixed a couple of minor gaps in the meantime:

  • hierarchical on post types - since it changes the regex
  • publicly_queryable since it swaps the query var

tyxla and others added 2 commits May 7, 2026 18:13
The two `*_create_draft_does_not_schedule` tests bypass the REST
controller via direct `wp_insert_post`, so they don't exercise any of
the controller scheduling logic. The `*_update_draft_to_draft_does_not_schedule`
tests already cover the "no flush for unpublished" guarantee through
the REST path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tyxla tyxla enabled auto-merge (squash) May 7, 2026 16:41
@tyxla tyxla merged commit 12a12af into trunk May 7, 2026
39 checks passed
@tyxla tyxla deleted the try/content-types-flush-rewrite-rules branch May 7, 2026 17:00
@github-actions github-actions Bot added this to the Gutenberg 23.2 milestone May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Content types (experimental) Affects the content types experiment [Type] Experimental Experimental feature or API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants