Repository: Automattic/jetpack
Pull request #42731: Jetpack SEO Enhancer: add business restriction
Author: login: CGastrell
Labels: name: Admin Page
description: React-powered dashboard under the Jetpack menu
name: [Feature] SEO Tools
description: Tools for improving a site's search engine optimization.
name: [Plugin] Jetpack
description: Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/
name: [Package] Plans
name: [Extension] SEO
name: [Block] AI Assistant
name: [Plugin] Wpcomsh
name: Coverage tests to be added later
description: Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR
Created: 2025-03-26T18:34:51Z | Merged: 2025-03-27T15:51:09Z

Fixes #42722 

## Proposed changes:
This PR adds a feature `ai-seo-enhancer` as a Business plan supports entry and sets the availability on the editor.

As required, a syncing change is proposed for wpcom: 178565-ghe-Automattic/wpcom

### Other information:

- [ ] Have you written new tests for your changes, if applicable?
- [ ] Have you checked the E2E test CI results, and verified that your changes do not break them?
- [ ] Have you tested your changes on WordPress.com, if applicable (if so, you'll see a generated comment below with a script to run)?

## Jetpack product discussion
pe4Cmx-37z-p2

## Does this pull request change what data or activity we track or use?
No

## Testing instructions:
Sandbox public API and patch with 178565-ghe-Automattic/wpcom

Add the SEO Enhancer filter `add_filter( 'ai_seo_enhancer_enabled', '__return_true' );` and switch to Beta blocks variation.

On a site with Business plan and above:
- Jetpack Settings -> Traffic should show a toggle for automatic SEO generation
- Block editor should show the SEO Enhancer on Jetpack sidebar, SEO panel

On a site with a lesser plan:
- Jetpack Settings -> Traffic should not show the toggle
- Block editor should not show the SEO Enhancer on Jetpack sidebar

Linked issue #42722: Make the feature available for Business and above
Restrict the tool (frontend/backend) to Business and above

Diff:
diff --git a/projects/packages/plans/changelog/add-jetpack-seo-enhancer-business-restriction b/projects/packages/plans/changelog/add-jetpack-seo-enhancer-business-restriction
new file mode 100644
index 000000000000..83dd6bc0caf5
--- /dev/null
+++ b/projects/packages/plans/changelog/add-jetpack-seo-enhancer-business-restriction
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add ai-seo-enhancer as Business plan supported feature
diff --git a/projects/packages/plans/src/class-current-plan.php b/projects/packages/plans/src/class-current-plan.php
index cef57d52b3d4..3577b26d7980 100644
--- a/projects/packages/plans/src/class-current-plan.php
+++ b/projects/packages/plans/src/class-current-plan.php
@@ -135,7 +135,9 @@ class Current_Plan {
 				'wooexpress-medium-bundle-monthly',
 				'wp_com_hundred_year_bundle_centennially',
 			),
-			'supports' => array(),
+			'supports' => array(
+				'ai-seo-enhancer',
+			),
 		),
 
 		'complete' => array(
diff --git a/projects/plugins/jetpack/_inc/client/traffic/seo.jsx b/projects/plugins/jetpack/_inc/client/traffic/seo.jsx
index adaa14d9ab64..2e7d519ecd77 100644
--- a/projects/plugins/jetpack/_inc/client/traffic/seo.jsx
+++ b/projects/plugins/jetpack/_inc/client/traffic/seo.jsx
@@ -19,6 +19,7 @@ import SettingsCard from 'components/settings-card';
 import SettingsGroup from 'components/settings-group';
 import analytics from 'lib/analytics';
 import { isSeoEnhancerAvailable } from 'state/initial-state';
+import { siteHasFeature } from 'state/site';
 import { isFetchingPluginsData, isPluginActive } from 'state/site/plugins';
 import CustomSeoTitles from './seo/custom-seo-titles.jsx';
 
@@ -223,13 +224,18 @@ export const SEO = withModuleSettingsFormHelpers(
 								{ __( 'Customize your SEO settings', 'jetpack' ) }
 							</span>
 						</ModuleToggle>
-						{ this.props.seoEnhancerAvailable && (
+						{ this.props.seoEnhancerAvailable && this.props.hasSeoEnhancer && (
 							<FormFieldset>
 								<ToggleControl
 									id="seo-enhancer"
-									disabled={ ! this.props.getOptionValue( 'seo-tools' ) }
+									disabled={
+										! this.props.getOptionValue( 'seo-tools' ) || ! this.props.hasSeoEnhancer
+									}
 									toggling={ this.props.isSavingAnyOption( 'ai_seo_enhancer_enabled' ) }
-									checked={ this.props.getOptionValue( 'ai_seo_enhancer_enabled' ) }
+									checked={
+										this.props.hasSeoEnhancer &&
+										this.props.getOptionValue( 'ai_seo_enhancer_enabled' )
+									}
 									onChange={ this.toggleSeoEnhancer }
 									label={
 										<span className="jp-form-toggle-explanation">
@@ -368,5 +374,6 @@ export default connect( state => {
 		siteData: state.jetpack.siteData.data,
 		seoEnhancerAvailable: isSeoEnhancerAvailable( state ),
 		state,
+		hasSeoEnhancer: siteHasFeature( state, 'ai-seo-enhancer' ),
 	};
 } )( SEO );
diff --git a/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
index 60f89742e707..acd763b79d56 100644
--- a/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
+++ b/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php
@@ -3097,7 +3097,7 @@ public static function get_updateable_data_list( $selector = '' ) {
 
 		// SEO Tools - SEO Enhancer. Only available to Automatticians.
 		// TODO: either remove this or make it available to all users by moving it to the main options array.
-		if ( apply_filters( 'ai_seo_enhancer_enabled', false ) ) {
+		if ( apply_filters( 'ai_seo_enhancer_enabled', false ) || apply_filters( 'ai_seo_enhancer_enabled_unrestricted', false ) ) {
 			$options['ai_seo_enhancer_enabled'] = array(
 				'description'       => esc_html__( 'Automatically generate SEO title, SEO description, and image alt text for new posts.', 'jetpack' ),
 				'type'              => 'boolean',
diff --git a/projects/plugins/jetpack/changelog/add-jetpack-seo-enhancer-business-restriction b/projects/plugins/jetpack/changelog/add-jetpack-seo-enhancer-business-restriction
new file mode 100644
index 000000000000..58006bf05793
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-jetpack-seo-enhancer-business-restriction
@@ -0,0 +1,4 @@
+Significance: minor
+Type: other
+
+Jetpack SEO Enhancer: add Business plan restriction
diff --git a/projects/plugins/jetpack/extensions/blocks/ai-assistant/ai-assistant.php b/projects/plugins/jetpack/extensions/blocks/ai-assistant/ai-assistant.php
index 606577024158..17a5da7f3a86 100644
--- a/projects/plugins/jetpack/extensions/blocks/ai-assistant/ai-assistant.php
+++ b/projects/plugins/jetpack/extensions/blocks/ai-assistant/ai-assistant.php
@@ -130,8 +130,11 @@ function () {
 				Jetpack_Gutenberg::set_extension_available( 'ai-proofread-breve' );
 			}
 
-			if ( apply_filters( 'ai_seo_enhancer_enabled', false ) ) {
+			if ( apply_filters( 'ai_seo_enhancer_enabled_unrestricted', false ) ) {
+				Jetpack_Gutenberg::set_extension_available( 'ai-seo-enhancer-enabled-unrestricted' );
 				Jetpack_Gutenberg::set_extension_available( 'ai-seo-enhancer' );
+			} elseif ( apply_filters( 'ai_seo_enhancer_enabled', false ) ) {
+				Jetpack_Gutenberg::set_availability_for_plan( 'ai-seo-enhancer' );
 			}
 		}
 	}
diff --git a/projects/plugins/jetpack/extensions/index.json b/projects/plugins/jetpack/extensions/index.json
index acdddf97b30a..0eeb592bfe14 100644
--- a/projects/plugins/jetpack/extensions/index.json
+++ b/projects/plugins/jetpack/extensions/index.json
@@ -82,7 +82,8 @@
 		"ai-list-to-table-transform",
 		"ai-seo-assistant",
 		"ai-use-chrome-ai-sometimes",
-		"ai-seo-enhancer"
+		"ai-seo-enhancer",
+		"ai-seo-enhancer-enabled-unrestricted"
 	],
 	"experimental": [],
 	"no-post-editor": [
diff --git a/projects/plugins/jetpack/extensions/plugins/seo/index.js b/projects/plugins/jetpack/extensions/plugins/seo/index.js
index 77ff451564b5..430036c5fa12 100644
--- a/projects/plugins/jetpack/extensions/plugins/seo/index.js
+++ b/projects/plugins/jetpack/extensions/plugins/seo/index.js
@@ -59,6 +59,9 @@ const supportsPublishSidebar =
 const isSeoAssistantEnabled =
 	getJetpackExtensionAvailability( 'ai-seo-assistant' )?.available === true;
 
+const isSeoEnhancerEnabledUnrestricted =
+	getJetpackExtensionAvailability( 'ai-seo-enhancer-enabled-unrestricted' )?.available === true;
+
 const isSeoEnhancerEnabled =
 	getJetpackExtensionAvailability( 'ai-seo-enhancer' )?.available === true &&
 	supportsPublishSidebar;
@@ -113,6 +116,8 @@ const Seo = () => {
 
 	const requiredPlan = getRequiredPlan( 'advanced-seo' );
 	const canShowUpsell = isAtomicSite() || isSimpleSite();
+	const hasRequiredPlanForEnhancer =
+		isSeoEnhancerEnabledUnrestricted || ! getRequiredPlan( 'ai-seo-enhancer' );
 
 	const jetpackSeoPanelProps = {
 		title: __( 'SEO', 'jetpack' ),
@@ -181,7 +186,9 @@ const Seo = () => {
 							<SeoAssistantSidebarEntrypoint disabled={ false } placement="jetpack-sidebar" />
 						</PanelRow>
 					) }
-					{ isSeoEnhancerEnabled && <SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } /> }
+					{ isSeoEnhancerEnabled && hasRequiredPlanForEnhancer && (
+						<SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } />
+					) }
 					<PanelRow
 						className={ clsx( {
 							'jetpack-seo-sidebar__feature-section': isSeoEnhancerEnabled,
@@ -208,7 +215,9 @@ const Seo = () => {
 
 			<PluginPrePublishPanel { ...jetpackSeoPublishPanelsProps }>
 				<div className="jetpack-seo-panel">
-					{ isSeoEnhancerEnabled && <SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } /> }
+					{ isSeoEnhancerEnabled && hasRequiredPlanForEnhancer && (
+						<SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } />
+					) }
 					<PanelRow>
 						<SeoTitlePanel />
 					</PanelRow>
diff --git a/projects/plugins/wpcomsh/changelog/add-jetpack-seo-enhancer-business-restriction b/projects/plugins/wpcomsh/changelog/add-jetpack-seo-enhancer-business-restriction
new file mode 100644
index 000000000000..b7f02ddd9ef0
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/add-jetpack-seo-enhancer-business-restriction
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add AI_SEO_ENHANCER feature for Business plans and above and JP complete plans
diff --git a/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php b/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php
index 6622da61b02b..d09b13b0cc31 100644
--- a/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php
+++ b/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php
@@ -328,6 +328,7 @@ class WPCOM_Features {
 	 * Public const for every mapped feature, sorted alphabetically.
 	 */
 	public const AI_ASSISTANT                      = 'ai-assistant';
+	public const AI_SEO_ENHANCER                   = 'ai-seo-enhancer';
 	public const AD_CREDIT_VOUCHERS                = 'ad-credit';
 	public const ADVANCED_SEO                      = 'advanced-seo';
 	public const AKISMET                           = 'akismet';
@@ -479,6 +480,10 @@ class WPCOM_Features {
 			self::WPCOM_PERSONAL_AND_HIGHER_PLANS,
 			self::JETPACK_COMPLETE_PLANS,
 		),
+		self::AI_SEO_ENHANCER                   => array(
+			self::WPCOM_BUSINESS_AND_HIGHER_PLANS,
+			self::JETPACK_COMPLETE_PLANS,
+		),
 		self::AD_CREDIT_VOUCHERS                => array(
 			self::WPCOM_BUSINESS_AND_HIGHER_PLANS,
 			self::WPCOM_PRO_PLANS,


Reviews:
user: login: dhasilva
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T19:17:06Z
user: login: CGastrell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:20:01Z
user: login: dhasilva
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T20:35:18Z
user: login: CGastrell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:10:55Z
user: login: dhasilva
body: LGTM
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T15:42:34Z

Review comments:
diff_hunk: @@ -208,7 +211,9 @@ const Seo = () => {
 
 			<PluginPrePublishPanel { ...jetpackSeoPublishPanelsProps }>
 				<div className="jetpack-seo-panel">
-					{ isSeoEnhancerEnabled && <SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } /> }
+					{ isSeoEnhancerEnabled && hasRequiredPlanForEnhancer && (
+						<SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } />
+					) }
path: projects/plugins/jetpack/extensions/plugins/seo/index.js
user: login: dhasilva
body: The check is required on the Post Publish panel as well
created_at: 2025-03-26T19:17:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -208,7 +211,9 @@ const Seo = () => {
 
 			<PluginPrePublishPanel { ...jetpackSeoPublishPanelsProps }>
 				<div className="jetpack-seo-panel">
-					{ isSeoEnhancerEnabled && <SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } /> }
+					{ isSeoEnhancerEnabled && hasRequiredPlanForEnhancer && (
+						<SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } />
+					) }
path: projects/plugins/jetpack/extensions/plugins/seo/index.js
user: login: CGastrell
body: Debatable. We're aiming to show the PostPublish summary anyway so I thought it would be ok to just show that even without the SEO Enhancer on. But you're right we should at least discuss this particular scenario.
created_at: 2025-03-26T20:20:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -208,7 +211,9 @@ const Seo = () => {
 
 			<PluginPrePublishPanel { ...jetpackSeoPublishPanelsProps }>
 				<div className="jetpack-seo-panel">
-					{ isSeoEnhancerEnabled && <SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } /> }
+					{ isSeoEnhancerEnabled && hasRequiredPlanForEnhancer && (
+						<SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } />
+					) }
path: projects/plugins/jetpack/extensions/plugins/seo/index.js
user: login: dhasilva
body: Yeah, you're right, good point. I just need to make sure the "edit metadata" button would work as expected.
created_at: 2025-03-26T20:35:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -208,7 +211,9 @@ const Seo = () => {
 
 			<PluginPrePublishPanel { ...jetpackSeoPublishPanelsProps }>
 				<div className="jetpack-seo-panel">
-					{ isSeoEnhancerEnabled && <SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } /> }
+					{ isSeoEnhancerEnabled && hasRequiredPlanForEnhancer && (
+						<SeoEnhancer disableAutoEnhance={ ! canHaveAutoEnhance } />
+					) }
path: projects/plugins/jetpack/extensions/plugins/seo/index.js
user: login: CGastrell
body: oh, good point as well, we need to test this on wpcom
created_at: 2025-03-27T14:10:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-26T18:35:47Z
body: <!-- wpcom-reminder-comment -->
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

- To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate [the Jetpack Beta plugin](https://jetpack.com/download-jetpack-beta/). Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the `add/jetpack-seo-enhancer-business-restriction` branch.
- To test on Simple, run the following command on your sandbox:
```
bin/jetpack-downloader test jetpack add/jetpack-seo-enhancer-business-restriction
```
```
bin/jetpack-downloader test jetpack-mu-wpcom-plugin add/jetpack-seo-enhancer-business-restriction
```

*Interested in more tips and information?*

- In your local development environment, use the `jetpack rsync` command to sync your changes to a WoA dev blog.
- Read more about our development workflow here: PCYsg-eg0-p2
- Figure out **when your changes will be shipped to customers** here: PCYsg-eg5-p2
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-26T18:35:59Z
body: **Thank you for your PR!**

When contributing to Jetpack, we have [a few suggestions](https://github.com/Automattic/jetpack/blob/trunk/.github/PULL_REQUEST_TEMPLATE.md) that can help us test and review your patch:<br>
- :white_check_mark: Include a description of your PR changes.<br>
- :white_check_mark: Add a "[Status]" label (In Progress, Needs Review, ...).<br>
- :white_check_mark: Add a "[Type]" label (Bug, Enhancement, Janitorial, Task).<br>
- :white_check_mark: Add testing instructions.<br>
- :white_check_mark: Specify whether this PR includes any changes to data or privacy.<br>
- :white_check_mark: Add changelog entries to affected projects<br>


This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation :robot:

******

**Follow this PR Review Process:**

1. Ensure all required checks appearing at the bottom of this PR are passing.
2. Make sure to test your changes on all platforms that it applies to. *You're responsible for the quality of the code you ship*.
3. You can use [GitHub's Reviewers functionality](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to request a review.
4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!
******

**Jetpack plugin:**

The Jetpack plugin has different release cadences depending on the platform:

- WordPress.com Simple releases happen as soon as you deploy your changes after merging this PR (PCYsg-Jjm-p2).
- WoA releases happen weekly.
- Releases to self-hosted sites happen monthly:
    - Scheduled release: _April 1, 2025_
    - Code freeze: _March 30, 2025_


If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

******

**Wpcomsh plugin:**

- Next scheduled release: _Atomic deploys happen twice daily on weekdays (p9o2xV-2EN-p2)_


If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

author_association: CONTRIBUTOR
user: login: jp-launch-control[bot]
created_at: 2025-03-26T18:47:25Z
body: ### Code Coverage Summary

Coverage changed in 2 files.

| File | Coverage | Δ% | Δ Uncovered |
| :--- | :------: | :---: | :---: |
| projects/plugins/jetpack/extensions/blocks/ai-assistant/ai-assistant.php | 0/102 (0.00%) | 0.00% | 3 ❤️‍🩹 |
| projects/plugins/jetpack/extensions/plugins/seo/index.js | 0/42 (0.00%) | 0.00% | 2 ❤️‍🩹 |



[Full summary](https://jetpackcodecoverage.atomicsites.blog/prs/42731/) · [PHP report](https://jetpackcodecoverage.atomicsites.blog/prs/42731/php/) · [JS report](https://jetpackcodecoverage.atomicsites.blog/prs/42731/js/)

<sub>Coverage check overridden by https://github.com/Automattic/jetpack/labels/Coverage%20tests%20to%20be%20added%20later.</sub>
author_association: NONE