Skip to content

feat: resolve WPGraphQL settings outside the admin (at the /graphql endpoint)#3878

Merged
jasonbahl merged 3 commits into
mainfrom
feat/settings-resolve-at-endpoint
Jun 4, 2026
Merged

feat: resolve WPGraphQL settings outside the admin (at the /graphql endpoint)#3878
jasonbahl merged 3 commits into
mainfrom
feat/settings-resolve-at-endpoint

Conversation

@jasonbahl

Copy link
Copy Markdown
Collaborator

What

Initializes the WPGraphQL settings registry on every request (not just admin_init), so registered settings resolve in non-admin contexts such as a /graphql request.

Why

Extracted from #3784 (the IDE 5.0 rebuild) so the core feature lands as its own minor without inheriting that PR's feat! major. The rebuilt IDE — and any consumer reading settings via GraphQL — depends on settings being available at the endpoint.

Changes

  • Settings::init_registry() runs on init (priority 11) alongside the existing admin path.
  • SettingsRegistry::init_registry() fires graphql_init_settings, ensures each section's option exists, and registers it. Idempotent (per-instance guard) so the init + admin_init paths can't double-fire in one request. admin_init() delegates registration to it and keeps only admin-UI scaffolding.
  • Experimental: Admin is initialized in all contexts (registration-only work) so the endpoint can resolve experiment settings.
  • Lint-only: an import/no-unresolved eslint-disable comment in the legacy wpgraphiql GraphiQLToolbar (folded in here so it isn't lost when the IDE branch is reduced to plugins/wp-graphql-ide/**).

Backward-compatible — additive new public methods and an action that fires earlier/more often. No breaking changes.

…ndpoint)

Extracted from #3784. Initializes the settings registry on every request
instead of only on `admin_init`, so registered settings are available in
non-admin contexts such as a /graphql request.

- Settings::init_registry() now runs on `init` (priority 11, after
  register_settings) in addition to the existing admin path.
- SettingsRegistry::init_registry() fires `graphql_init_settings` and
  ensures each section's option exists + is registered. It's idempotent
  (guarded per-instance) so reaching it via both `init` and `admin_init`
  in one request can't double-fire. admin_init() now delegates to it for
  the registration work and keeps only the admin-UI scaffolding.
- Experimental: the Admin class is now initialized in all contexts (it only
  does registration work), so the GraphQL endpoint can resolve experiment
  settings without depending on an admin request.

Also folds in a lint-only eslint-disable comment in the legacy wpgraphiql
GraphiQLToolbar (import/no-unresolved for @graphiql/react) so it isn't
stranded when the IDE branch is reduced to plugins/wp-graphql-ide/**.
@vercel

vercel Bot commented Jun 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
wpgraphql-com Skipped Skipped Jun 4, 2026 12:06am

@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.28571% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.5%. Comparing base (9e7e98e) to head (b836ff8).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
plugins/wp-graphql/src/Admin/Settings/Settings.php 33.3% 2 Missing ⚠️
...ugins/wp-graphql/src/Experimental/Experimental.php 0.0% 2 Missing ⚠️
...wp-graphql/src/Admin/Settings/SettingsRegistry.php 88.9% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main   #3878     +/-   ##
=========================================
- Coverage     84.5%   83.5%   -1.0%     
- Complexity    4360    5284    +924     
=========================================
  Files          221     286     +65     
  Lines        19438   22753   +3315     
=========================================
+ Hits         16431   18995   +2564     
- Misses        3007    3758    +751     
Flag Coverage Δ
wp-graphql-acf-wpunit-twentytwentyfive-single 77.1% <ø> (?)
wp-graphql-wpunit-twentytwentyfive-multisite 84.5% <64.3%> (+<0.1%) ⬆️
wp-graphql-wpunit-twentytwentyfive-single 84.5% <64.3%> (+<0.1%) ⬆️
wp-graphql-wpunit-twentytwentyone-multisite 84.5% <64.3%> (+<0.1%) ⬆️
wp-graphql-wpunit-twentytwentyone-single 84.5% <64.3%> (+<0.1%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...wp-graphql/src/Admin/Settings/SettingsRegistry.php 52.0% <88.9%> (+1.4%) ⬆️
plugins/wp-graphql/src/Admin/Settings/Settings.php 98.5% <33.3%> (-1.0%) ⬇️
...ugins/wp-graphql/src/Experimental/Experimental.php 0.0% <0.0%> (ø)

... and 66 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Regression caught by tests/e2e/specs/settings-registry-options-page.spec.js:
init_registry() created each section's option via add_option( $id, [] ).
An array value serializes, and WordPress renders serialized options as a
disabled "SERIALIZED DATA" input on /wp-admin/options.php — so the e2e test
that edits graphql_experiments_settings there could no longer fill the field.

Revert to add_option( $id ) (WP's empty-string default), matching the
long-standing behavior. sanitize_options() already tolerates a string value,
so nothing downstream needs the [] seed.
The previous approach (seeding each option in init_registry, which runs on
every `init`) was the root of two test failures:

- add_option($id, []) stored a serialized array -> WordPress renders it as a
  disabled "SERIALIZED DATA" input on /wp-admin/options.php, breaking
  settings-registry-options-page.spec.js.
- add_option($id) stored an empty string -> on a /graphql or test request the
  option now exists as a scalar, so callers that array-write onto it
  (GraphQLHeadersCept: `$opt['query_analyzer_enabled'] = 'off'`) hit a PHP 8
  "Cannot access offset of type string on string" TypeError. On main this
  works because the option doesn't exist yet (false auto-vivifies to array).

Fix: init_registry() no longer creates the option row — it only fires
graphql_init_settings and register_setting() so settings resolve at the
endpoint. Option creation moves back to admin_init() (lazy, empty-string
default), matching main's long-standing behavior. Reads are unaffected:
get_graphql_setting() already defaults a missing option to [].
@jasonbahl jasonbahl merged commit f10661c into main Jun 4, 2026
61 of 64 checks passed
@jasonbahl jasonbahl deleted the feat/settings-resolve-at-endpoint branch June 4, 2026 00:22
jasonbahl added a commit that referenced this pull request Jun 4, 2026
Sync main (split PRs #3877/#3878/#3879 + asset-pipeline #3880/#3882/#3883) into
the IDE rebuild branch. Conflicts were all IDE release-metadata + the plugin
header; resolved as:

- package.json / wpgraphql-ide.php Version / readme.txt Stable tag: 4.5.0
  (the release-please manifest value; release-please bumps to 5.0.0 at release —
  the in-repo version tracks the last release per the plugin's CLAUDE.md).
- CHANGELOG.md / readme.txt changelog: union — keep the 5.0.0 notes AND main's
  released 4.5.0 entry.
- Requires at least: 6.1 (not the 7.0 the branch had staged). WordPress 7.0's
  i18n features (.l10n.php 6.5, JIT textdomain 6.7) degrade gracefully and no
  runtime gate gates on 7.0, so there's no reason to lock out current installs.
  Dropped the "raised floor to 7.0" breaking-change note and aligned CLAUDE.md +
  i18n docs to 6.1.
- Tested up to: 7.0 (WordPress 7.0 is released).
jasonbahl added a commit to josephfusco/wp-graphql that referenced this pull request Jun 4, 2026
Sync main (split PRs wp-graphql#3877/wp-graphql#3878/wp-graphql#3879 + asset pipeline wp-graphql#3880/wp-graphql#3882/wp-graphql#3883) onto
the IDE rebuild branch, on top of Joe's floor/CI commits (floor 6.1, WP 7.0 CI
coverage + tested-up-to). Conflicts (all IDE release metadata) resolved as:

- Version / Stable tag / package.json: 4.5.0 (release-please manifest value;
  release-please bumps to 5.0.0 at release — in-repo version tracks last release
  per the plugin's CLAUDE.md).
- CHANGELOG.md / readme.txt changelog: union — keep the 5.0.0 notes AND main's
  released 4.5.0 entry. Dropped the now-inaccurate "raised floor to 7.0"
  breaking-change note (Joe set the floor to 6.1) and the 7.0-floor framing in
  the i18n docs.
- Requires at least: 6.1 / Tested up to: 7.0 — kept Joe's values.
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.

1 participant