Skip to content

Add schema feature toggle and settings page#22772

Merged
leonidasmi merged 33 commits intotrunkfrom
826-add-a-toggle-for-the-yoast-schema-graph
Dec 19, 2025
Merged

Add schema feature toggle and settings page#22772
leonidasmi merged 33 commits intotrunkfrom
826-add-a-toggle-for-the-yoast-schema-graph

Conversation

@leonidasmi
Copy link
Copy Markdown
Contributor

@leonidasmi leonidasmi commented Dec 1, 2025

Context

  • We want to add a UI schema configuration page to give users more control into whether the Schema graph is active on their website.

Summary

This PR can be summarized in the following changelog entry:

  • Adds a schema settings page to allow users more control over the Yoast Schema Framework API.

Relevant technical choices:

  • Added a const isSchemaFrameworkEnabled = Boolean( window.wpseoIntegrationsData[ "schema_framework_enabled" ] ); flag to tell the Woocommerce SEO Integration component that this instance is being used in the Schema API integrations section.
  • Replaced FormikValueChangeField to be able to intercept the "disable" action and show the confirmation modal before the value changes.
  • enable_schema from options inc/options/class-wpseo-option-wpseo.php is controlled by the user toggle while isSchemaDisabledProgrammatically compute at runtime from the wpseo_json_ld_output filter in the theme functions.php.
  • Changed get_schema_api_integrations() from private to public to create a single source of truth for the get_schema_api_integrations()

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

Note

At the moment of writting these test instructions, the shortlinks have not been added yet (see thread 🧵 ). Therefore, some links will still re-direct to the yoast.com homepage.

  • With Yoast SEO (free) active
Site features
  • Navigate to the Setting page in the Yoast SEO menu.
  • In the Site features, scroll to the APIs section.
  • Confirm that you see the new added Schema Framework card.
  • Confirm that the card matches the design
  • The Learn more link should redirect in a new tab.
  • The Schema Framework should be enabled by default.
  • Click on the toggle to disable it.
  • Confirm that you get a confirmation modal to disable the Schema, the modal should match the design.
  • With the modal open, recreate a small screen (using dev tools or resizing the screen).
  • Confirm that the modal is responsive and it looks like the Tailwind example (for context on the modal layout refer to this thread 🧵 ).
  • Dismiss the modal from the X or the Cancel buttons and confirm that both close the modal and that the Shema Framework is still enabled.
  • Click again on the toggle to disable it and this time click on the Turn off Schema Framework button.
  • Confirm that the modal is dismissed and the Schema Framework is now disabled.
  • The toggle should be in the disabled state and the Schema Framework icon of the card should now be grayed out.
  • Click on the Discard changes button and Yes, discard changes in the modal and confirm that the Schema is again enabled with the toggle and the icon with color.
Schema page
  • Navigate to the new added Schema page in the Yoast SEO setting menu.
image
  • Confirm that you navigate to the Schema Framework page as per this design
  • Check the text copies and design.
  • Confirm that the Schema API integrations section, shows a list of pluggins that have integration with the Yoast Schema Framework API.
  • If the plugin is installed and active, the integration should show as follows:
image
  • If the plugin is not active, the integration should be:
image
  • Confirm this behaviour by installing and activation or deactivating one of the plugins (e.g. The Events Calendar).
  • If WooCommerce is installed and activate but the Woo Yoast SEO plugin it isn't, the Yoast WooCommerce SEO plugin should show the Upgrate to Woo SEO button.
image
  • If Premium is not active, the Easy Digital Downloads should show the Upgrate to Premium button.
image
  • Click on the buttons and confirm that they redirect to the correct pages. (Woo SEO and Premium)
  • The Enable Schema Framework toggle should be enabled, click on the toggle to disable it.
  • You should see the disable confirmation modal again.
  • Save the changes.
  • Confirm that the list of integrations now has less opacity and each integration says Schema Framework disabled as per the design.
  • Navigate to the site features page in the Yoast SEO settings menu and confirm that the Schema Framework card in the APIs is also disabled.
  • Click on the toggle to enable the Schema again, save the changes, and navigate back to the Schema page.
  • The Enable Schema Framework should be enabled.
Test feature toggle when disabled
  • Have the schema feature disable either from the Site features page or the schema page
  • Go to the site's frontend (homepage, post, category, etc.) and confirm that there's no schema output there anymore
  • Also check product schema, with WooCommerce and Woo SEO. With the feature disabled, we should fall back to native WooCommerce schema on product pages.
  • There's some exception for that:
    • If you go to the REST pages of the frontend (eg. http://example/wp-json/wp/v2/posts/<POST_ID>), you will see the schema not being removed from yoast_head_json, although it SHOULD be removed from yoast_head
Integrations page
  • Click on the link in the Schema API integrations section.
image
  • Confirm that you are redirected to the Inegrations page in the Yoast SEO menu.
  • Check the Schema API integrations section and confirm that the cards for each plugin integration also indicate whether the integration is active, the plugin is not detected, or the upgrade buttons.
image
  • Each card header including the name and logo of the plugin should be in active if the integration is active or grayed out if the Plugin is not detected.
  • Navigate again to the Schema page, disable the Schema Framework, save the changes, and click again in the integrations page link (Schema API integrations section).
  • Confirm that now all integrations headers are grayed out, and the footer shows the same text in red as this design.
  • Note that when the Schema is disabled, the integrations page should show an informative alert with a link to the Schema page.
image
  • Don't click it yet. Instead click on the Yoast Schema API link from the description of the section and confirm that it redirects you to the Schema - API documentation page in a new tab.
  • Now click on the Shema Framework link in the alert. It should redirect you to the Schema page.
Disable the Schema programmatically
  • In the Schema page check the section Schema for devs at the bottom of the page and confirm that it matches the copy in the design.
  • Click on the Schema API link and confirm that it redirects to the Schema - API documentation page in a new tab.
  • Click on Schema documentation link and confirm that it redirects to the Schema - Background information page in a new tab.
  • Add the following code to the functions.php file of your wp test site theme. (e.g. onewordpresstest/app/public/wp-content/themes/twentytwentyfour/functions.php)
add_filter( 'wpseo_json_ld_output', '__return_false' );
  • Refresh the Schema page and confirm that now you see an informational alert as per the design.
  • Click on the Learn more about the filter link in the alert and confirm that it redirects to the To disable Schema entirely page in a new tab.
  • Try enabling the Schema Framework with the toggle.
  • Confirm that a modal matches the design.
  • Check the responsiveness of the modal by resizing the screen.
  • Confirm that the link in the modal also redirects to the To disable Schema entirely page in a new tab.
  • Close the modal.
  • Navigate to the Site features page and click click on the toggle to enable the Schema Framework.
  • Confirm that you see the same modal.
Regression test schema
  • In a fresh site install Yoast SEO and confirm that you see schema in the frontend
    • Also disable schema via the wpseo_json_ld_output filter and confirm that you don't see schema in the frontend
  • In a site that has already Yoast SEO, upgrade to this RC and confirm that you still see schema in the frontend
    • In a site that has already Yoast SEO but schema disabled via that filter, upgrade to this RC and confirm that you still don't see schema in the frontend.
  • Also check product schema, with WooCommerce and Woo SEO. With the filter there, we should fall back to native WooCommerce schema on product pages.
  • The same exceptions mentioned in Test feature toggle when disabled are true here too.
  • Let's also trying with a third-party plugin that disables our schema, to see if they still disable it:
    • Install the WP SEO Structured Data Schema plugin
    • Go to WP SEO Schema->Settings and check the YOAST SEO Default Schema JSON-LD disable option
    • With the new Yoast schema setting enabled, go to the frontend and confirm that there's no schema output.
    • With the new Yoast schema setting disabled, again go to the frontend and confirm that there's no schema output.
Test case when schema is disabled both with the setting and the filter
  • Disable the schema via its setting
  • Then add the filter that disables the schema via that hook: add_filter( 'wpseo_json_ld_output', '__return_false' );
  • Go to the schema settings page, refresh the page and confirm that you see the Schema output disabled alert that informs users that the filter is active
  • Try to toggle the feature on and confirm that you get the modal that informs me that we can't turn this on.
  • Go to the Site Features and try to enable the schema setting from there. Confirm that again you get the modal that informs me that we can't turn this on.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

QA can test this PR by following these steps:

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

Other environments

  • This PR also affects Shopify. I have added a changelog entry starting with [shopify-seo], added test instructions for Shopify and attached the Shopify label to this PR.

Documentation

  • I have written documentation for this change. For example, comments in the Relevant technical choices, comments in the code, documentation on Confluence / shared Google Drive / Yoast developer portal, or other.

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have ran grunt build:images and commited the results, if my PR introduces new images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Fixes #826

@leonidasmi leonidasmi added the changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog label Dec 1, 2025
@coveralls
Copy link
Copy Markdown

coveralls commented Dec 1, 2025

Pull Request Test Coverage Report for Build 6f473720d8cc2b0b2019f154fca9319b6b81a59d

Details

  • 19 of 183 (10.38%) changed or added relevant lines in 17 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall first build on 826-add-a-toggle-for-the-yoast-schema-graph at 41.433%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/integrations/settings-integration.php 1 2 50.0%
packages/js/src/integrations-page/woocommerce-integration.js 0 2 0.0%
packages/js/src/integrations-page/integrations-grid.js 0 3 0.0%
packages/js/src/integrations-page/plugin-integration.js 0 3 0.0%
packages/js/src/settings/components/schema-disable-confirmation-modal.js 0 3 0.0%
src/conditionals/schema-disabled-conditional.php 0 4 0.0%
src/schema/infrastructure/disable-schema-integration.php 0 5 0.0%
packages/js/src/settings/components/schema-programmatically-disabled-modal.js 0 6 0.0%
packages/js/src/settings/hooks/use-toggle-handler-with-modals.js 1 7 14.29%
packages/js/src/settings/store/schema-framework.js 0 8 0.0%
Totals Coverage Status
Change from base Build db67b95b8ffb4589da91d96e479da4912c44d981: 41.4%
Covered Lines: 22776
Relevant Lines: 51546

💛 - Coveralls

@leonidasmi leonidasmi added changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog and removed changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog labels Dec 3, 2025
leonidasmi and others added 19 commits December 3, 2025 13:05
…k and FeatureCard for confirmation before disabling schema features
@JorPV JorPV marked this pull request as ready for review December 11, 2025 15:48
@leonidasmi
Copy link
Copy Markdown
Contributor Author

One thing I noticed, when schema is disabled via the filter:

We then show in the integration page a message about the schema being disabled (rightly so), but then instruct the user to enable it in the settings, which is not right since they will not be able to enable it from there, until they remove the filter.

So, to recap. When we have disabled schema via filter, we show this in the integrations page:
image
but we should show something similar to what we show in the schema page:
image

@leonidasmi
Copy link
Copy Markdown
Contributor Author

Also, I'm not able to tell by the design, but when we have disabled schema via the filter, it looks like the main feature toggle is disabled in the schema page, but in the PR it's not:
image

I've noticed that instead it has the same behavior with the main feature card (that is, it's not disabled but displays a warning modal when tried to be toggled on), so if we have double checked that it's ok with Tom, then all goo

Copy link
Copy Markdown
Contributor Author

@leonidasmi leonidasmi left a comment

Choose a reason for hiding this comment

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

CR: 🏗️

And some comments about the functionality above

*/
public static function get_conditionals() {
return [
Front_End_Conditional::class,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Come to think about it, this doesn't need the Front_End_Conditional because then it wouldnt work on the REST API.

Basically, if you have disabled schema via the option and visit a post's REST address (eg. http://example.com/wp-json/wp/v2/posts/<POST_ID> and search for "schema", you will find the schema even though you shouldn't.

We can remove that conditional and do nothing else, or create a conditional that checks if we're in REST request, but I'm leaning towards the former.

Comment thread src/schema/application/configuration/schema-configuration.php Outdated
*
* @return array<string, array<string, bool|string>> The schema API integrations status.
*/
private function get_schema_api_integrations(): array {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Everything in this function is duplicated code from Yoast\WP\SEO\Integrations\Admin::Integrations_Page basically, let's have a single source of truth.

Best to do is:

  • Take what the Integrations_Page class has and move it to the Schema_Configuration class (probably what you have already done here, but let's make sure that it's exactly the same data)
  • Make a dependency in the integrations page for the schema configuration class and use the latter's method there too
  • That way, we have the single source of truth (the method of the schema config class) that's being used both in the integrations page class and the settings integrations class


// Flag to check if the Schema Framework is enabled.
// eslint-disable-next-line dot-notation
const isSchemaFrameworkEnabled = Boolean( window.wpseoIntegrationsData[ "schema_framework_enabled" ] );
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We have multiple usages of this very same line, maybe there's a more graceful way to pass that information along the component tree? We can consider React's context for this?

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.

There are only 2 usages. A context makes sense if this pattern would be used in more places, which I doubt is the case.

const [ isConfirmModalOpen, setIsConfirmModalOpen ] = useState( false );
const [ isProgrammaticallyDisabledModalOpen, setIsProgrammaticallyDisabledModalOpen ] = useState( false );

const handleToggleChange = useCallback( ( newValue ) => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Again, we use this here and in the schema framework route, so let's have it centralized somewhere in a higher level and use the centralized version in both places

const structuredDataLearnMoreLink = useSelectSettings( "selectLink", [], "https://yoast.com/features/structured-data/" );
const learnMoreFilterLink = useSelectSettings( "selectLink", [], "https://yoa.st/schema-framework-filters" );
const schemaApiLink = useSelectSettings( "selectLink", [], "https://yoa.st/schema-api" );
const schemaDocumentationLink = useSelectSettings( "selectLink", [], "https://developer.yoast.com/features/schema/api/" );
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we want this link behind a shortlink as well? Best to check with Manuel for this, to confirm (same with the other link to our dev docs)

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.

Correct, this should be a shortlink. I've already asked for them.

@JorPV
Copy link
Copy Markdown
Contributor

JorPV commented Dec 12, 2025

One thing I noticed, when schema is disabled via the filter:

We then show in the integration page a message about the schema being disabled (rightly so), but then instruct the user to enable it in the settings, which is not right since they will not be able to enable it from there, until they remove the filter.

So, to recap. When we have disabled schema via filter, we show this in the integrations page: image but we should show something similar to what we show in the schema page: image

Good appreciation 👍🏼 I'll double check it with @ux-tom

Update:

  • I've confirmed with @ux-tom that this is correct, it should still re-direct to the Schema Framework page.

@JorPV
Copy link
Copy Markdown
Contributor

JorPV commented Dec 12, 2025

Also, I'm not able to tell by the design, but when we have disabled schema via the filter, it looks like the main feature toggle is disabled in the schema page, but in the PR it's not: image

I've noticed that instead it has the same behavior with the main feature card (that is, it's not disabled but displays a warning modal when tried to be toggled on), so if we have double checked that it's ok with Tom, then all goo

I know what you mean, but in the design the toogle has always an slate-200 value when disabled, it doesn't matter if the filter is present or not.

Regarding your point of only showing the info modal in the Site Features toggle, and not in the Schema page toggle, I'm not sure whether it makes sense 🤔 So good that you notice it! I'll check it with @ux-tom as well!

Update:

  • Here too, I've confirmed with Tom that the toogle is just switched off, no disabled, and it should show the modal on click.

Copy link
Copy Markdown
Contributor Author

@leonidasmi leonidasmi left a comment

Choose a reason for hiding this comment

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

CR: ✅, aside from one thing I missed and want us to improve:

  • In Schema_Disabled_Conditional, turn return ! $this->options->get( 'enable_schema', true ); into return $this->options->get( 'enable_schema', true ) === false;, to check for false specifically instead of just false-y values
  • I did that and performed some regression test and looks good, but before pushing that change, I wouldnt mind a second look from you too @JorPV

Acceptance: 🚧

A thing I noticed that I think we should handle better:

  • Disable the schema via its setting
  • Then add the filter that disables the schema via that hook: add_filter( 'wpseo_json_ld_output', '__return_false' );
  • Go to the schema settings page and you will see the toggle being turned off
  • Toggle it on
    • At that point, I would expect the modal that informs me that we can't turn this on because we have the filter enabled
    • At the very least, even if I dont get that modal, I shouldn't be able to toggle that thing on
  • Save the changes, which is done successfully so the user might think that they indeed turned on the schema feature
  • Refresh the page and see that the schema is disabled! (this time you also see the alert informing that the filter is there)

@JorPV
Copy link
Copy Markdown
Contributor

JorPV commented Dec 17, 2025

CR: ✅, aside from one thing I missed and want us to improve:

* In `Schema_Disabled_Conditional`, turn `return ! $this->options->get( 'enable_schema', true );` into `return $this->options->get( 'enable_schema', true ) === false;`, to check for false specifically instead of just false-y values

* I did that and performed some regression test and looks good, but before pushing that change, I wouldnt mind a second look from you too @JorPV

You are right, $this->options->get( 'enable_schema', true ) === false is safer since the option should only be considered disabled when it's explicitly falsy.

@leonidasmi
Copy link
Copy Markdown
Contributor Author

leonidasmi commented Dec 18, 2025

@JorPV Things I've pushed myself and would appreciate a CR and regression test:

Copy link
Copy Markdown
Contributor

@vraja-pro vraja-pro left a comment

Choose a reason for hiding this comment

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

Comments about site-features.js

label={ __( "Enable feature", "wordpress-seo" ) }
disabled={ isDisabled }
checked={ disabledSetting === "language" || showProgrammaticallyDisabledModal ? false : value }
onChange={ handleToggleChange }
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.

What about adding onChange to FormikValueChangeField to override the handleChange?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After my explanation here, I dont see the benefit in that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As per a talk with @vraja-pro , we can ignore this for now and we can improve this after we merge both affected PRs.

{ showProgrammaticallyDisabledModal && programmaticallyDisabledModal( {
isOpen: isProgrammaticallyDisabledModalOpen,
onClose: handleProgrammaticallyDisabledModalClose,
} ) }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As per a talk with @vraja-pro , we can ignore this for now and we can improve this after we merge both affected PRs.

const showConfirmationModal = Boolean( disableConfirmationModal );
const showProgrammaticallyDisabledModal = Boolean( programmaticallyDisabledModal );

const handleToggleChange = useSchemaToggleHandler( {
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.

This is coupling the feature card to the schema framwork feature, better to have that logic in a separate component and pass that as children. We can overcome the toggle callback by adding a prop onChange or onEnable and override the FormikValueChangeField onChange.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is coupling the feature card to the schema framwork feature

Actually, this is NOT coupling the feature card to the schema framework, my last commit did what it needed to be done to avoid that.

Granted, the name of the toggle handler does carry a schema-related context, but that's the only thing that is still schema related. I'm pushing a commit to remedy that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just pushed this, so I believe we have now completely decoupled the feature card component from the schema framework.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As per a talk with @vraja-pro , we can ignore this for now and we can improve this after we merge both affected PRs.

{ showProgrammaticallyDisabledModal && programmaticallyDisabledModal( {
isOpen: isProgrammaticallyDisabledModalOpen,
onClose: handleProgrammaticallyDisabledModalClose,
} ) }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As per a talk with @vraja-pro , we can ignore this for now and we can improve this after we merge both affected PRs.

@JorPV
Copy link
Copy Markdown
Contributor

JorPV commented Dec 18, 2025

Make stricter comparisons to check whether schema is enabled - Like we said, better to have a stricter comparison for checking if the schema is enabled

Agree, since the option shouldn't be considered disabled when it might be an empty string or other falsy value.

Fix schema settings page when schema is disabled both via setting and via filter - Like we said, to fix the case where schema is disabled both via setting and via filter, so that we don't allow users to toggle the setting on (which would have no effect any way upon save)

👍🏼 Only add the filter on frontend pages, not on admin.

Dont couple schema-related components with non-schema-related variables - That's something we hadn't discussed and it's a refactor I did to de-couple schema-related components fron non-schema-related logic at the Feature Card component. With the new code structure, we will be able to have other features in the future use their own confirmation modals and programmatically-disabled modals in a similar fashion.

Completely decouple the feature card from schema by renaming toggle handler - a follow-up of the above commit, to rename the toggle handler, to make it very clear that is now agnostic of schema (so, it can be used for any other feature toggle in the future, in theory)

🧠 good thinking! It makes sense to pass the modal as a prop.

CR: ✅

@leonidasmi
Copy link
Copy Markdown
Contributor Author

Acceptance: ✅ (as per this thread)

@leonidasmi leonidasmi added this to the 26.8 milestone Dec 19, 2025
@leonidasmi leonidasmi merged commit 724f3df into trunk Dec 19, 2025
39 of 40 checks passed
@leonidasmi leonidasmi deleted the 826-add-a-toggle-for-the-yoast-schema-graph branch December 19, 2025 08:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants