本文へ移動
cccskills
無料GitHub で公開

wc-customer-and-sessions

Use WooCommerce's customer and session APIs correctly — WC ships its own session handler (WC_Session_Handler, single wp_woocommerce_sessions table for both guests and logged-in users) and its own customer object (WC_Customer via WC()->customer), separate from $_SESSION / native PHP sessions / wp_get_current_user. AI consistently reaches for $_SESSION (breaks with page caching, setcookie (no signing, no namespacing), or user_meta for guests (impossible). The correct path is WC()->session->set/get for ephemeral per-visitor data, WC()->customer->get_billing_* for the active customer context including guests, and new WC_Customer($user_id) for one-off loads. Important — WC()->session is auto-initialized only on frontend requests (incl. admin-ajax); REST, cron, and WP-CLI must call wc_load_cart() explicitly. Use when storing per-visitor state, reading the current visitor's billing data, or debugging session loss across page caches / REST / cron. Triggers on WC_Session, WC_Session_Handler,...

インストール方法を見る

含まれるファイル(1)

  • SKILL.md21.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

WooCommerce: customer and session APIs

For plugins that store per-visitor data (cart-adjacent state, in-progress form, last-viewed product, A/B variant), or read / mutate the current customer's address / email pre-checkout. WC has its own session and customer abstractions that look optional but aren't — using $_SESSION, setcookie, or WP-user-meta directly for these breaks across page caching, REST, AJAX, cron, and guest contexts.

Misconception this skill corrects

"I'll use $_SESSION['my_data'] to remember the visitor's choice across pages."

Three different ways this fails:

  1. Page caching plugins (WP Rocket, W3 Total Cache, hosted edge cache) bypass PHP entirely on cached pages. $_SESSION is never written / read for those visitors.
  2. WP REST API doesn't start a session unless something explicitly does. Your AJAX call has no $_SESSION even when the previous page wrote to it.
  3. Guests have no $user_id, so user meta isn't an option either.

The right path: WC ships its own session handler with cookie-based identity (separate from WP login cookies), backed by the single wp_woocommerce_sessions custom table — for both guests AND logged-in users. The cookie keeps the visitor identifier across pages and the row persists per-customer in the table.

Important — auto-initialization is frontend-only. WC::init() (registered on hook init priority 0, see class-woocommerce.php:323 and class-woocommerce.php:944-946) calls wc_load_cart() only when is_request('frontend') is true. That predicate (class-woocommerce.php:645) excludes REST, cron, and WP-CLI; it includes admin-ajax (DOING_AJAX) and regular page loads. Code running under /wp-json/, wp_cron, or wp CLI must call wc_load_cart() (or WC()->initialize_session()) explicitly before reading WC()->session. Store API routes do this themselves — see src/StoreApi/Utilities/CartController.php:30-33.

When to use this skill

Trigger when ANY of the following is true:

  • Storing cart-adjacent ephemeral data (selected gift wrap option, A/B test variant, "viewed but not purchased" tracking, multi-step checkout state).
  • Reading the current customer's billing / shipping address before they place an order.
  • Reviewing code that uses $_SESSION, session_start(), setcookie, or direct wp_woocommerce_sessions table writes in WC context.
  • Debugging "session is lost on the next page" / "cart empties when page cache warms" / "AJAX call doesn't see session data".
  • The diff or file contains: WC_Session, WC_Session_Handler, WC_Customer, WC()->session, WC()->customer, $_SESSION next to WC code.

Mental model — three layers

LayerClass / APIWhat it storesLifetime
Visitor sessionWC()->session (WC_Session_Handler)Arbitrary key/value data per visitor (cart contents, applied coupons, custom plugin state). Cookie-based identity.Default 2 days for guests, 7 days for logged-in users (filterable via wc_session_expiration).
Active customer contextWC()->customer (WC_Customer)Billing / shipping address, email, display name FOR THE CURRENT REQUEST. Logged-in users persist across requests via user meta; guests persist via session.Until logout / session expiry.
Customer object (one-off)new WC_Customer( $user_id )A specific user's WC profile data.One-off load; you save it explicitly.

The three are related but distinct. Most plugin code that "needs the customer's email" wants WC()->customer->get_billing_email(). Most plugin code that needs to remember a visitor choice across pages wants WC()->session->set('my_key', $value). The third (loading another user's profile) is rarer.

WC()->session — visitor session API

The session handler (includes/class-wc-session-handler.php) wraps the abstract WC_Session (includes/abstracts/abstract-wc-session.php). Identity comes from a cookie named wp_woocommerce_session_<COOKIEHASH> set on the first page that touches the session.

Reading and writing

// Set a value
WC()->session->set( 'myplugin_step', 'step_2' );
WC()->session->set( 'myplugin_choices', array( 'option_a' => true, 'option_b' => false ) );

// Read with a default
$step    = WC()->session->get( 'myplugin_step', 'step_1' );
$choices = (array) WC()->session->get( 'myplugin_choices', array() );

// Remove
WC()->session->__unset( 'myplugin_step' );
// OR equivalently
unset( WC()->session->myplugin_step );

Values can be any serializable PHP type — arrays, objects (be careful with PHP class compatibility), scalars. The handler serializes them when writing to storage.

Storage details (verified)

  • Session payload (both guests and logged-in users): serialized into the wp_woocommerce_sessions custom table — a single INSERT ... ON DUPLICATE KEY UPDATE keyed by session_key (= customer ID for logged-in users, generated guest ID otherwise). Verified at class-wc-session-handler.php:561-575 (save_data()).
  • Persistent cart (logged-in users only): a separate user-meta entry keyed _woocommerce_persistent_cart_<blog_id> storing only the cart contents, written by WC_Cart_Session::persistent_cart_update() (class-wc-cart-session.php:458-471). This is NOT the session payload — it lives in user meta and persists across logins so a returning user finds their cart re-hydrated. Filter woocommerce_persistent_cart_enabled to disable.
  • Default expiry: guest = 2 * DAY_IN_SECONDS, logged-in = WEEK_IN_SECONDS. Verified at class-wc-session-handler.php:401-403: $default_expiration_seconds = is_user_logged_in() ? WEEK_IN_SECONDS : 2 * DAY_IN_SECONDS;. Filter wc_session_expiration to override.
  • Cache layer: an object-cache wp_cache_set() writes the session into WC_SESSION_CACHE_GROUP on save — so persistent object-cache backends (Redis, Memcached) shortcut DB reads.
  • Cleanup: WC schedules a recurring action (woocommerce_cleanup_sessions, see Action Scheduler) to purge expired rows from wp_woocommerce_sessions.

When the session is initialized

WC::init() runs on hook init priority 0 (class-woocommerce.php:323) and calls wc_load_cart() only when is_request('frontend') is true (class-woocommerce.php:944-946). wc_load_cart() then calls WC()->initialize_session() and WC()->initialize_cart() (wc-core-functions.php:2526-2527).

Concretely:

Request typeWC()->session auto-initialized?Why
Regular frontend pageYesis_request('frontend') is true
admin-ajax.php (DOING_AJAX)Yesis_request('frontend') returns true when DOING_AJAX is defined
/wp-json/... (REST)Nois_request('frontend') returns false for REST (is_rest_api_request())
Store API (/wp-json/wc/store/...)Yes — but only because the route calls wc_load_cart() itself (CartController.php:30-33)
wp_cron (DOING_CRON)Nois_request('frontend') excludes cron
wp CLINois_request('frontend') excludes CLI
wp-admin (no AJAX)Nois_request('frontend') excludes admin

If your code runs in a context where the session is not auto-initialized AND you need session data, call wc_load_cart() (or WC()->initialize_session() if you don't need the cart):

if ( ! did_action( 'wc_loaded' ) ) return; // WC must be loaded
if ( null === WC()->session ) {
    wc_load_cart(); // or WC()->initialize_session(); for session-only
}
$value = WC()->session->get( 'myplugin_key' );

Even after init, WC()->session->has_session() may be false (no cookie set yet because the visitor hasn't triggered anything that would set one). To force the cookie + table row for an anonymous tracking flow:

add_action( 'wp', static function (): void {
    if ( ! WC()->session ) return;
    if ( WC()->session->has_session() ) return;
    WC()->session->set_customer_session_cookie( true );
} );

set_customer_session_cookie( true ) writes the cookie and creates the row in wp_woocommerce_sessions.

Common usage pattern

// On a custom AJAX endpoint or page hit:
$tracking = (array) WC()->session->get( 'myplugin_tracking', array() );
$tracking['last_viewed_product'] = $product_id;
$tracking['last_seen_at']        = time();
WC()->session->set( 'myplugin_tracking', $tracking );

// Later, perhaps in a checkout filter:
$tracking = (array) WC()->session->get( 'myplugin_tracking', array() );
if ( ! empty( $tracking['last_viewed_product'] ) ) {
    // attach to order, send to analytics, etc.
}

The session is request-scoped (in-memory) AND persisted (to storage on shutdown). Multiple set calls in the same request just update the in-memory copy; the actual DB write happens once on shutdown via save_data().

WC()->customer — active customer context

WC()->customer returns the WC_Customer for the current visitor (logged-in user or guest). The instance is hydrated from:

  • The logged-in user's WC_Customer data (user meta), if they're logged in.
  • The session-stored customer fields, if a guest with active session.
  • Defaults (store country, etc.), otherwise.

Reading address / email

$customer = WC()->customer;

$email          = $customer->get_billing_email();
$first_name     = $customer->get_billing_first_name();
$country        = $customer->get_billing_country();
$shipping_state = $customer->get_shipping_state();

// Convenience
$is_vat_exempt  = $customer->is_vat_exempt();

These work for guests too — WC promotes session-stored billing fields onto the customer object.

Writing address / email

WC()->customer->set_billing_email( 'visitor@example.com' );
WC()->customer->set_billing_country( 'HU' );
WC()->customer->save(); // for logged-in users; guests save via session

For logged-in users, save() writes to user meta. For guests, the values flow back into WC()->session automatically — calling save() on a guest customer is harmless but unnecessary.

One-off loading another user

$customer = new WC_Customer( $user_id );
$email    = $customer->get_billing_email();
// Modify and save:
$customer->set_billing_phone( '+36 1 234 5678' );
$customer->save();

This loads from user meta directly and saves back. Doesn't touch the session.

Critical rules

  • Don't use $_SESSION in WC plugin code. It's broken with page caching, REST, AJAX, and cron contexts.
  • Don't use setcookie directly for visitor data. No signing, no expiry handshake, no automatic cleanup. Use WC()->session instead.
  • Don't use update_user_meta for guest data. Guests have no $user_id.
  • WC()->session auto-init is frontend-only. REST, cron, and CLI must call wc_load_cart() (or WC()->initialize_session()) before reading the session. Don't assume WC()->session is non-null in those contexts.
  • Code running before hook init priority 0 cannot use WC()->session — that's where WC::init() runs and conditionally creates the handler. plugins_loaded is too early.
  • Force a session with set_customer_session_cookie( true ) if you need persistence for empty-cart visitors (e.g. tracking) — set alone does not write the cookie.
  • Namespace all session keys with your plugin slug. myplugin_* — without prefix you collide with WC core ('cart', 'shipping_methods', 'customer', etc.) or other plugins.
  • Storable types only. Don't put closures or non-serializable objects into the session — fatal on unserialize.
  • WC()->customer IS the right way to read the current visitor's billing email, even when they're not logged in. Don't reach for $_POST['billing_email'] or wp_get_current_user()->user_email — those are checkout-form-time and account-time values, which differ.
  • Session expiry differs by login state. Default 2 days for guests, 7 days for logged-in users. Filter wc_session_expiration to change. Don't store data that needs to last weeks in the session.
  • The session payload lives in wp_woocommerce_sessions for everyone (guest and logged-in alike). The _woocommerce_persistent_cart_<blog_id> user meta is a SEPARATE, cart-only persistence layer for logged-in users — don't confuse the two.
  • WC()->session->save_data() is automatic on shutdown. Don't call it manually unless you specifically need an early flush (rare).
  • Cron, CLI, and REST contexts have no auto-loaded session. Code running under WP_CLI, wp_cron, or /wp-json/ does not have WC()->session populated by default — you'll get null. Either bootstrap explicitly, or design the flow not to need a visitor session there.

Common mistakes

// WRONG — $_SESSION breaks with page caching, REST, AJAX
session_start();
$_SESSION['myplugin_choice'] = $choice;

// RIGHT
WC()->session->set( 'myplugin_choice', $choice );

// WRONG — direct cookie, no signing, leaks across user agents
setcookie( 'myplugin_data', wp_json_encode( $data ), time() + DAY_IN_SECONDS );

// RIGHT — session handles cookie + signing + cleanup
WC()->session->set( 'myplugin_data', $data );

// WRONG — user_meta for guest data
update_user_meta( get_current_user_id(), 'myplugin_data', $data );
// get_current_user_id() returns 0 for guests; update_user_meta(0, ...) silently fails.

// RIGHT
WC()->session->set( 'myplugin_data', $data );

// WRONG — accessing session before WC initializes it
add_action( 'plugins_loaded', function () {
    $value = WC()->session->get( 'foo' ); // WC()->session is null at plugins_loaded
} );

// RIGHT — wait for init or later
add_action( 'init', function () {
    if ( ! WC()->session ) return; // null in REST/cron/CLI even after init
    $value = WC()->session->get( 'foo' );
}, 10 );

// WRONG — assuming the session exists in a REST endpoint
register_rest_route( 'myplugin/v1', '/state', array(
    'callback' => function () {
        return WC()->session->get( 'foo' ); // null in REST — fatal
    },
) );

// RIGHT — bootstrap explicitly in REST
register_rest_route( 'myplugin/v1', '/state', array(
    'callback' => function () {
        if ( null === WC()->session ) {
            wc_load_cart(); // brings up session + cart
        }
        return WC()->session->get( 'foo' );
    },
) );

// WRONG — no namespace prefix on key
WC()->session->set( 'cart', $stuff );
// Collides with WC core's 'cart' key — corrupts cart contents.

// RIGHT
WC()->session->set( 'myplugin_cart_meta', $stuff );

// WRONG — stuffing closures or non-serializable objects
WC()->session->set( 'myplugin_callback', function () { /* ... */ } );
// Fatal on next request.

// WRONG — assuming wp_get_current_user matches WC()->customer
$email = wp_get_current_user()->user_email;
// For a guest mid-checkout, this is empty string. WC()->customer->get_billing_email()
// returns the billing email they typed into the checkout form, even pre-submit.

// RIGHT — for "the customer's email in the active checkout context"
$email = WC()->customer->get_billing_email();

// WRONG — reading session in cron / WP-CLI and expecting visitor data
add_action( 'wp_scheduled_event', function () {
    $value = WC()->session->get( 'foo' ); // always default; no visitor cookie in cron context
} );

Forcing a guest session — the analytics use case

If your plugin tracks visitor behavior across pages even when the cart is empty, you need to force the session to start:

add_action( 'wp', static function (): void {
    // Some condition — e.g. visitor on a tracked landing page
    if ( ! is_singular( 'product' ) ) return;
    if ( ! WC()->session ) return;
    if ( WC()->session->has_session() ) return;

    WC()->session->set_customer_session_cookie( true );
} );

Without this, WC()->session->get( 'myplugin_*' ) returns the default for empty-cart guests because there's no row to read from. Calling set on its own does NOT initialize the cookie — set_customer_session_cookie( true ) is the explicit init.

Reading from the customer object (post-checkout context)

After an order is placed, prefer reading from the order, not the session — sessions are visitor-scoped and the data may be the NEXT visitor's by the time your async handler runs:

// WRONG in an async webhook handler
$email = WC()->customer->get_billing_email();
// At webhook receive time, WC()->customer is the receiver's session customer,
// not the original buyer.

// RIGHT
$order = wc_get_order( $order_id );
$email = $order->get_billing_email();

WC()->customer is for the active page-render context. For order-tied work, read from $order directly.

Cross-references

  • Run wc-payment-gateway when payment processing reads / mutates the customer or session — payment gateways use both heavily.
  • Run wc-hpos-compatibility for any meta you also store on orders — orders go through HPOS, sessions don't.
  • Run wp-plugin-options-storage when deciding what data goes in the session vs in user meta vs in custom tables — session is for ephemeral, options/meta for durable.

What this skill does NOT cover

  • Custom session handlers (replacing WC_Session_Handler entirely via the woocommerce_session_handler filter). Niche; the default handler suits 99% of plugins.
  • WC's REST API customer endpoints (/wc/v3/customers, /wc/v4/customers). Sibling skill wc-rest-api-v4 covers REST.
  • Login / authentication flow, "Remember me" cookies, social login plugins. WP-level concerns; WC consumes the resolved current user.
  • Multi-currency per-session selection — usually handled by the multi-currency plugin's own filter chain, not directly via session.
  • PII compliance (GDPR data export / erasure) on session data — WC's privacy export tools cover the standard fields; custom session keys need their own export hook.
  • WP-CLI / cron contexts that need to "log in as user" — use wp_set_current_user() explicitly; that doesn't restore a WC session.

References

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.

日本語の概要は準備中です。原文の説明を表示しています。

nvdigitalsolutions/mcp-ai-wpoos72026年10月13日 更新

Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.

日本語の概要は準備中です。原文の説明を表示しています。

nvdigitalsolutions/mcp-ai-wpoos72026年10月13日 更新

Control a headless browser to navigate URLs, click elements, fill forms, extract content from JavaScript-rendered pages, take screenshots, and automate end-to-end web workflows.

日本語の概要は準備中です。原文の説明を表示しています。

nvdigitalsolutions/mcp-ai-wpoos72026年10月13日 更新

Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.

日本語の概要は準備中です。原文の説明を表示しています。

nvdigitalsolutions/mcp-ai-wpoos72026年10月13日 更新

Review code for quality, simplicity, and maintainability. Identifies unnecessary abstractions, duplicated logic, oversized functions, missing error handling, and naming issues — then fixes them automatically before presenting the result.

日本語の概要は準備中です。原文の説明を表示しています。

nvdigitalsolutions/mcp-ai-wpoos72026年10月13日 更新

WooCommerce store operations through NV oOS Pro — product research, catalog cross-referencing, inventory lookups, and Shopify or Flowhub comparisons where integrations are configured.

日本語の概要は準備中です。原文の説明を表示しています。

nvdigitalsolutions/mcp-ai-wpoos72026年10月13日 更新

nvdigitalsolutions のスキルをすべて見る

このスキルの問題を報告する