Store Setup and Launch

This content was generated with the assistance of AI and should be verified against the official documentation of each platform before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

A WooCommerce store is a WordPress site plus about a dozen decisions: where you sell and in which currency, whether prices include tax, which zones you ship to and what it costs, how customers pay, which e-mails they get, what the shop looks like and what you do every week to keep it safe. This page walks through those decisions in the order that avoids rework, for WooCommerce 11.2.1 on WordPress 7.1.3 (checked on 2026-10-27), using the Settings screens as the reference and WP-CLI to make the result repeatable. It covers the free plugin and the payment, tax and shipping features it ships with; paid extensions are named only to say what core cannot do. Where a step needs a WordPress.org, WordPress.com or Woo account, or a payment provider’s onboarding (WooPayments, PayPal, Stripe), the page says so and does not run it.

Everything shown with a command was run on a clean WordPress 7.1.3 site with WooCommerce 11.2.1 (see Installation and Local Environments for how the site was built), and the complete script of the Pocket Coffee shop was run end to end on a second clean site. Pocket Coffee is the running example of the section: single-origin beans in 18 variants, a starter kit, a Coffee Club discount, two shipping zones and euro prices that include Spanish VAT. The sibling pages go deeper into each area: Products and Product Types, Shipping Methods, Taxes, Coupons, Currencies and i18n, Payment Gateways, Emails and Themes: Block and Classic.

Nine steps to set up a WooCommerce store in three rows: store details, general settings, products, tax, shipping, payments, accounts and e-mails, theme and extensions, and security with the launch checks

Decide before you click

The Settings screens ask for facts that are hard to change once orders exist, so settle these first. They are generic store-planning questions (the books about WooCommerce spend most of their pages on them) and the Pocket Coffee answers show what a decision looks like.

Decision Why it matters later Pocket Coffee

Selling and shipping countries

Limits the address fields, the tax rules and the shipping zones; a country you do not sell to cannot check out

Spain plus eight EU countries (ES, DE, FR, IT, PT, NL, BE, AT, IE)

Currency

One shop currency per site; orders store it, so changing it later does not convert old orders

EUR; a second currency needs an extension (core has none)

Prices with or without tax

Decides how every price is entered and how tax is shown; changing it re-interprets the prices you typed

Entered and shown with 21 % VAT, because Spanish consumers expect it

What the catalogue looks like

Simple versus variable products, SKU scheme, attributes and categories drive filters, stock and reports

PC-<ORIGIN>-<GRIND>-<SIZE> SKUs, 18 variants, categories Beans and Equipment

Shipping promise

Zones, a free-shipping threshold and parcel limits must be supported by a method you can actually configure

Spain 4.90 EUR flat, free from 40 EUR; other EU 9.90 EUR flat

Payment methods

The gateway decides fees, refunds, recurring payments and the PCI burden

Bank transfer in the examples; a card gateway is a provider decision

Who sees the store

Guest checkout and account creation change conversion and what you must say about personal data

Guest checkout on; accounts optional

Legal pages and policies

Terms, privacy policy and refund policy have to exist before the first order

Pages created from drafts before launch

None of this is legal or tax advice. The tax rates, the VAT scheme for sales to other EU countries (the One Stop Shop scheme) and the legal pages depend on where the business is established; the page uses the standard Spanish rate of 21 % as an illustration only.

Onboarding: the Core Profiler and the task list

Activating WooCommerce starts the Core Profiler in the browser (it replaced the Setup Wizard of the 3.x era). It collects store details and offers recommended extensions and a theme; the answers are stored in the option woocommerce_onboarding_profile, and the WooCommerce home screen then shows a task list. The tasks are classes under src/Admin/Features/OnboardingTasks/Tasks/ in 11.2.1 with the ids store_details, products, payments, tax, shipping, marketing, appearance (the theme step), extend-store, launch-your-store and get-mobile-app, plus further ids (customize-store, review-shipping, woocommerce-payments, shipping-recommendation, tour-in-app-marketplace) that the source defines for some configurations. A task is only a link to the screens below, so skipping the Core Profiler loses nothing that cannot be set by hand.

The flow needs a browser, an administrator session and, for some of its offers, a connection to a WordPress.com or Woo account; it was not run for this page. The scripted route below does not depend on it. The legacy class WC_Admin_Setup_Wizard still ships in includes/admin/class-wc-admin-setup-wizard.php, and books that describe a page-by-page wizard (location, currency, payments, shipping, recommended extras) describe that older flow.

The Settings tabs

All store settings live under WooCommerce > Settings. In 11.2.1 the tab ids and labels, read from WC_Admin_Settings::get_settings_pages(), are general (General), products (Products), tax (Tax), shipping (Shipping), checkout (Payments), account (Accounts and Privacy), email (Emails), integration (Integration), site-visibility (Site visibility), point-of-sale (Point of Sale) and advanced (Advanced). The Tax tab only exists when tax calculation is on (woocommerce_calc_taxes is yes). The tab id is the value of the tab parameter in the URL: /wp-admin/admin.php?page=wc-settings&tab=shipping&section=options.

Eleven WooCommerce Settings tabs with their sections and main options: General, Products, Tax, Shipping, Payments, Accounts and Privacy, Emails, Integration, Site visibility, Point of Sale and Advanced

Every setting is a WordPress option whose name is the field id, so a value can be read and changed from WP-CLI with wp option get and wp option update, and the same fields are exposed by the REST API under /wp-json/wc/v3/settings (listing a group, then reading or updating one field; the REST settings route was not exercised for this page). Changing an option directly skips the screen’s validation and, for a few settings, the side effects that run on save, so use WP-CLI for the simple fields below and the screen when a setting has dependencies.

General

Option id What it controls Pocket Coffee value

woocommerce_store_address, woocommerce_store_address_2, woocommerce_store_city, woocommerce_store_postcode

The business address, used for the tax base, shipping origin and invoices

Calle Ejemplo 1, Madrid, 28001

woocommerce_default_country

Base country and state in the form ES:M (country, colon, state code); decides the default tax and shipping location

ES:M

woocommerce_allowed_countries, woocommerce_all_except_countries, woocommerce_specific_allowed_countries

Selling location(s): all countries, all except some, or specific ones (a list stored as an array)

specific, nine EU countries

woocommerce_ship_to_countries, woocommerce_specific_ship_to_countries

Shipping location(s); empty means the same as selling locations

empty

woocommerce_default_customer_address

Default customer location for visitors who have not entered an address (none, shop base, or geolocation)

unchanged

woocommerce_address_autocomplete_enabled

Address autocomplete at checkout

unchanged

woocommerce_calc_taxes

Switches tax calculation on and adds the Tax tab

yes

woocommerce_enable_coupons, woocommerce_calc_discounts_sequentially

Coupon support and whether coupons apply one after another

coupons on

woocommerce_currency, woocommerce_currency_pos, woocommerce_price_thousand_sep, woocommerce_price_decimal_sep, woocommerce_price_num_decimals

Currency code and how prices are formatted

EUR, defaults

wp option update woocommerce_store_address "Calle Ejemplo 1"
wp option update woocommerce_store_city Madrid
wp option update woocommerce_store_postcode 28001
wp option update woocommerce_default_country ES:M
wp option update woocommerce_currency EUR
wp option update woocommerce_allowed_countries specific
wp option update woocommerce_specific_allowed_countries '["ES","DE","FR","IT","PT","NL","BE","AT","IE"]' --format=json
wp option get woocommerce_specific_allowed_countries --format=json
Success: Updated 'woocommerce_store_address' option.
Success: Updated 'woocommerce_store_city' option.
Success: Updated 'woocommerce_store_postcode' option.
Success: Updated 'woocommerce_default_country' option.
Success: Updated 'woocommerce_currency' option.
Success: Updated 'woocommerce_allowed_countries' option.
Success: Updated 'woocommerce_specific_allowed_countries' option.
["ES","DE","FR","IT","PT","NL","BE","AT","IE"]

Products, Inventory, Downloadable and Advanced

Option id What it controls Default in 11.2.1

woocommerce_shop_page_id

The Shop page (the product archive)

set by the install

woocommerce_cart_redirect_after_add, woocommerce_enable_ajax_add_to_cart

Redirect to the cart after adding, and AJAX add-to-cart buttons on archives

no, yes

woocommerce_weight_unit, woocommerce_dimension_unit

Units for product weight and size; shipping rules and parcel limits use them

lbs, in in the field definition; Pocket Coffee sets kg, cm

woocommerce_enable_reviews, woocommerce_enable_review_rating, woocommerce_review_rating_required, woocommerce_review_rating_verification_label, woocommerce_review_rating_verification_required

Customer reviews, star ratings, whether a rating is mandatory and whether only verified owners can review

reviews, ratings and labels yes; verification required no

woocommerce_placeholder_image

The image shown for products without a picture

empty

Section Inventory (&section=inventory) holds woocommerce_manage_stock (yes), woocommerce_hold_stock_minutes (60: how long an unpaid order reserves stock), woocommerce_notify_low_stock and woocommerce_notify_no_stock (yes), woocommerce_notify_backorder (yes), woocommerce_stock_email_recipient (the store e-mail address), woocommerce_notify_low_stock_amount (2), woocommerce_notify_no_stock_amount (0), woocommerce_hide_out_of_stock_items (no) and woocommerce_stock_format. Section Downloadable products holds woocommerce_file_download_method (force), woocommerce_downloads_require_login, woocommerce_downloads_grant_access_after_payment (yes) and the options that control the hash added to file names. The Advanced section of the Products tab has woocommerce_product_match_featured_image_by_sku (no). Pocket Coffee keeps stock tracking on because every variant has its own stock (25 units each in the script below).

Tax

The Tax tab (woocommerce_calc_taxes must be yes) has these options on its first screen and one section per tax class: woocommerce_prices_include_tax, woocommerce_tax_based_on (shipping, billing or base), woocommerce_shipping_tax_class (inherit), woocommerce_tax_round_at_subtotal, woocommerce_tax_classes (additional classes, one per line), woocommerce_tax_display_shop, woocommerce_tax_display_cart (incl or excl), woocommerce_price_display_suffix and woocommerce_tax_total_display (itemized or single). The sections are Standard rates, Reduced rate rates and Zero rate rates, the three classes that exist after the install. The shop and the cart display settings default to excl (prices shown without tax), which is wrong for a shop that sells to consumers in the EU, so set both to incl.

Shipping

The first screen of the Shipping tab lists the shipping zones; the sections are Shipping options (woocommerce_enable_shipping_calc, woocommerce_shipping_cost_requires_address, woocommerce_shipping_hide_rates_when_free, woocommerce_ship_to_destination with billing as default, woocommerce_shipping_debug_mode), Classes (shipping classes, for example "bulky") and Local pickup (pickup locations). Zones and methods are described in their own section below.

Payments (tab id checkout)

The Payments tab lists the installed gateways in the order in which they are offered; in the core install the gateways are bacs (Direct bank transfer), cheque (Check payments) and cod (Cash on delivery), none of which charges a fee. Gateway suggestions, including WooPayments, are offered from this screen; their onboarding was not run. See the Payments section below.

Accounts and Privacy

The options are woocommerce_enable_guest_checkout, woocommerce_enable_checkout_login_reminder, woocommerce_enable_signup_and_login_from_checkout, woocommerce_enable_myaccount_registration, woocommerce_registration_generate_password, woocommerce_enable_delayed_account_creation (create the account after the order), the personal data options (woocommerce_erasure_request_removes_order_data, woocommerce_erasure_request_removes_download_data, woocommerce_allow_bulk_remove_personal_data), the privacy policy text options (woocommerce_registration_privacy_policy_text, woocommerce_checkout_privacy_policy_text) and the retention options (woocommerce_delete_inactive_accounts, woocommerce_trash_pending_orders, woocommerce_trash_failed_orders, woocommerce_trash_cancelled_orders, woocommerce_anonymize_refunded_orders, woocommerce_anonymize_completed_orders). The retention options are the GDPR housekeeping that the books do not cover; decide the periods with the person responsible for data protection and write the privacy policy page that the text options point to.

Emails

See the E-mails section below.

Integration, Site visibility, Point of Sale

Integration lists the integrations registered by core and extensions; the only one in 11.2.1 is MaxMind Geolocation (maxmind_geolocation). Site visibility controls the coming-soon mode described in the launch section. Point of Sale holds the store name, address, phone, e-mail and refund and returns policy that point-of-sale receipts print (woocommerce_pos_store_name, woocommerce_pos_store_address, woocommerce_pos_store_phone, woocommerce_pos_store_email, woocommerce_pos_refund_returns_policy); it matters only if you use the point-of-sale feature.

Advanced

The Advanced tab has the sections Page setup, REST API keys, Webhooks, WooCommerce.com (usage tracking woocommerce_allow_tracking, default no, and marketplace suggestions woocommerce_show_marketplace_suggestions, default yes), Blueprint (beta) and Features.

  • Page setup maps the Cart, Checkout, My account and Terms pages (woocommerce_cart_page_id, woocommerce_checkout_page_id, woocommerce_myaccount_page_id, woocommerce_terms_page_id) and the endpoint slugs such as order-pay, order-received, orders, view-order, downloads, edit-account and edit-address. The option ids woocommerce_force_ssl_checkout and woocommerce_unforce_ssl_checkout still appear in the 11.2.1 field definitions; the books' "HTTPS only on checkout" advice is outdated, and a store should serve every page over HTTPS.

  • REST API keys and Webhooks are covered on the REST API and Webhooks page.

  • Features lists the toggles of the features that can still be switched: woocommerce_custom_orders_table_enabled (HPOS, off after a scripted install), woocommerce_custom_orders_table_data_sync_enabled, woocommerce_analytics_enabled (yes), woocommerce_feature_order_attribution_enabled (yes), woocommerce_feature_rate_limit_checkout_enabled (no), woocommerce_feature_block_email_editor_enabled (no), woocommerce_feature_email_improvements_enabled (no), woocommerce_feature_cost_of_goods_sold_enabled (no), woocommerce_feature_product_gallery_videos_enabled (no), woocommerce_hpos_datastore_caching_enabled (no), woocommerce_feature_remote_logging_enabled (yes) and woocommerce_feature_blueprint_enabled (yes). Some of these exist for a release or two and then become permanent; treat the screen as the source of truth for the version you run.

Tax

Tax is where a launch usually goes wrong, so the decisions are explicit. With woocommerce_prices_include_tax set to yes the price you type is the price the customer pays and WooCommerce extracts the tax from it; with no tax is added on top. woocommerce_tax_based_on decides which address selects the rate: shipping (the default), billing or base (the shop address). Rates are rows of country, state, postcode, city, rate, name, priority, compound and shipping (whether the rate also applies to shipping), grouped by tax class. A product’s Tax status (taxable, shipping only or none) and Tax class choose the class; the classes after install are Standard rate, Reduced rate and Zero rate.

Pocket Coffee sells with tax included and one Spanish rate:

wp option update woocommerce_calc_taxes yes
wp option update woocommerce_prices_include_tax yes
wp option update woocommerce_tax_display_shop incl
wp option update woocommerce_tax_display_cart incl
wp --user=shopadmin wc tax create --country=ES --rate=21.0000 --name=IVA --priority=1 --shipping=true --class=standard --porcelain
wp --user=shopadmin wc tax list --fields=id,country,rate,name,shipping,class --format=table
wp --user=shopadmin wc tax_class list --fields=slug,name --format=table
1
id	country	rate	name	shipping	class
1	ES	21.0000	IVA	1	standard
slug	name
standard	Standard rate
reduced-rate	Reduced rate
zero-rate	Zero rate

The same data goes through Settings > Tax > Standard rates > Insert row or the import CSV (sample-data/sample_tax_rates.csv in the plugin shows the columns). Rates entered by hand are not a tax-compliance system: they do not know about EU One Stop Shop thresholds, VAT numbers for business customers or changing rates. Extensions or the automated tax calculation of the WooCommerce Shipping & Tax extension (it needs a connected account and was not run here) cover that, and the Taxes, Coupons, Currencies and i18n page goes through the calculation.

Products and variations

Products live under Products. The classic product editor is the editor in 11.2.1: the block-based product editor was deprecated in 10.9.0 and removed in 11.0.0, and old guides that show its screens no longer match. The product types are simple, variable, grouped and external (affiliate); virtual and downloadable are checkboxes on a simple or variable product. A product has a name, description, price, SKU, stock, weight and dimensions, categories, tags, attributes, a tax class, a shipping class and images; the Products and Product Types page covers each tab. A variable product has attributes flagged "used for variations" and one variation per combination; each variation has its own SKU, price, weight and stock.

Pocket Coffee’s catalogue is one simple product and one variable product with three attributes (origin, grind, size), 18 variations, per-variant stock and the SKU pattern PC-<ORIGIN3>-<GRIND3>-<SIZE>. The prices are 8.90 EUR for 250 g and 29.90 EUR for 1 kg, with Ethiopia 1.00 EUR dearer per 250 g. Created from WP-CLI (the loop at the end of the script below creates the variations):

wp --user=shopadmin wc product_cat create --name=Beans --porcelain
wp --user=shopadmin wc product_cat create --name=Equipment --porcelain
wp --user=shopadmin wc product create --name="Starter kit" --type=simple --regular_price=24.00 --sku=PC-KIT \
  --weight=1.2 --manage_stock=true --stock_quantity=30 --categories='[{"id":17}]' --porcelain
wp --user=shopadmin wc product_variation list 11 --per_page=30 --fields=id,sku,regular_price,weight,stock_quantity --format=table
16
17
10
id	sku	regular_price	weight	stock_quantity
29	PC-BRA-FIL-1KG	29.90	1	25
28	PC-BRA-FIL-250	8.90	0.25	25
...
17	PC-ETH-FIL-1KG	33.90	1	25
16	PC-ETH-FIL-250	9.90	0.25	25
15	PC-ETH-ESP-1KG	33.90	1	25
14	PC-ETH-ESP-250	9.90	0.25	25
13	PC-ETH-WHL-1KG	33.90	1	25
12	PC-ETH-WHL-250	9.90	0.25	25

(The middle rows of the real 18-row table are omitted.) Three rules save trouble. Custom attributes (typed in the product) are private to that product, while global attributes (Products > Attributes) create taxonomy terms that the shop can filter on, so the "origin" attribute that Pocket Coffee wants to filter by should be global; the script uses product-level attributes to stay short. A variation’s attributes must name the exact attribute and option strings of the parent, or the variation is created but never matches a selection. And creating hundreds of variations through the editor is slow; use the CSV importer (Products > Import, a Type of variation and a Parent SKU) or the batch REST endpoint for large catalogues.

Shipping zones

A shipping zone is a set of locations (countries, states, postcodes) with shipping methods attached; a customer gets the methods of the first zone, in the zone order, that matches the address, and the built-in "Locations not covered by your other zones" zone is the fallback. Methods are Flat rate, Free shipping and Local pickup in core; carriers' methods come from extensions. The facts below were measured on the Pocket Coffee zones.

wp --user=shopadmin wc shipping_zone create --name="Spain" --porcelain
wp --user=shopadmin wc shipping_zone create --name="European Union (excluding Spain)" --porcelain
wp --user=shopadmin wc shipping_zone_method create 1 --method_id=free_shipping \
  --settings='{"requires":"min_amount","min_amount":"40"}' --porcelain
wp --user=shopadmin wc shipping_zone_method create 1 --method_id=flat_rate \
  --settings='{"cost":"4.90","tax_status":"taxable"}' --porcelain
wp --user=shopadmin wc shipping_zone_method create 2 --method_id=flat_rate \
  --settings='{"cost":"9.90","tax_status":"taxable"}' --porcelain
wp eval '
$es = new WC_Shipping_Zone( 1 ); $es->add_location( "ES", "country" ); $es->set_zone_order( 0 ); $es->save();
$eu = new WC_Shipping_Zone( 2 ); $eu->set_zone_order( 1 );
foreach ( array( "DE", "FR", "IT", "PT", "NL", "BE", "AT", "IE" ) as $c ) { $eu->add_location( $c, "country" ); }
$eu->save();
foreach ( WC_Shipping_Zones::get_zones() as $z ) {
	echo $z["zone_name"], ": ", $z["formatted_zone_location"], " -> ";
	foreach ( $z["shipping_methods"] as $m ) { echo $m->id, "(", $m->get_option( "cost" ) ?: $m->get_option( "min_amount" ), ") "; }
	echo "\n";
}'
1
2
1
2
3
Spain: Spain -> free_shipping(40) flat_rate(4.90)
European Union (excluding Spain): Germany, France, Italy, Portugal, Netherlands, Belgium, Austria, Ireland -> flat_rate(9.90)

The calls to WC_Shipping_Zone are needed because the wp wc shipping_zone_location group only has list (there is no create or update command); the REST route PUT /wc/v3/shipping/zones/<id>/locations does the same with a JSON array. Four behaviours are worth knowing before they surprise a customer.

  1. The continent code EU means Europe, not the European Union. A first attempt that added the location type continent with code EU listed the zone as "Europe", which includes countries outside the EU. The zone above lists the member states one by one.

  2. A flat rate cost is entered without tax. With prices that include tax, a flat rate of 4.90 with Tax status: Taxable costs 4.90 plus 21 % VAT, which the cart showed as shipping 4.90 and shipping tax 1.03 (5.93 in total). To charge 4.90 all-in, enter the net amount (4.0496) or set the method untaxable. The cost field also takes formulas such as 10.00 * [qty], [cost] and [fee percent="10" min_fee="20"].

  3. The free-shipping threshold follows the cart display setting. The minimum order amount is compared with the subtotal as it is displayed. With woocommerce_tax_display_cart at its default excl, a cart with two starter kits (48.00 with VAT, 39.67 without) did not get free shipping against a 40.00 minimum; after switching to incl it did. The method’s Coupons discounts checkbox (ignore_discounts) decides whether coupons reduce the amount that is compared.

  4. The first method in the zone is preselected. With free shipping created after the flat rate, a cart that qualified for free shipping showed both rates and selected the flat rate; reordering the methods so that free shipping comes first (the drag handles on the zone screen) made it the selected rate. In the REST and CLI route, create the free method first, as the script does.

wp wc shipping_zone_method list 1 --user=shopadmin --fields=instance_id,method_id,order --format=table
instance_id	method_id	order
2	free_shipping	1
1	flat_rate	2

The output is from the first test site after its two methods were reordered; the order is the method_order column of wp_woocommerce_shipping_zone_methods. The setting Hide shipping rates when free shipping is available (woocommerce_shipping_hide_rates_when_free) removes the other rates once the free one qualifies, which makes the default question moot; Pocket Coffee leaves it off so that customers can still choose a faster method later.

Core has no per-parcel weight limit. The 5 kg limit of the Pocket Coffee spec is therefore expressed through the structure of the catalogue (a shipping class for bulky items, quantity-based cost formulas) or an extension; it cannot be set as a single number on the flat-rate method. Local pickup, shipping classes and the rate pipeline are covered on the Shipping Methods page.

Payments

A gateway is an object that decides which payment methods the checkout offers and what happens to the order afterwards. In the core install there are three, and none talks to a provider:

wp --user=shopadmin wc payment_gateway update bacs --enabled=true --porcelain
wp --user=shopadmin wc payment_gateway list --fields=id,title,enabled --format=table
bacs
id	title	enabled
bacs	Direct bank transfer	1
cheque	Check payments
cod	Cash on delivery

Enabling a gateway sends the administrator an e-mail through the admin_payment_gateway_enabled message, so the command printed sendmail: can't connect to remote host (127.0.0.1): Connection refused on a machine with no mail server; the gateway was still enabled. bacs takes the account details shown to the customer on the order confirmation and in the e-mail, and an order paid by bank transfer is created as On hold until you mark it paid. cheque behaves the same way. cod needs a shipping method that supports it: a checkout with cod before any shipping zone existed returned woocommerce_rest_checkout_payment_method_disabled. For card payments, the choices are the WooPayments plugin (the official one, with an in-context onboarding since 9.9.0 that needs a WooPayments account), other provider plugins from the WordPress.org directory or the Woo Marketplace, and a hosted payment page. Their onboarding was not run for this page. Prefer a provider whose card fields are hosted by the provider (hosted fields or a redirect): then card numbers never reach your server, which keeps the PCI DSS burden at the lightest self-assessment. A gateway must also support the Checkout block; the Payment Gateways page explains how, and why an old gateway that only registers a classic payment method does not appear in the block checkout.

The setup is tested with a real order. The Store API request below uses the same endpoints as the block checkout, with a guest cart, the coupon and bank transfer; the shipping and totals are computed by WooCommerce, not by the script:

B="http://localhost:8095/?rest_route="
H=$(curl -s -D - -o /dev/null "${B}/wc/store/v1/cart")
TOKEN=$(echo "$H" | grep -i '^cart-token' | awk '{print $2}' | tr -d '\r')
NONCE=$(echo "$H" | grep -i '^nonce:' | awk '{print $2}' | tr -d '\r')
curl -s -X POST -H "Cart-Token: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"id":11,"quantity":2,"variation":[{"attribute":"Origin","value":"Colombia Huila"},{"attribute":"Grind","value":"Espresso"},{"attribute":"Size","value":"1 kg"}]}' \
  "${B}/wc/store/v1/cart/add-item" > /dev/null
curl -s -X POST -H "Cart-Token: $TOKEN" -H 'Content-Type: application/json' -d '{"id":10,"quantity":1}' \
  "${B}/wc/store/v1/cart/add-item" > /dev/null
curl -s -X POST -H "Cart-Token: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"billing_address":{"email":"ada@example.test","country":"ES","first_name":"Ada","last_name":"Lovelace","address_1":"Calle Ejemplo 1","city":"Madrid","state":"M","postcode":"28001","phone":"600000000"}}' \
  "${B}/wc/store/v1/cart/update-customer" > /dev/null
curl -s -X POST -H "Cart-Token: $TOKEN" -H 'Content-Type: application/json' -d '{"code":"COFFEECLUB"}' \
  "${B}/wc/store/v1/cart/apply-coupon" > /dev/null

The order is then placed with a POST to /wc/store/v1/checkout carrying the Cart-Token and Nonce headers and a body with billing_address, shipping_address and payment_method set to bacs. The order that came out:

wp --user=shopadmin wc shop_order list --fields=id,status,total,discount_total,total_tax,shipping_total,payment_method --format=table
id	status	total	discount_total	total_tax	shipping_total	payment_method
36	on-hold	77.82	4.94	13.51	0.00	bacs
35	on-hold	83.80	0.00	14.55	0.00	bacs

Order 35 is the same cart without the coupon (83.80, shipping 0.00 because the cart qualifies for free shipping); order 36 has the Coffee Club coupon (see the next section) and a total of 77.82. Both are on-hold because bank transfer waits for the payment.

The Coffee Club discount

Pocket Coffee wants "10 % off beans for Coffee Club members, applied automatically". WooCommerce core has no customer groups and no automatic role-based discounts. What it offers:

  • A coupon (Marketing > Coupons, or wp wc shop_coupon create) with a discount type (percent, fixed_cart, fixed_product), an amount, product and category restrictions, an Allowed emails list (email_restrictions), usage limits and an expiry. It is applied by code, not automatically.

  • A user role to tag members (wp role create coffee_club "Coffee Club" and wp user add-role <user> coffee_club), which gives you a list of members and something for extensions and code to test, but a role alone changes no price.

  • An extension or custom code for an automatic discount keyed to the role, such as a membership extension (see Subscriptions, Memberships and Bookings) or a filter on the cart.

wp role create coffee_club "Coffee Club"
wp user add-role 2 coffee_club
wp --user=shopadmin wc shop_coupon create --code=COFFEECLUB --discount_type=percent --amount=10 \
  --product_categories='[16]' --email_restrictions='["ada@example.test"]' --porcelain
wp --user=shopadmin wc shop_coupon get 30 --fields=id,code,discount_type,amount,product_categories,email_restrictions --format=json
Success: Role with key 'coffee_club' created.
Success: Added 'coffee_club' role for ada (2).
30
{"id":30,"code":"coffeeclub","amount":"10.00","discount_type":"percent","usage_limit_per_user":null,"product_categories":[16],"email_restrictions":["ada@example.test"]}

WooCommerce stores coupon codes in lower case (coffeeclub), whatever case you typed. Two behaviours of the e-mail restriction came out of the tests: applying the coupon to a guest cart whose customer has no e-mail returned Please enter a valid email at checkout to use coupon code "coffeeclub", and applying it after update-customer had set the billing e-mail to ada@example.test worked. The restriction is therefore checked against the e-mail the customer has entered, not against a login. A coupon restricted to the Beans category did nothing to a cart with only the starter kit (coupon is not applicable to selected products).

A Pocket Coffee cart: two kilos of Colombia Huila espresso beans at 29.90 and one starter kit at 24.00 with VAT included, the Coffee Club coupon takes 10 percent off the beans, and the total is 83.75 with the flat rate preselected or 77.82 with free shipping first

The figure shows the verified cart: 59.80 of beans plus the 24.00 kit is 83.80; the coupon takes 5.98 from the beans only (4.94 plus 1.04 VAT); that leaves 77.82. With the flat rate preselected the customer would have paid 77.82 plus 5.93 = 83.75 although free shipping was available, which is why the order of the methods matters.

E-mails

WooCommerce sends transactional e-mails from classes registered in WC()->mailer(). In 11.2.1 the registered classes are:

E-mail id Class Goes to

new_order

WC_Email_New_Order

admin

cancelled_order

WC_Email_Cancelled_Order

admin

failed_order

WC_Email_Failed_Order

admin

admin_payment_gateway_enabled

WC_Email_Admin_Payment_Gateway_Enabled

admin

customer_processing_order

WC_Email_Customer_Processing_Order

customer

customer_completed_order

WC_Email_Customer_Completed_Order

customer

customer_on_hold_order

WC_Email_Customer_On_Hold_Order

customer

customer_refunded_order

WC_Email_Customer_Refunded_Order

customer

customer_cancelled_order

WC_Email_Customer_Cancelled_Order

customer

customer_failed_order

WC_Email_Customer_Failed_Order

customer

customer_invoice

WC_Email_Customer_Invoice

customer

customer_note

WC_Email_Customer_Note

customer

customer_reset_password

WC_Email_Customer_Reset_Password

customer

customer_new_account

WC_Email_Customer_New_Account

customer

customer_verify_email

a class in Automattic\WooCommerce\Internal\CustomerEmailVerification\Emails

customer

customer_pos_completed_order, customer_pos_refunded_order

point-of-sale receipts

customer

Settings > Emails sets the sender (woocommerce_email_from_name, woocommerce_email_from_address), an optional reply-to (woocommerce_email_reply_to_enabled, woocommerce_email_reply_to_name, woocommerce_email_reply_to_address), the header image and its width and alignment (woocommerce_email_header_image, woocommerce_email_header_image_width, woocommerce_email_header_alignment), the font and footer text (woocommerce_email_font_family, woocommerce_email_footer_text) and the colours (woocommerce_email_base_color, woocommerce_email_background_color, woocommerce_email_body_background_color, woocommerce_email_text_color, woocommerce_email_footer_text_color, with woocommerce_email_auto_sync_with_theme to take them from the theme). Each e-mail then has its own screen under the same tab with an enable switch, the subject, the heading, additional content and the e-mail type (plain or HTML). The new e-mail templates and the block e-mail editor sit behind the feature options woocommerce_feature_email_improvements_enabled and woocommerce_feature_block_email_editor_enabled, both no in 11.2.1.

wp option update woocommerce_email_from_name "Pocket Coffee"
wp option update woocommerce_email_from_address "shop@example.test"
wp eval 'foreach ( WC()->mailer()->get_emails() as $e ) { printf( "%-36s %s\n", $e->id, $e->is_customer_email() ? "customer" : "admin" ); }'
Success: Updated 'woocommerce_email_from_name' option.
Success: Updated 'woocommerce_email_from_address' option.
new_order                            admin
cancelled_order                      admin
customer_cancelled_order             customer
failed_order                         admin
customer_failed_order                customer
customer_on_hold_order               customer
customer_processing_order            customer
customer_completed_order             customer
customer_refunded_order              customer
customer_invoice                     customer
customer_note                        customer
customer_reset_password              customer
customer_new_account                 customer
admin_payment_gateway_enabled        admin
customer_pos_completed_order         customer
customer_pos_refunded_order          customer
customer_verify_email                customer

The sender address must belong to a domain whose mail you control (SPF, DKIM and DMARC records for that domain), or providers flag the order e-mails as spam; WordPress’s default wp_mail() uses PHP mail() and often fails on containers and cheap hosts. Delivery is a hosting decision (an SMTP or transactional-mail plugin); in the test stack there was no mail server, so no e-mail left the machine (sendmail: can't connect to remote host), and the e-mail bodies were read from the templates, not from an inbox. A mail catcher (a local SMTP server with a web inbox) is the way to read them in development. The Emails page covers templates and WC_Email.

Choosing a theme and extensions

A theme decides the look and the templates; extensions add behaviour. Both are choices you live with, so the checks below matter more than the demo.

Theme. A new WordPress 7.1.3 site comes with the default block themes (Twenty Twenty-Five 1.5, Twenty Twenty-Four 1.6, Twenty Twenty-Three 1.7) and the first one was active in the test site. WooCommerce works with block themes through theme.json and the Woo block templates, with classic themes through PHP templates, and the Cart and Checkout blocks work in both. Storefront 4.6.2 (2025-12-09) is the long-standing classic theme from the WooCommerce team; Purple (version 1.0.3, updated 2026-10-09) is the block theme and needs WordPress 7.1 or newer. Prefer a block theme for a new store, keep customisations in a child theme or in the Site Editor, and expect a classic theme’s yourtheme/woocommerce/ template overrides to break when WooCommerce bumps a template version (WooCommerce > Status lists outdated overrides). The theme section of the Themes: Block and Classic page covers this in detail; the books' advice to design around a Customizer and widget areas describes the classic era.

Extensions. WooCommerce core is deliberately small, so most stores add a few extensions. Before installing one, check:

Check What to look for

Maintenance

Last update date, Tested up to (WordPress) and WC tested up to in the plugin header, and the number of active installs

Compatibility declarations

HPOS and Cart and Checkout block compatibility (wp wc hpos compatibility-info lists what each active plugin declared)

Source

The WordPress.org directory or the Woo Marketplace; never "nulled" copies of paid plugins, which are a common malware route

Marketplace vetting

Woo Marketplace extensions pass the Quality Insights Toolkit tests (activation, API, end-to-end, security, PHP compatibility); that is a floor, not a guarantee

Overlap

Does it duplicate a core feature, the theme, or another extension? Every extra plugin is code that runs on each request and an update to track

Exit cost

Where its data lives (custom tables, post meta) and what happens to it on uninstall

wp wc hpos compatibility-info is the quick HPOS check; a plugin that has not declared compatibility makes WooCommerce warn before HPOS is enabled. Keep the number of extensions small, remove the ones you stop using instead of deactivating them, and test updates on a copy of the store first. Advice that named specific plugins in the books (SEO, security, backup, caching) is dated; evaluate current options with the checks above.

The complete Pocket Coffee script

The script below assembles the commands of this page into one file that creates the settings, tax rate, shipping zones, payments, catalogue and coupon. It was run on a clean WordPress 7.1.3 and WooCommerce 11.2.1 site, finished in about 45 seconds and produced the zones, the 18 variations (with the IDs, SKUs and prices of the table above), the tax rate and the coupon listed in this page. It uses an administrator called shopadmin and WP-CLI as $WP.

#!/usr/bin/env bash
# pocket-coffee-setup.sh: settings, tax, shipping, catalogue and coupon of Pocket Coffee.
# WP-CLI is called through $WP (default: wp) inside the WordPress directory.
set -euo pipefail
WP=${WP:-wp}
ADMIN=${ADMIN:-shopadmin}
wcli() { "$WP" --user="$ADMIN" wc "$@"; }
opt()  { "$WP" option update "$@" > /dev/null; }

# 1. Store details, selling countries, currency, units
opt woocommerce_store_address "Calle Ejemplo 1"
opt woocommerce_store_city Madrid
opt woocommerce_store_postcode 28001
opt woocommerce_default_country ES:M
opt woocommerce_currency EUR
opt woocommerce_weight_unit kg
opt woocommerce_dimension_unit cm
opt woocommerce_allowed_countries specific
opt woocommerce_specific_allowed_countries '["ES","DE","FR","IT","PT","NL","BE","AT","IE"]' --format=json

# 2. Tax: prices include VAT, shown with VAT in the shop and the cart, one rate for Spain
opt woocommerce_calc_taxes yes
opt woocommerce_prices_include_tax yes
opt woocommerce_tax_display_shop incl
opt woocommerce_tax_display_cart incl
wcli tax create --country=ES --rate=21.0000 --name=IVA --priority=1 --shipping=true --class=standard --porcelain

# 3. Shipping: free shipping is created first so that it is the preselected method
ES=$(wcli shipping_zone create --name="Spain" --porcelain)
EU=$(wcli shipping_zone create --name="European Union (excluding Spain)" --porcelain)
"$WP" eval '
$es = new WC_Shipping_Zone( '"$ES"' ); $es->add_location( "ES", "country" ); $es->set_zone_order( 0 ); $es->save();
$eu = new WC_Shipping_Zone( '"$EU"' ); $eu->set_zone_order( 1 );
foreach ( array( "DE", "FR", "IT", "PT", "NL", "BE", "AT", "IE" ) as $c ) { $eu->add_location( $c, "country" ); }
$eu->save();'
wcli shipping_zone_method create "$ES" --method_id=free_shipping --settings='{"requires":"min_amount","min_amount":"40"}' --porcelain
wcli shipping_zone_method create "$ES" --method_id=flat_rate --settings='{"cost":"4.90","tax_status":"taxable"}' --porcelain
wcli shipping_zone_method create "$EU" --method_id=flat_rate --settings='{"cost":"9.90","tax_status":"taxable"}' --porcelain

# 4. Payments
wcli payment_gateway update bacs --enabled=true --porcelain

# 5. Categories and products
BEANS=$(wcli product_cat create --name=Beans --porcelain)
EQUIP=$(wcli product_cat create --name=Equipment --porcelain)
wcli product create --name="Starter kit" --type=simple --regular_price=24.00 --sku=PC-KIT --weight=1.2 \
  --manage_stock=true --stock_quantity=30 --categories="[{\"id\":$EQUIP}]" --porcelain

PID=$(wcli product create --name="Single-origin beans" --type=variable --categories="[{\"id\":$BEANS}]" \
  --attributes='[{"name":"Origin","position":0,"visible":true,"variation":true,"options":["Ethiopia Yirgacheffe","Colombia Huila","Brazil Cerrado"]},{"name":"Grind","position":1,"visible":true,"variation":true,"options":["Whole bean","Espresso","Filter"]},{"name":"Size","position":2,"visible":true,"variation":true,"options":["250 g","1 kg"]}]' --porcelain)
for o in "Ethiopia Yirgacheffe:ETH:1" "Colombia Huila:COL:0" "Brazil Cerrado:BRA:0"; do
  IFS=: read -r oname ocode extra <<< "$o"
  for g in "Whole bean:WHL" "Espresso:ESP" "Filter:FIL"; do
    IFS=: read -r gname gcode <<< "$g"
    for s in "250 g:250:8.90:0.25:1" "1 kg:1KG:29.90:1:4"; do
      IFS=: read -r sname scode price weight mult <<< "$s"
      total=$(awk -v p="$price" -v e="$extra" -v m="$mult" 'BEGIN { printf "%.2f", p + e * m }')
      wcli product_variation create "$PID" --regular_price="$total" --sku="PC-$ocode-$gcode-$scode" \
        --weight="$weight" --manage_stock=true --stock_quantity=25 \
        --attributes="[{\"name\":\"Origin\",\"option\":\"$oname\"},{\"name\":\"Grind\",\"option\":\"$gname\"},{\"name\":\"Size\",\"option\":\"$sname\"}]" \
        --porcelain > /dev/null
    done
  done
done

# 6. Coffee Club: a role to tag members and a coupon restricted to Beans and to one member e-mail
"$WP" role create coffee_club "Coffee Club"
wcli shop_coupon create --code=COFFEECLUB --discount_type=percent --amount=10 \
  --product_categories="[$BEANS]" --email_restrictions='["ada@example.test"]' --porcelain

The script is not idempotent: run twice, it creates a second tax rate, second zones and a second catalogue. For repeatable environments, create the site from scratch (a fresh container or wp db reset --yes) each time, as the local-environment page describes. The coupon code is taken by the first run, and a second shop_coupon create for the same code fails with a duplicate-code error.

Security, backups and maintenance

Most store compromises come from outdated or untrusted plugins and weak administrator accounts, not from WooCommerce core; Patchstack’s 2026 report counts 91 % of the new WordPress vulnerabilities of 2025 in plugins and 9 % in themes (a third-party source). The routine below is what a small store can sustain. The commands were run on the test site.

When Task How

Every week

Apply WordPress, WooCommerce, theme and plugin updates, after testing on a copy; remove unused plugins

wp plugin list --fields=name,status,version,update,auto_update, wp core check-update

Every week

Check scheduled actions and failed jobs (WooCommerce > Status > Scheduled Actions) and the log

The Action Scheduler page

Every day

Back up the database; back up uploads and code after every change

wp db export, a file-system copy, an off-site copy

Every month

Restore a backup into a throw-away site and place a test order

The restore is the test; an untested backup is a hope

After each update

Verify the files of core and plugins against the published checksums

wp plugin verify-checksums --all, wp core verify-checksums

Every quarter

Review administrator and shop-manager accounts, API keys (Advanced > REST API), webhooks and application passwords

wp user list --role=administrator

Always

HTTPS on every page, strong passwords and two-factor authentication for every administrator

WordPress 7.1.3 core contains no two-factor code; use a maintained plugin

wp plugin verify-checksums --all
wp core check-update
wp plugin auto-updates enable woocommerce
wp plugin list --fields=name,status,version,update,auto_update
wp db export /backups/pocket-coffee-2026-10-27.sql
wp config set DISALLOW_FILE_EDIT true --raw
wp config set WP_ENVIRONMENT_TYPE production
wp cron event run --due-now
Success: Verified 3 of 3 plugins.
Success: WordPress is at the latest version.
Success: Enabled 1 of 1 plugin auto-updates.
name	status	version	update	auto_update
akismet	inactive	5.7.2	none	off
hello	inactive	1.7.2	none	off
woocommerce	active	11.2.1	none	on
Success: Exported to '/backups/pocket-coffee-2026-10-27.sql'.
Success: Added the constant 'DISALLOW_FILE_EDIT' to the 'wp-config.php' file with the raw value 'true'.
Success: Added the constant 'WP_ENVIRONMENT_TYPE' to the 'wp-config.php' file with the value 'production'.
Executed the cron event 'wc_admin_process_orders_milestone' in 0.001s.
Success: Executed a total of 8 cron events.

Automatic updates for WooCommerce are a trade-off: security releases arrive fast (the July 2021 SQL injection was patched in a forced update within a day), but an automatic update can break a customised checkout, so many stores enable them for core and security releases and test major updates by hand. DISALLOW_FILE_EDIT removes the theme and plugin editors from wp-admin, which stops an attacker with an administrator session from writing PHP there. WordPress’s cron runs on page visits; a store with low traffic or a caching layer should call wp-cron.php or wp cron event run --due-now from the system cron and set DISABLE_WP_CRON, because Action Scheduler (orders, e-mails, subscriptions) rides on it.

WooCommerce > Status > Tools offers maintenance actions that are safe to run when a symptom matches. The ids in 11.2.1 (shown by wp wc tool list --user=<administrator>) include clear_transients, clear_expired_transients, delete_orphaned_variations, clear_expired_download_permissions, regenerate_product_lookup_tables, repair_coupons_lookup_table, recount_terms, reset_roles, clear_sessions, clear_template_cache, install_pages, db_update_routine, verify_db_tables, regenerate_product_attributes_lookup_table, clear_woocommerce_analytics_cache, hpos_legacy_cleanup and, destructive, delete_taxes and delete_custom_orders_table. A tool runs with wp wc tool run <id> --user=<administrator>; read the description before you run the destructive ones. The Status > System status report is what support asks for first: it lists versions, active plugins, database tables, outdated template overrides and the HPOS state.

Payment security. A store that never sees card numbers (hosted fields or redirect) completes the lightest PCI DSS self-assessment; a store that handles card data itself takes on the full burden. Keep API keys and gateway secrets out of the code and rotate them after any suspected compromise. The incident history, the 21-release security backport window and hardening beyond this routine are on the Performance and Security page, and the developer-side checklist is in the official security best practices (PHPCS and Semgrep clean code, WordPress hardening, 2FA, data protection).

Launching

WooCommerce can hide the store until you are ready. Settings > Site visibility (the option names are woocommerce_coming_soon and woocommerce_store_pages_only) has two modes: coming soon for the whole site, or for the store pages only, so that a blog or marketing site stays public while the shop is private. In the 11.2.1 source, woocommerce_coming_soon set to yes with woocommerce_store_pages_only not yes puts the whole site in coming-soon mode, and both set to yes limits it to the store pages; administrators and shop managers (manage_woocommerce) always see the real site. A scripted install leaves woocommerce_coming_soon unset, so the store is live from the moment it is installed; check the setting before you point a domain at the site.

On a test site, with the whole site in coming-soon mode, an anonymous request to the home page returned HTTP 200 with the header Cache-Control: max-age=60, and the page head contained <meta name='woo-coming-soon-page' content='yes'> and a robots meta of max-image-preview:large. The coming-soon page is therefore a normal 200 response, not a 503, and it carried no noindex directive; search engines can index it if they find it. WordPress’s own Discourage search engines from indexing this site switch (Settings > Reading, option blog_public) is a separate control. A page cache in front of the site keeps serving the page for as long as its own lifetime, so purge it when you switch the mode.

Launch checklist in four groups: configuration, sales and tax, content and search, and security and operations, each with the commands or screens that verify it
Check What to verify Command or screen

Settings

Country, currency, units, selling countries, store address

wp option get woocommerce_default_country, woocommerce_currency

Permalinks

Pretty permalinks on (Post name), so that /wp-json/ and product URLs work

wp rewrite structure '/%postname%/' then wp rewrite flush (both reported success)

Pages

Shop, Cart, Checkout, My account exist and are published; the terms, privacy and refund pages are written and linked

wp post list --post_type=page, woocommerce_terms_page_id

Tax

The rate for each country you sell to; prices and display consistent; a test order for each customer type

wp wc tax list --user=<administrator>

Shipping

A zone for every selling country; the method preselected is the one you want; the free-shipping threshold behaves with and without tax display

wp wc shipping_zone list --user=<administrator>, a Store API cart

Payments

One live gateway, tested with a real small payment and a refund; bank transfer details correct

Payments tab, Orders

E-mails

Sender, branding, one test of each customer e-mail, delivery from the real domain

Emails tab, a mail catcher in staging

Content

Product titles, descriptions, images with alt text, categories; no demo products left

Products

Search

A single canonical URL per product (WordPress handles rel=canonical), a sitemap (core provides /wp-sitemap.xml), no noindex left from staging

view-source of the home page and a product

Security

Updates applied, administrators with 2FA, backup restored once, coming soon switched off at the right moment

wp option get woocommerce_coming_soon

Monitoring

Uptime check, log review, order e-mail alert, a plan for rollbacks

Host tools, Status > Logs

Search and marketing are launch topics in every book: use readable permalinks (the default product base is /product/ and the product category base /product-category/, both editable in Settings > Permalinks), give each product a unique title and description, keep one URL per product (variations use query parameters on the same URL), and let WooCommerce emit its product structured data. Analytics and advertising pixels must respect consent rules in the countries you sell to, so add them through a consent-aware plugin and mention them in the privacy policy. The order of the final steps is: finish the content, run the test orders, switch the site visibility to live, purge caches, place a real order yourself, and watch the first days of orders and e-mails.

Legacy and book notes

The books on this subject describe an older WooCommerce and are background only. Sims (2018, WooCommerce 3.x and WordPress 4.9) walks the Setup Wizard, the Customizer and widget areas of Storefront, shortcode-based Cart and Checkout pages and PHP 5-era hosting; today the Core Profiler and the task list replace the wizard, block themes replace the Customizer for new stores, the Cart and Checkout blocks are the default and PHP 7.4 is the floor (PHP 8.3 is recommended). Rauland (2015, WooCommerce 2.2 and 2.3) edits the checkout with woocommerce_checkout_fields and enforces HTTPS on the checkout only; those filters do not affect the Checkout block and HTTPS now belongs on every page. Ravensbergen (2015, WooCommerce 2.4 and 2.5) builds a Storefront child theme with storefront_* hooks and shortcodes and refers to WooThemes and Simplify Commerce, which no longer exist as described. The store-planning, launch, security, backup and maintenance advice survives, which is why it is rewritten above for the current screens, options and commands.

References