Direct answer
Keep the product published and leave its price, stock, and catalog visibility alone. Save one product-level flag, return false only when WooCommerce asks whether that exact product is purchasable, and reject a direct add-to-cart request for the same product on the server.
Disable Add to Cart for Selected Products follows that narrow boundary. Free handles one native simple product. Pro adds bounded variation and category choices plus one short unavailable message.
Choose the outcome before choosing a catalog-mode plugin
These three requests sound similar but change different store behavior:
| Merchant request | Safe change | What should remain native |
|---|---|---|
| Keep one product visible but do not sell it online | Mark that exact product not purchasable | Page, URL, price, stock, search visibility, other products |
| Hide every price and purchase button | A full catalog-mode policy | Product and order data still need explicit protection |
| Replace buying with an enquiry or quote flow | A separate customer-contact workflow | Consent, delivery, forms, and lead data need their own contract |
If the actual job is only the first row, installing a global catalog system creates more settings, templates, role rules, and compatibility surfaces than the outcome requires.
A public WooCommerce support topic captures the exact beginner-visible need: keep product information and price while disabling Add to Cart. Another merchant reported a button still appearing in a product list, and a separate report described purchase behavior remaining broken after deactivation. These are individual reports, not proof that every catalog plugin fails. They do show why storefront scope and rollback need explicit tests.
Use WooCommerce's purchasability decision
WooCommerce already owns product types, prices, stock state, and whether a product can be purchased. Its current WC_Product source exposes the native purchasability path. A focused plugin can narrow that decision for an exact product instead of editing the product's commercial data.
That distinction matters:
- Display state: the product can remain published, searchable, and priced.
- Purchase state: WooCommerce can treat it as not purchasable.
- Commerce data: stock, price, tax, coupons, carts, and orders stay unchanged.
Do not use a stock value of zero as a substitute. Stock describes inventory, not whether the merchant wants this product available offline, in person, by quotation, or only as a reference. Do not remove the button with CSS either. Hidden markup can still be submitted directly, and another theme or block can render a different button.
Enforce the same decision on a direct request
A missing button is not authorization. A shopper, bot, old bookmark, or custom frontend can send an add request without using the visible product form.
The current WooCommerce WC_Cart source shows that cart admission has a server-side validation path. A bounded implementation should:
- Resolve one exact product or variation identifier.
- Read only the owned flag needed for that product.
- Defer when identity or product type is uncertain.
- Return a failed validation decision for a proven flagged product.
- Add at most one escaped notice.
- Leave the cart unchanged.
It should not remove existing cart lines, redirect the customer, lower stock, rewrite the price, or create a quote request as a side effect.
Set up one product safely
- Back up the database and retain the current plugin ZIP.
- Edit one native simple WooCommerce product.
- Open Product data > General.
- Enable Disable online purchase.
- Update the product.
- Open the public product while signed out.
- Confirm the page and price remain visible and the purchase form is unavailable.
- Test one unflagged product.
- Test a direct add request for the flagged product and confirm WooCommerce rejects it once.
The pass condition is not merely “the button disappeared.” The pass condition is that the exact product remains inspectable, cannot enter the cart through the supported server path, and every unrelated product retains native behavior.
Diagnose a product that can still be bought
| Symptom | Check first | Safe expected result |
|---|---|---|
| Checkbox is missing | Product type, WooCommerce version, editor capability | Field appears only for a supported simple product |
| Single-product button remains | Cache, native template, purchasability result | Native template removes the purchase action |
| Product-list button remains | Block or theme ownership | Direct add is still rejected; no CSS guessing |
| Direct URL adds the item | Exact product/variation mapping and validation hook | One rejection and no cart mutation |
| Other products stop selling | Scope of saved flag and global catalog plugins | Only the selected product changes |
| Deactivation does not restore buying | Cache and other commerce extensions | Native WooCommerce behavior returns with StoreFixKit inactive |
If a default WooCommerce product page is correct but a quick-view or builder surface is not, the surface has a different owner. Fixing that with a universal selector is fragile: markup, localization, and asynchronous rendering can all change. Treat support for that surface as a named adapter with its own tests, not an assumed extension of the core job.
Keep Pro expansion bounded
Paid value can reduce repeated work without turning the plugin into a catalog suite. One deterministic rule order is enough:
- A valid Free simple-product flag.
- A Pro variation flag.
- The variation's parent-product flag.
- One of at most 25 configured category IDs.
- Native WooCommerce behavior.
The unavailable message should be plain text and short. It should explain that the product cannot be bought online, not collect customer data, open a popup, promise availability, or infer the merchant's offline process.
When Pro or licensing is unavailable, Free must continue. Billing state should never decide whether a previously flagged Free product suddenly re-enters the cart.
Test rollback as a product feature
A safe release test covers more than activation:
- Install the prior version.
- Seed valid plugin settings and ordinary WooCommerce product and order data.
- Update to the current version.
- Downgrade to the prior version.
- Roll forward again.
- Deactivate Pro, then Free.
- Run default uninstall and confirm recoverable settings remain.
- Run explicit cleanup and confirm only owned data is removed.
- Restore the exact pre-test state.
The StoreFixKit 0.1.2 release drill exercised that sequence against a real WordPress and WooCommerce installation. Packaged Plugin Check reported zero errors and zero warnings across Free and Pro. That is dated release evidence for this package, not a guarantee about every theme or third-party product type.
Resource boundary
An exact product decision does not need a catalog scan, custom table, cron task, queue, telemetry event, frontend script, stylesheet, or public API request.
In the Batch 14 measured product-page run, all six Free and Pro plugins together completed 25 requests with the plugins active and 25 with them inactive. The active state added one database query at the median and P95, 2 MB of measured PHP memory, no plugin HTTP call, no plugin-owned write, and no unrelated frontend asset. Hosting, cache, theme, and plugin stacks vary, so repeat the benchmark on any high-traffic production configuration.
Common mistakes
Hiding the button with CSS
CSS changes presentation only. It does not prove a direct add request will be rejected.
Setting stock to zero
That changes inventory truth and can affect backorders, reports, feeds, and availability messages.
Disabling checkout for the whole store
That expands one product decision into a global commercial policy and can block unrelated sales.
Removing a flagged item from the cart automatically
Silent removal changes customer state. Reject admission before mutation and explain the result once.
Supporting every custom product type by default
Bundles, subscriptions, bookings, composites, and custom Store API clients have separate ownership. Fail passive until one exact contract is implemented and tested.
Market boundary
The official WooCommerce Marketplace has a current Catalog Visibility Options category offer, and WordPress.org has established catalog-mode plugins. That proves the broader category exists. It does not prove StoreFixKit sales, conversion, or $10,000 monthly revenue.
The narrower commercial hypothesis is that a nontechnical merchant will choose a one-job product because it takes one checkbox, keeps the price visible, and has a verified rollback boundary. Measure that with legitimate installs, completed setup, direct-add rejections, trial starts, paid conversions, refunds, renewals, and support minutes. Do not substitute competitor installs or marketplace price for StoreFixKit traction.
FAQ
How do I disable Add to Cart for one WooCommerce product but keep the price?
Leave the product published and priced, save one product flag, return false through WooCommerce's native purchasability decision, and enforce the same flag during server-side add-to-cart validation.
Does disabling purchase change stock?
Not in this design. Stock, backorders, and inventory reports remain entirely with WooCommerce.
Is hiding the Add to Cart button enough?
No. The server must reject a direct request for the same exact product.
What happens after deactivation?
The runtime filters disappear immediately, so WooCommerce returns to its native purchase decision. The owned flag can remain dormant for recovery until explicit cleanup is requested.
Can I disable a variation or a whole category?
Yes as bounded Pro choices with explicit variation, parent, and category precedence. Free deliberately limits the job to one native simple product.
Focused plugin
Use the one-job implementation.
Keep one selected WooCommerce product and its price visible while preventing that exact product from being bought online, without changing stock, carts, orders, templates, or customer data.