{"slug":"wcm-membership-hooks","title":"wcm-membership-hooks","summary":"Curated WooCommerce Memberships hook and extension-point map","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-16T14:52:21.402402Z","repo":{"url":"https://github.com/Lonsdale201/wp-agent-skills","stars":22,"forks":2,"license":"MIT","updatedAt":"2026-09-26T23:03:36Z"},"bodyHtml":"<hr>\n<h2>name: wcm-membership-hooks\ndescription: Curated WooCommerce Memberships hook and extension-point map\nfor plugins that build on user memberships, membership plans, status\ntransitions, purchase/free-signup grants, profile fields, REST API,\nwebhooks, members-area templates, CSV import/export, and Woo\nSubscriptions-linked memberships. Use when the user asks for a\nMemberships hook list, \"where should I hook\", membership created/saved,\nstatus changed, grant access from order, user membership API, profile\nfields, member directory, or code contains wc_memberships_,\nWC_Memberships_User_Membership, WC_Memberships_Membership_Plan,\nwc_user_membership, wc_membership_plan, wcm-, _subscription_id,\nwc_memberships_rules, or woocommerce-memberships.\nmetadata:\nwp-skills-author: \"Soczó Kristóf\"\nwp-skills-contact: \"mailto:lonsdale201@hotmail.com\"\nwp-skills-plugin: \"woocommerce-memberships\"\nwp-skills-plugin-version-tested: \"1.29.0\"\nwp-skills-php-min: \"7.4\"\nwp-skills-last-updated: \"2026-07-06\"</h2>\n<h1>WooCommerce Memberships: hook map</h1>\n<p>Use this when building or reviewing an integration that needs to react to WooCommerce Memberships events. This is a curated decision map, not an exhaustive dump of every hook call in the plugin.</p>\n<h2>Misconception this skill corrects</h2>\n<blockquote>\n<p>\"Memberships are just posts/meta, so query <code>wc_user_membership</code> posts and update <code>_member_plan_id</code> or <code>post_status</code> directly.\"</p>\n</blockquote>\n<p>User memberships are stored as custom posts, but the plugin has object wrappers, status hooks, access rules, order-grant metadata, REST/webhook behavior, profile-field storage, and Subscriptions integration. Prefer public functions and <code>WC_Memberships_User_Membership</code> methods unless the task explicitly requires low-level inspection.</p>\n<h2>When to use this skill</h2>\n<p>Trigger when ANY of the following is true:</p>\n<ul>\n<li>The user asks for WooCommerce Memberships actions/filters, lifecycle hooks, status hooks, purchase grant hooks, profile fields, REST API hooks, webhooks, member directory, members area, or Subscriptions-linked memberships.</li>\n<li>Code contains <code>wc_memberships_</code>, <code>WC_Memberships_User_Membership</code>, <code>WC_Memberships_Membership_Plan</code>, <code>wc_user_membership</code>, <code>wcm-active</code>, <code>_member_plan_id</code>, <code>_wc_memberships_access_granted</code>, or <code>woocommerce-memberships</code>.</li>\n<li>You need to decide whether to hook at membership creation, post save, status transition, access grant, profile field save, API response shaping, or frontend members-area rendering.</li>\n</ul>\n<h2>Workflow</h2>\n<ol>\n<li>Identify the layer first: user membership lifecycle, purchase/free-signup grant, access/restriction, discount, profile fields, REST/webhook, Abilities API, members area, CSV/admin, or Subscriptions integration.</li>\n<li>Prefer public functions and objects over raw <code>WP_Query</code>, <code>wp_update_post()</code>, or direct post meta writes.</li>\n<li>For status strings, know the boundary: WP post statuses use <code>wcm-</code> prefix, but object methods and lifecycle hooks commonly use unprefixed statuses such as <code>active</code>, <code>paused</code>, <code>cancelled</code>, <code>expired</code>, <code>free_trial</code>, <code>delayed</code>.</li>\n<li>Before implementing, inspect the installed plugin line with:</li>\n</ol>\n<pre><code>rg -n \"hook_name|function_name\" wp-content/plugins/woocommerce-memberships/src wp-content/plugins/woocommerce-memberships/templates\n</code></pre>\n<h2>Storage facts agents must not guess</h2>\n<p>Memberships registers two CPTs: <code>wc_membership_plan</code> for plans and <code>wc_user_membership</code> for user memberships. A user membership's plan ID is <code>post_parent</code>, and its member/user ID is <code>post_author</code>; the installed plugin does not use <code>_member_plan_id</code> as the plan relation.</p>\n<p>Core plan meta keys are <code>_access_method</code>, <code>_access_length</code>, <code>_access_start_date</code>, <code>_access_end_date</code>, <code>_product_ids</code>, <code>_members_area_sections</code>, and <code>_email_content</code>.</p>\n<p>Core user membership meta keys are <code>_start_date</code>, <code>_end_date</code>, <code>_cancelled_date</code>, <code>_paused_date</code>, <code>_paused_intervals</code>, <code>_product_id</code>, <code>_order_id</code>, <code>_previous_owners</code>, <code>_renewal_login_token</code>, and <code>_locked</code>.</p>\n<p>Membership rules are stored in the <code>wc_memberships_rules</code> option. Rule type values are <code>content_restriction</code>, <code>product_restriction</code>, and <code>purchasing_discount</code>.</p>\n<p>When WooCommerce Subscriptions links a membership, the <code>wc_user_membership</code> post gets <code>_subscription_id</code>; subscription-linked memberships may also use <code>_has_installment_plan</code> and <code>_free_trial_end_date</code>.</p>\n<h2>Active-detection canon</h2>\n<p>When gating code that registers something at FluentCRM / WP boot time (FluentCRM trigger / action, WC custom block, JFB action, etc.), use a symbol that exists at FILE LOAD — not one declared inside Memberships's own <code>plugins_loaded:10</code> callback. The symptom of getting this wrong is non-deterministic: works on some installs, your code disappears on others, depending on plugin activation order.</p>\n<pre><code>// RIGHT — declared at file scope in woocommerce-memberships.php:83.\n// Available the moment WP includes the plugin file, before any hook fires.\nclass_exists('WC_Memberships_Loader');\n\n// WRONG — load-order race.\n// wc_memberships() is declared inside class-wc-memberships.php which\n// Memberships's plugins_loaded:10 callback require_onces from init_plugin().\n// If your registration callback runs BEFORE Memberships's, function_exists\n// returns false and your code skips registration silently.\nfunction_exists('wc_memberships');\n</code></pre>\n<p>Same rule for <code>WC_Memberships</code> itself (the main class): it's loaded INSIDE <code>init_plugin()</code> so <code>class_exists('WC_Memberships')</code> is also racy. The loader class is the safe top-level checkpoint. At call time (REST endpoints, funnel handlers, AJAX) all classes/functions are loaded — the racy checks only matter at registration time.</p>\n<h2>Public API first</h2>\n<table>\n<thead>\n<tr>\n<th>Need</th>\n<th>API</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Get one membership</td>\n<td><code>wc_memberships_get_user_membership( $id, $plan = null )</code></td>\n<td>Returns <code>WC_Memberships_User_Membership</code> or false. Subscriptions integration may return the subscription-aware subclass.</td>\n</tr>\n<tr>\n<td>Get memberships for user/query</td>\n<td><code>wc_memberships_get_user_memberships( $user_id, $args )</code></td>\n<td>Prefer over hand-written membership <code>WP_Query</code> unless doing admin/reporting internals.</td>\n</tr>\n<tr>\n<td>Get active memberships</td>\n<td><code>wc_memberships_get_user_active_memberships( $user_id, $args )</code></td>\n<td>Uses Memberships status semantics.</td>\n</tr>\n<tr>\n<td>Check plan membership</td>\n<td><code>wc_memberships_is_user_member( $user_id, $plan )</code></td>\n<td>General membership check.</td>\n</tr>\n<tr>\n<td>Check active membership</td>\n<td><code>wc_memberships_is_user_active_member( $user, $plan )</code></td>\n<td>Use for \"currently active member\" business rules.</td>\n</tr>\n<tr>\n<td>Create membership</td>\n<td><code>wc_memberships_create_user_membership( $args, $action = 'create' )</code></td>\n<td>Lets Memberships run grant/renew/update logic. Throws <code>SV_WC_Plugin_Exception</code> on missing plan — wrap in try/catch. Sets <code>post_status = 'wcm-active'</code> by default; override with <code>$membership-&gt;update_status('paused', $note)</code> after.</td>\n</tr>\n<tr>\n<td>Get plan(s)</td>\n<td><code>wc_memberships_get_membership_plan()</code>, <code>wc_memberships_get_membership_plans()</code></td>\n<td>Avoid reading plan post meta directly.</td>\n</tr>\n<tr>\n<td>Status registry</td>\n<td><code>wc_memberships_get_user_membership_statuses( $with_labels = true, $prefixed = true )</code></td>\n<td>Canonical picker / dropdown source. Pass <code>(true, false)</code> for <code>{key, label}</code> pairs with UNPREFIXED keys (<code>active</code> not <code>wcm-active</code>) — that's the form <code>update_status()</code> accepts. Honours the <code>wc_memberships_user_membership_statuses</code> filter so 3rd-party additions appear automatically.</td>\n</tr>\n<tr>\n<td>Set / clear start date</td>\n<td><code>$membership-&gt;set_start_date( $date_string_or_empty )</code></td>\n<td>Empty string defaults to <code>current_time('timestamp', true)</code> — useful for \"member-since = now\" semantics.</td>\n</tr>\n<tr>\n<td>Set / clear end date</td>\n<td><code>$membership-&gt;set_end_date( $timestamp_or_string_or_empty )</code></td>\n<td>Empty string clears the end date — the canonical \"unlimited / never expires\" pattern. Numeric timestamp or <code>strtotime</code>-able string accepted.</td>\n</tr>\n<tr>\n<td>Check access</td>\n<td><code>wc_memberships_user_can( $user_id, 'view'|'purchase', $target, $when = '' )</code></td>\n<td>Use targets like <code>array( 'post' =&gt; $post_id )</code> or <code>array( 'product' =&gt; $product_id )</code>.</td>\n</tr>\n</tbody>\n</table>\n<h2>User membership lifecycle hooks</h2>\n<table>\n<thead>\n<tr>\n<th>Need</th>\n<th>Hook</th>\n<th>Type</th>\n<th>Args</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Alter new membership post data</td>\n<td><code>wc_memberships_new_membership_data</code></td>\n<td>filter</td>\n<td><code>array $data, array $args</code></td>\n<td>Set initial status/date/title before insert/update. Keep status prefixed if assigning <code>post_status</code>.</td>\n</tr>\n<tr>\n<td>Membership created by product/API/CLI create flow</td>\n<td><code>wc_memberships_user_membership_created</code></td>\n<td>action</td>\n<td><code>WC_Memberships_Membership_Plan $plan, array $args</code></td>\n<td>Attach external IDs or provision once. Args include <code>user_id</code>, <code>user_membership_id</code>, <code>is_update</code>.</td>\n</tr>\n<tr>\n<td>Membership saved through <code>save_post</code></td>\n<td><code>wc_memberships_user_membership_saved</code></td>\n<td>action</td>\n<td><code>WC_Memberships_Membership_Plan $plan, array $args</code></td>\n<td>Catch admin/manual/import saves. May fire in broader situations than creation.</td>\n</tr>\n<tr>\n<td>Status catalog</td>\n<td><code>wc_memberships_user_membership_statuses</code></td>\n<td>filter</td>\n<td><code>array $statuses</code></td>\n<td>Add labels/statuses. Keys normally use <code>wcm-</code> prefix.</td>\n</tr>\n<tr>\n<td>Statuses granting access</td>\n<td><code>wc_memberships_active_access_membership_statuses</code></td>\n<td>filter</td>\n<td><code>string[] $statuses</code></td>\n<td>Controls access semantics; do not assume only <code>active</code> grants access.</td>\n</tr>\n<tr>\n<td>Renewal-eligible statuses</td>\n<td><code>wc_memberships_valid_membership_statuses_for_renewal</code></td>\n<td>filter</td>\n<td><code>string[] $statuses</code></td>\n<td>Change when a membership can be renewed.</td>\n</tr>\n<tr>\n<td>Cancel-eligible statuses</td>\n<td><code>wc_memberships_valid_membership_statuses_for_cancel</code></td>\n<td>filter</td>\n<td><code>string[] $statuses</code></td>\n<td>Change when members can cancel.</td>\n</tr>\n<tr>\n<td>Any status transition</td>\n<td><code>wc_memberships_user_membership_status_changed</code></td>\n<td>action</td>\n<td><code>WC_Memberships_User_Membership $membership, string $old, string $new</code></td>\n<td>Best generic lifecycle sync hook. <code>$old</code> and <code>$new</code> are unprefixed.</td>\n</tr>\n<tr>\n<td>Membership activated</td>\n<td><code>wc_memberships_user_membership_activated</code></td>\n<td>action</td>\n<td><code>$membership, bool $was_paused, string $previous_status</code></td>\n<td>Resume/provision after activation.</td>\n</tr>\n<tr>\n<td>Membership paused</td>\n<td><code>wc_memberships_user_membership_paused</code></td>\n<td>action</td>\n<td><code>$membership</code></td>\n<td>Suspend access in external systems.</td>\n</tr>\n<tr>\n<td>Membership cancelled</td>\n<td><code>wc_memberships_user_membership_cancelled</code></td>\n<td>action</td>\n<td><code>$membership</code></td>\n<td>Cancel external entitlements.</td>\n</tr>\n<tr>\n<td>Gate automatic expiry</td>\n<td><code>wc_memberships_expire_user_membership</code></td>\n<td>filter</td>\n<td><code>bool $expire, $membership</code></td>\n<td>Block/allow scheduled expiration.</td>\n</tr>\n<tr>\n<td>Membership expired</td>\n<td><code>wc_memberships_user_membership_expired</code></td>\n<td>action</td>\n<td><code>int $user_membership_id</code></td>\n<td>Runs after expiry; load object if needed.</td>\n</tr>\n<tr>\n<td>Can member cancel</td>\n<td><code>wc_memberships_user_membership_can_be_cancelled</code></td>\n<td>filter</td>\n<td><code>bool $can, $membership</code></td>\n<td>UI/business rule for cancellation.</td>\n</tr>\n<tr>\n<td>Cancel URL</td>\n<td><code>wc_memberships_get_cancel_membership_url</code></td>\n<td>filter</td>\n<td><code>string $url, $membership</code></td>\n<td>Route cancellation to custom flow.</td>\n</tr>\n<tr>\n<td>Can renew</td>\n<td><code>wc_memberships_user_membership_can_be_renewed</code></td>\n<td>filter</td>\n<td><code>bool $can, $membership</code></td>\n<td>UI/business rule for renewal.</td>\n</tr>\n<tr>\n<td>Renewal product</td>\n<td><code>wc_memberships_user_membership_get_product_for_renewal</code></td>\n<td>filter</td>\n<td><code>$product, array $products, $membership</code></td>\n<td>Choose renewal product when several grant access.</td>\n</tr>\n<tr>\n<td>Renew URL</td>\n<td><code>wc_memberships_get_renew_membership_url</code></td>\n<td>filter</td>\n<td><code>string $url, $membership</code></td>\n<td>Route renewal to custom flow.</td>\n</tr>\n<tr>\n<td>Can transfer</td>\n<td><code>wc_memberships_user_membership_can_be_transferred</code></td>\n<td>filter</td>\n<td><code>bool $can, $membership, int $from_user_id, int $to_user_id</code></td>\n<td>Gate ownership transfer.</td>\n</tr>\n<tr>\n<td>Transfer error</td>\n<td><code>wc_memberships_user_membership_can_be_transferred_error</code></td>\n<td>filter</td>\n<td><code>WP_Error $error, $membership, $from_user_id, $to_user_id</code></td>\n<td>Customize transfer failure.</td>\n</tr>\n<tr>\n<td>Transferred</td>\n<td><code>wc_memberships_user_membership_transferred</code></td>\n<td>action</td>\n<td><code>$membership, WP_User $new_owner, WP_User $previous_owner</code></td>\n<td>Sync owner change.</td>\n</tr>\n<tr>\n<td>Deleted</td>\n<td><code>wc_memberships_user_membership_deleted</code></td>\n<td>action</td>\n<td><code>WC_Memberships_User_Membership $membership</code></td>\n<td>Clean external state before Memberships removes related data.</td>\n</tr>\n</tbody>\n</table>\n<h2>Purchase and free-signup grants</h2>\n<table>\n<thead>\n<tr>\n<th>Need</th>\n<th>Hook</th>\n<th>Type</th>\n<th>Args</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Choose order item product that grants access</td>\n<td><code>wc_memberships_access_granting_purchased_product_id</code></td>\n<td>filter</td>\n<td><code>int|int[] $product_ids, int[] $candidate_ids, WC_Memberships_Membership_Plan $plan</code></td>\n<td>Resolve parent/variation/multiple granting products.</td>\n</tr>\n<tr>\n<td>Access granted from purchase</td>\n<td><code>wc_memberships_grant_membership_access_from_purchase</code></td>\n<td>action</td>\n<td><code>WC_Memberships_Membership_Plan $plan, array $args</code></td>\n<td>Runs after plan grants access from an order. Args include user, order, product, membership.</td>\n</tr>\n<tr>\n<td>Free membership granted on signup</td>\n<td><code>wc_memberships_grant_free_membership_access_from_sign_up</code></td>\n<td>action</td>\n<td><code>WC_Memberships_Membership_Plan $plan, array $args</code></td>\n<td>Add onboarding/profile data for free signup plans.</td>\n</tr>\n<tr>\n<td>Force checkout registration</td>\n<td><code>wc_memberships_force_checkout_registration</code></td>\n<td>filter</td>\n<td><code>bool $force, array $plans</code></td>\n<td>Force account creation when cart grants membership access. Avoid disabling if granting products are in cart.</td>\n</tr>\n<tr>\n<td>Thank-you membership links</td>\n<td><code>woocommerce_memberships_thank_you_message</code></td>\n<td>filter</td>\n<td><code>string $message, int $order_id, array $memberships</code></td>\n<td>Customize order thank-you/email membership links.</td>\n</tr>\n</tbody>\n</table>\n<h2>Profile fields</h2>\n<table>\n<thead>\n<tr>\n<th>Need</th>\n<th>Hook</th>\n<th>Type</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Field type compatibility</td>\n<td><code>wc_memberships_profile_fields_compatible_types</code></td>\n<td>filter</td>\n<td>Extend/limit field type support.</td>\n</tr>\n<tr>\n<td>Visibility options</td>\n<td><code>wc_memberships_profile_fields_visibility_options</code></td>\n<td>filter</td>\n<td>Add visibility choices.</td>\n</tr>\n<tr>\n<td>Plan-specific visibility</td>\n<td><code>wc_memberships_profile_fields_membership_plan_visibility_options</code></td>\n<td>filter</td>\n<td>Change visibility choices for a plan/access method.</td>\n</tr>\n<tr>\n<td>Read profile fields</td>\n<td><code>wc_memberships_get_profile_fields</code></td>\n<td>filter</td>\n<td>Adjust loaded <code>Profile_Field</code> objects for a user.</td>\n</tr>\n<tr>\n<td>Validate definition</td>\n<td><code>wc_memberships_profile_field_definition_validation</code></td>\n<td>filter</td>\n<td>Validate admin field definition data.</td>\n</tr>\n<tr>\n<td>Validate value</td>\n<td><code>wc_memberships_profile_field_validation</code></td>\n<td>filter</td>\n<td>Validate submitted member value.</td>\n</tr>\n<tr>\n<td>Before/after signup fields</td>\n<td><code>wc_memberships_before_signup_profile_fields</code>, <code>wc_memberships_after_signup_profile_fields</code></td>\n<td>action</td>\n<td>Surround registration profile fields.</td>\n</tr>\n<tr>\n<td>Before/after product fields</td>\n<td><code>wc_memberships_before_product_profile_fields</code>, <code>wc_memberships_after_product_profile_fields</code></td>\n<td>action</td>\n<td>Surround product-page profile fields.</td>\n</tr>\n<tr>\n<td>Process product field submission</td>\n<td><code>wc_memberships_should_process_product_profile_fields_submission</code></td>\n<td>filter</td>\n<td>Disable product-page profile field collection for a product.</td>\n</tr>\n<tr>\n<td>My Account profile endpoint</td>\n<td><code>wc_memberships_profile_fields_area_endpoint</code></td>\n<td>filter</td>\n<td>Change endpoint slug/query var.</td>\n</tr>\n<tr>\n<td>Profile field CRUD</td>\n<td><code>wc_memberships_create_profile_field</code>, <code>wc_memberships_update_profile_field</code>, <code>wc_memberships_delete_profile_field</code></td>\n<td>action</td>\n<td>Observe user field storage changes.</td>\n</tr>\n<tr>\n<td>Definition CRUD</td>\n<td><code>wc_memberships_create_profile_field_definition</code>, <code>wc_memberships_update_profile_field_definition</code>, <code>wc_memberships_delete_profile_field_definition</code></td>\n<td>action</td>\n<td>Observe field definition changes.</td>\n</tr>\n</tbody>\n</table>\n<h2>REST API and webhooks</h2>\n<table>\n<thead>\n<tr>\n<th>Area</th>\n<th>Hooks</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>User membership write request</td>\n<td><code>wc_memberships_rest_api_create_user_membership_request</code>, <code>wc_memberships_rest_api_update_user_membership_request</code>, <code>wc_memberships_rest_api_user_membership_set_data</code></td>\n<td>Validate/shape REST create/update before save.</td>\n</tr>\n<tr>\n<td>User membership response</td>\n<td><code>wc_memberships_rest_api_user_membership_data</code>, <code>wc_memberships_rest_api_user_membership_links</code>, <code>wc_memberships_rest_api_user_membership_schema</code>, <code>wc_memberships_rest_api_user_membership_endpoint_args</code>, <code>wc_memberships_rest_api_user_memberships_collection_params</code></td>\n<td>Add fields, links, schema, endpoint args, or collection params.</td>\n</tr>\n<tr>\n<td>Membership plan response</td>\n<td><code>wc_memberships_rest_api_membership_plan_data</code>, <code>wc_memberships_rest_api_membership_plan_schema</code>, <code>wc_memberships_rest_api_membership_plans_collection_params</code></td>\n<td>Extend plan REST output.</td>\n</tr>\n<tr>\n<td>Excluded meta</td>\n<td><code>wc_memberships_rest_api_{$object_name}_excluded_meta_keys</code></td>\n<td>Hide internal meta keys in REST.</td>\n</tr>\n<tr>\n<td>Webhook topics</td>\n<td><code>wc_memberships_user_membership_webhook_topic_hooks</code></td>\n<td>Map webhook topics to actions.</td>\n</tr>\n<tr>\n<td>User membership webhook events</td>\n<td><code>wc_memberships_webhook_user_membership_created</code>, <code>updated</code>, <code>transferred</code>, <code>deleted</code></td>\n<td>Webhook topic source events; usually observe, not replace.</td>\n</tr>\n<tr>\n<td>Plan webhook events</td>\n<td><code>wc_memberships_webhook_membership_plan_created</code>, <code>updated</code>, <code>deleted</code>, <code>restored</code></td>\n<td>Webhook topic source events for plans.</td>\n</tr>\n</tbody>\n</table>\n<p>The public member directory endpoint is not a generic user-memberships list shortcut. <code>/wc/v4/memberships/members/directory</code> requires a <code>page_id</code> that contains a Memberships Directory block and validates the referenced block context. Directory responses are intentionally narrowed to the fields the block renders: <code>id</code>, <code>customer_data</code>, <code>plan_name</code>, <code>profile_fields</code>, and <code>meta_data</code> with <code>meta_data</code> blanked and <code>profile_fields</code> filtered to the block's allowed slugs. Cross-resource REST links are stripped. Use the admin-gated user memberships endpoint for full membership records.</p>\n<h2>Abilities API</h2>\n<p>Memberships 1.28.0+ registers WordPress Abilities API abilities when <code>wp_register_ability()</code> and <code>wp_register_ability_category()</code> exist:</p>\n<ul>\n<li><code>woocommerce-memberships/plans-create</code></li>\n<li><code>woocommerce-memberships/plans-delete</code></li>\n<li><code>woocommerce-memberships/plans-get</code></li>\n<li><code>woocommerce-memberships/plans-list</code></li>\n<li><code>woocommerce-memberships/user-memberships-create</code></li>\n<li><code>woocommerce-memberships/user-memberships-delete</code></li>\n<li><code>woocommerce-memberships/user-memberships-get</code></li>\n<li><code>woocommerce-memberships/user-memberships-list</code></li>\n<li><code>woocommerce-memberships/post-restriction-rules-get</code></li>\n<li><code>woocommerce-memberships/post-restriction-rules-update</code></li>\n</ul>\n<p>Plan and user-membership abilities use <code>manage_woocommerce</code>; post restriction rule abilities use <code>manage_woocommerce_membership_plans</code>. The GET rule ability also gets an <code>edit_post</code> check from its integer input, while UPDATE wrappers should add their own <code>edit_post</code> guard. Treat them as privileged admin/editor automation, not front-end/headless customer endpoints. Use <code>wcm-abilities-api</code> when implementing or reviewing ability-based automation.</p>\n<h2>Members area, directory, CSV, admin</h2>\n<table>\n<thead>\n<tr>\n<th>Area</th>\n<th>Hooks</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Members area wrapper</td>\n<td><code>wc_memberships_before_members_area</code>, <code>wc_memberships_after_members_area</code></td>\n<td>Add UI around a members-area section.</td>\n</tr>\n<tr>\n<td>My memberships table</td>\n<td><code>wc_memberships_my_memberships_column_names</code>, <code>wc_memberships_my_memberships_column_{$column_id}</code>, <code>wc_memberships_my_memberships_no_memberships_text</code></td>\n<td>Add or render columns in My Account.</td>\n</tr>\n<tr>\n<td>Members area navigation/details</td>\n<td><code>wc_memberships_members_area_navigation_items</code>, <code>wc_memberships_members_area_my_membership_details</code>, <code>wc_memberships_members_area_{$section}_actions</code>, <code>wc_memberships_members_area_{$section}_title</code></td>\n<td>Customize member dashboard structure.</td>\n</tr>\n<tr>\n<td>Section tables</td>\n<td><code>wc_memberships_members_area_my_membership_content_column_names</code>, <code>products_column_names</code>, <code>discounts_column_names</code>, <code>notes_column_names</code> plus matching <code>..._column_{$column_id}</code> actions</td>\n<td>Add columns to content/product/discount/note sections.</td>\n</tr>\n<tr>\n<td>Active discount display</td>\n<td><code>wc_memberships_members_area_show_only_active_discounts</code></td>\n<td>Include inactive/future discounts in member area.</td>\n</tr>\n<tr>\n<td>Member directory</td>\n<td><code>wc_memberships_member_directory_settings</code>, <code>wc_memberships_member_directory_listing_plans</code>, <code>wc_memberships_member_directory_included_members</code>, <code>wc_memberships_member_directory_before_member_card</code>, <code>after_member_card</code></td>\n<td>Shape directory query and cards.</td>\n</tr>\n<tr>\n<td>CSV import/export</td>\n<td><code>wc_memberships_csv_import_user_memberships_data</code>, <code>wc_memberships_csv_import_user_membership</code>, <code>wc_memberships_csv_export_user_memberships_headers</code>, <code>wc_memberships_csv_export_user_memberships_row</code>, <code>wc_memberships_csv_export_user_memberships_{$column_name}_column</code>, <code>wc_memberships_csv_export_user_memberships_query_args</code></td>\n<td>Add columns/data to Memberships import/export.</td>\n</tr>\n<tr>\n<td>Admin capabilities</td>\n<td><code>woocommerce_memberships_can_import_export</code>, <code>woocommerce_memberships_can_manage_profile_fields</code>, <code>wc_memberships_admin_screen_ids</code>, <code>wc_memberships_admin_tabs</code></td>\n<td>Integrate admin pages/caps carefully.</td>\n</tr>\n</tbody>\n</table>\n<h2>Woo Subscriptions integration</h2>\n<p>If WooCommerce Subscriptions is active, Memberships may use subscription-aware membership objects and lifecycle rules.</p>\n<table>\n<thead>\n<tr>\n<th>Need</th>\n<th>Hook</th>\n<th>Type</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Membership linked to subscription</td>\n<td><code>wc_memberships_user_membership_linked_to_subscription</code></td>\n<td>action</td>\n<td>Sync external relation. Args include membership object, new subscription ID, old subscription ID.</td>\n</tr>\n<tr>\n<td>Membership unlinked from subscription</td>\n<td><code>wc_memberships_user_membership_unlinked_from_subscription</code></td>\n<td>action</td>\n<td>Remove external relation.</td>\n</tr>\n<tr>\n<td>Cancel membership when linked subscription cancels</td>\n<td><code>wc_memberships_cancel_subscription_linked_membership</code></td>\n<td>filter</td>\n<td>Return false only when your integration deliberately keeps access after subscription cancellation.</td>\n</tr>\n<tr>\n<td>Subscription affects access start</td>\n<td><code>wc_memberships_access_from_time</code></td>\n<td>filter</td>\n<td>Subscriptions integration uses this for trial-aware drip access.</td>\n</tr>\n<tr>\n<td>REST subscription data</td>\n<td><code>wc_memberships_rest_api_user_membership_data</code>, <code>wc_memberships_rest_api_user_membership_schema</code>, <code>wc_memberships_rest_api_user_membership_set_data</code></td>\n<td>Subscriptions integration extends these with subscription fields.</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n<h2>Common mistakes</h2>\n<pre><code>// WRONG: skips Memberships creation/update hooks and object semantics.\nwp_insert_post( array(\n    'post_type'   =&gt; 'wc_user_membership',\n    'post_status' =&gt; 'wcm-active',\n) );\n\n// RIGHT: let Memberships create/renew/update the user membership.\n$membership = wc_memberships_create_user_membership( array(\n    'user_id' =&gt; $user_id,\n    'plan_id' =&gt; $plan_id,\n) );\n\n// WRONG: status meta/post writes bypass object methods and can miss side effects.\nwp_update_post( array( 'ID' =&gt; $membership_id, 'post_status' =&gt; 'wcm-cancelled' ) );\n\n// RIGHT: use the object API; pass unprefixed status.\n$membership = wc_memberships_get_user_membership( $membership_id );\nif ( $membership instanceof WC_Memberships_User_Membership ) {\n    $membership-&gt;update_status( 'cancelled', 'Cancelled by external CRM.' );\n}\n\n// WRONG: raw \"active\" check misses Memberships access semantics and delayed/free-trial cases.\n$is_member = 'wcm-active' === get_post_status( $membership_id );\n\n// RIGHT: use public checks for business rules.\nif ( wc_memberships_is_user_active_member( $user_id, $plan_id ) ) {\n    myplugin_enable_feature( $user_id );\n}\n\n// WRONG: function_exists() is a load-order race at registration time.\n// wc_memberships() is declared inside Memberships's plugins_loaded:10\n// callback. If your registrar runs first (also plugins_loaded:10), this\n// returns false and your integration silently doesn't register.\nadd_action( 'plugins_loaded', function () {\n    if ( ! function_exists( 'wc_memberships' ) ) {\n        return; // racy — sometimes false even when Memberships is active\n    }\n    register_my_membership_integration();\n}, 11 );\n\n// RIGHT: WC_Memberships_Loader is at file scope — race-free.\nadd_action( 'plugins_loaded', function () {\n    if ( ! class_exists( 'WC_Memberships_Loader' ) ) {\n        return;\n    }\n    register_my_membership_integration();\n}, 11 );\n\n// WRONG: hardcoding the status list — Memberships allows 3rd parties to add\n// their own statuses via the wc_memberships_user_membership_statuses filter.\n$statuses = array( 'active', 'paused', 'cancelled', 'expired' );\n\n// RIGHT: read from the registry (UNPREFIXED keys for object methods).\n$statuses = wc_memberships_get_user_membership_statuses( true, false );\nforeach ( $statuses as $key =&gt; $data ) {\n    // $key = 'active', 'paused', ...; $data['label'] = 'Active', 'Paused', ...\n}\n</code></pre>\n<h2>Cross-references</h2>\n<ul>\n<li>Use <code>wcm-data-model-subscriptions-link</code> for exact CPT names, meta keys, rule storage, profile-field storage, and Memberships-Subscriptions relation details.</li>\n<li>Use <code>wcm-access-discounts</code> for access checks, restriction/drip hooks, member discounts, and price-adjustment recursion safety.</li>\n<li>Use <code>wcm-abilities-api</code> for Memberships 1.29+ WP Abilities API names, permissions, schemas, REST route exposure, and guardrails.</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li>Verified source paths:\n<ul>\n<li><code>wp-content/plugins/woocommerce-memberships/src/class-wc-memberships-post-types.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/class-wc-memberships-user-memberships.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/class-wc-memberships-user-membership.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/class-wc-memberships-membership-plan.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/class-wc-memberships-rules.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/API/Controller/User_Memberships.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/API/Webhooks.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/Abilities/Provider.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/Plans/Abilities/</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/UserMemberships/Abilities/</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/Posts/Abilities/</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/Helpers/Directory_Block_Validator.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/Profile_Fields.php</code></li>\n<li><code>wp-content/plugins/woocommerce-memberships/src/integrations/subscriptions/</code></li>\n</ul>\n</li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":25118,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-16T14:58:36.873771Z","sha256":"10D1E55C7BCA522D3047B19716538B9814292902245E3919952A7526F1DDC4D8","sizeBytes":7598},"review":null,"source":{"repositoryUrl":"https://github.com/Lonsdale201/wp-agent-skills","path":"woocommerce/wcm-membership-hooks","license":"MIT","commit":"c51b571a259f0c4b5f5c0a3bc50ed580c6851f98","subtreeSha":"0209D396D1BEC7756877ABCB4753E05478E1942C389C3C51BB53E674DD4199C9","lastSyncedAt":"2026-09-29T23:33:03.303675Z"},"reviewedAt":"2026-09-16T15:19:09.107875Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/woocommerce/wcm-membership-hooks"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart"},{"target":"git","command":"git clone https://github.com/Lonsdale201/wp-agent-skills.git"}]}