SaveTo Wishlist for WooCommerce
Get Started
How to extend SaveTo Wishlist with hooks and the REST API - SaveTo Wishlist
  1. Home
  2. Knowledge Base
  3. Wishlist
  4. Developer Guides
  5. How to extend SaveTo Wishlist with hooks and the REST API
  1. Home
  2. Knowledge Base
  3. Wishlist
  4. How to extend SaveTo Wishlist with hooks and the REST API

How to extend SaveTo Wishlist with hooks and the REST API

Building a custom integration, changing how the wishlist button looks, or reacting when a customer saves a product? SaveTo Wishlist gives you WordPress filters and actions for each of these jobs, plus a REST API for reading and changing wishlists. This article lists the hooks and routes that ship with SaveTo Wishlist Lite 1.1.8, with the arguments each one receives and a working example for the common cases.

Before you start

  • You need SaveTo Wishlist Lite and WooCommerce active on the site. SaveTo Wishlist Pro and Registry add their own hooks, which are not covered here.
  • Every hook name starts with stwlite_.
  • Put your code in a small custom plugin or a code snippets plugin. Add placement filters (see below) in a plugin file, not in a theme functions.php file.
  • Every hook and route in this article exists in SaveTo Wishlist Lite 1.1.8. A hook that is not listed here is not part of the public surface, so do not rely on it.

Hooks

Each hook below lists its type, the arguments it receives, and what it does. When you register a callback that takes more than one argument, pass the argument count as the fourth parameter of add_filter() or add_action(), as the examples show.

Button output

These filters change the wishlist button markup before it prints. The plugin passes the final markup through wp_kses(), so tags and attributes outside its allow list are removed. To allow more tags, use the stwlite_allowed_button_html filter, which receives the allow list array.

HookTypeArgumentsWhat it does
stwlite_wishlist_button_htmlFilter$button_html, $product, $placement, $style, $button_labelFilters the markup of every wishlist button the plugin builds. It runs even when the button is hidden, in which case $button_html is an empty string.
stwlite_wishlist_button_shop_htmlFilter$button_html, $product, $placement, $style, $color_optionsFilters the button on shop and archive pages.
stwlite_wishlist_button_product_htmlFilter$button_html, $product, $placement, $style, $color_optionsFilters the button on single product pages. If your callback returns an empty string, no button prints.
stwlite_wishlist_cart_button_htmlFilter$button_htmlFilters the cart page button that saves the whole cart.
stwlite_wishlist_button_cart_item_htmlFilter$button_html, $product, $style, $color_optionsFilters the per-item button in the cart.
stwlite_wishlist_button_show_buttonFilter$show (default true), $productReturn false to hide the button for a product.
stwlite_wishlist_button_allow_product_multiple_listsFilter$allow (default true), $productReturn false to remove the “Choose list” drop-down from the button.
stwlite_before_wishlist_button_shop_page, stwlite_after_wishlist_button_shop_pageActionNoneFire before and after the shop page button prints.
stwlite_allowed_button_htmlFilter$allowed_htmlFilters the wp_kses() allow list for button markup.

This example adds a short note after the button for products on sale:

add_filter( 'stwlite_wishlist_button_html', function ( $button_html, $product, $placement, $style, $button_label ) {
    if ( '' === $button_html || ! $product instanceof WC_Product || ! $product->is_on_sale() ) {
        return $button_html;
    }

    return $button_html . '<span class="my-sale-note">On sale</span>';
}, 10, 5 );

Button placement, style, and labels

These filters change where the button appears and how it looks. Each one receives the saved setting and returns the value to use.

HookTypeArgumentsWhat it does
stwlite_wishlist_button_shop_placementFilter$placementPlacement on shop and archive pages.
stwlite_wishlist_button_product_placementFilter$placementPlacement on single product pages.
stwlite_wishlist_button_shop_style, stwlite_wishlist_button_product_style, stwlite_wishlist_button_cart_styleFilter$styleButton style for each page type.
stwlite_wishlist_button_shop_label, stwlite_wishlist_button_selected_shop_label, stwlite_wishlist_button_cart_labelFilter$labelButton text for the unsaved state, the saved state, and the cart button.
stwlite_wishlist_button_shop_color_options, stwlite_wishlist_button_product_color_options, stwlite_wishlist_button_cart_color_optionsFilter$color_optionsThe color settings array for each page type.

Shop placements are top_right, top_left, above_title, below_title, above_button, below_button, and disable. Product page placements are top_image_right, top_image_left, top_cart, bottom_cart, inline_cart, top_title, bottom_title, and disable. The plugin reads the placement once, when it boots on the setup_theme hook at priority 100. A placement filter must therefore be added before that point, so register it in a plugin file and not in a theme functions.php file. The style, label, and color filters run each time a button renders, so they work from any code.

add_filter( 'stwlite_wishlist_button_product_placement', function ( $placement ) {
    return 'bottom_cart';
} );

For the matching settings screen, see How to customize wishlist button placement and styles in WooCommerce.

Wishlist data

These filters change wishlist data as the plugin reads it. They run on many requests, including the ownership and permission checks, so keep your callbacks fast. Do not change the id, user_id, status, or is_public values, because the plugin reads them to decide who can see a wishlist.

HookTypeArgumentsWhat it does
stwlite_collection_dataFilter$collectionFilters the raw database row each time one wishlist is loaded by ID, URL code, or name.
stwlite_collections_dataFilter$dataFilters the array of wishlist objects returned for a list of wishlists.
stwlite_single_collection_dataFilter$dataFilters a single wishlist object after a save, and in the admin wishlist route.
stwlite_collection_items_dataFilter$dataFilters the array of item objects for a wishlist.
stwlite_all_wishlistsFilter$collections, $paramsFilters the list result (data, total, currentPage) before the frontend returns it.
stwlite_collection_data_to_updateFilter$data, $paramsFilters the column values just before the plugin writes a wishlist to the database.
stwlite_optionsFilter$optionsFilters the options array the plugin passes to the wishlist frontend script.

This example removes the description from wishlist objects in list responses:

add_filter( 'stwlite_collections_data', function ( $collections ) {
    foreach ( $collections as $collection ) {
        $collection->description = '';
    }

    return $collections;
} );

User wishlist access

These filters decide who can read or own a wishlist. For a read request, the plugin first allows administrators with the manage_options capability, then the wishlist owner, then public published wishlists, then its own share rules. Only after those checks does it call stwlite_user_wishlist_access.

HookTypeArgumentsWhat it does
stwlite_user_wishlist_accessFilter$granted (default null), $data, $user_id, $requestReturn true to allow read access, false to refuse it, or null to let the plugin decide. $data is the wishlist object, $user_id is 0 for a guest, and $request is the REST request when there is one.
stwlite_user_owns_wishlistFilter$owns, $collection_id, $user_id, $collectionWhether the current user owns the wishlist. It only runs for a wishlist that exists.
stwlite_show_all_wishlists_for_adminFilter$show (default false)Return true to let administrators see every customer wishlist in the frontend wishlists route.

This example lets shop managers read any wishlist:

add_filter( 'stwlite_user_wishlist_access', function ( $granted, $wishlist, $user_id, $request ) {
    if ( $user_id && user_can( $user_id, 'manage_woocommerce' ) ) {
        return true;
    }

    return $granted;
}, 10, 4 );

Wishlist actions

Use these actions to react when something changes, for example to sync data to a CRM or write a log entry.

HookTypeArgumentsWhen it fires
stwlite_before_product_added_to_wishlistAction$product_id, $quantity, $collection_id, $user_idBefore a product is saved to a wishlist from the button or the plugin’s AI abilities.
stwlite_after_product_added_to_wishlistAction$product_id, $quantity, $collection_id, $user_id, $addedAfter that save. $added is false when nothing was written. $collection_id is the raw request value and is often 0, which means the default wishlist.
stwlite_before_product_removed_from_wishlistAction$product_id, $collection_id, $variation_idsBefore a product is removed from a wishlist with the button.
stwlite_after_product_removed_from_wishlistAction$product_id, $collection_id, $variation_idsAfter a product is removed from a wishlist, from the button, the REST API, or the AI abilities.
stwlite_before_product_added_to_cart, stwlite_after_product_added_to_cartAction$collection_id, $product_id, $quantity, $variation_idBefore and after a wishlist item is moved to the cart.
stwlite_before_save_collectionAction$dataBefore a wishlist is written. $data is the array of column values.
stwlite_after_save_collectionAction$data, $paramsAfter a wishlist is written. $data is the saved wishlist object.
stwlite_after_insert_collection_item, stwlite_after_update_collection_itemAction$dataAfter an item row is inserted or updated.
stwlite_before_delete_collectionsAction$idsBefore wishlists are deleted.
stwlite_after_delete_collectionsAction$ids, $deletedAfter wishlists are deleted. $deleted is the number of deleted wishlists.

This example stores the last product a customer saved in their user meta:

add_action( 'stwlite_after_product_added_to_wishlist', function ( $product_id, $quantity, $collection_id, $user_id, $added ) {
    if ( ! $added || ! $user_id ) {
        return;
    }

    update_user_meta( $user_id, 'my_last_wishlisted_product', (int) $product_id );
}, 10, 5 );

Admin settings tabs and controls

The settings screen builds its tabs and fields from arrays. Use these filters to add your own tab, section, or field, or to change an existing one.

HookTypeArgumentsWhat it does
stwlite_admin_setting_tabsFilter$tabsFinal filter for the full tabs array.
stwlite_admin_setting_controlsFilter$controlsFinal filter for the full controls array.
stwlite_admin_data_tablesFilter$data_tables (default empty array)Registers data tables for the settings app.
stwlite_admin_setting_default_tabs, stwlite_admin_setting_default_parent_tabs, stwlite_admin_setting_default_child_tabsFilter$tabsEarlier filters for the tabs array, the top-level tab, and the child tabs.
stwlite_admin_setting_default_general_tab, stwlite_admin_setting_default_wishlist_page_tab, stwlite_admin_setting_default_wishlist_guest_tab, stwlite_admin_setting_default_wishlist_button_tab, stwlite_admin_setting_default_importer_tabFilter$tabFilters one child tab. The _sections variants of these names filter that tab’s sections.
stwlite_admin_setting_default_controlsFilter$controlsEarlier filter for the controls array.
stwlite_admin_setting_default_general_tab_controls, stwlite_admin_setting_default_wishlist_page_tab_controls, stwlite_admin_setting_default_wishlist_guest_tab_controls, stwlite_admin_setting_default_wishlist_button_tab_controlsFilter$controlsFilters the controls of one tab.

Tabs sit under the saveto_wishlist parent key. The child tab keys are general, wishlist_page, wishlist_guest, wishlist_button, and importer. Each child tab holds sort, key, label, icon, and a sections array. The controls array is keyed as $controls[ 'saveto_wishlist' ][ $tab_key ][ $section_key ], and each section holds a list of control arrays. A control has a type (for example switch or select), a label, an id that is the option name, a description, and a default. Controls of type switch use the values yes and no.

This example adds a switch to the General tab. It assumes the section key save_to_wishlist_settings, which is the first section of that tab.

add_filter( 'stwlite_admin_setting_default_general_tab_controls', function ( $controls ) {
    $controls['save_to_wishlist_settings'][] = array(
        'type'        => 'switch',
        'label'       => __( 'Log wishlist adds', 'my-plugin' ),
        'id'          => 'my_plugin_log_wishlist_adds',
        'description' => __( 'If Yes, write an entry to the debug log when a customer saves a product.', 'my-plugin' ),
        'options'     => array(
            'yes' => __( 'Yes', 'my-plugin' ),
            'no'  => __( 'No', 'my-plugin' ),
        ),
        'default'     => get_option( 'my_plugin_log_wishlist_adds', 'no' ),
    );

    return $controls;
} );

The settings screen saves each control value through the admin settings route described later in this article. Two dynamic filters let you handle a save or a button yourself:

  • stwlite_group_settings_{action} is a filter that receives the saved values as $options when the settings payload contains a key named action. It returns the response array with status and message.
  • stwlite_trigger_{action} is a filter that receives the request parameters as $params when the action route is called. It returns an array with status and message, and an optional redirect URL.

Frontend translations

HookTypeArgumentsWhat it does
stwlite_frontend_translationsFilter$i18nFilters the map of translation keys to translated strings that the plugin passes to the wishlist app. Return the array. A non-array return value is ignored.

The wishlist page loads these strings twice per request, so your callback should merge in a map and avoid heavy work. This example changes one string. Use a key that already exists in the array.

add_filter( 'stwlite_frontend_translations', function ( $i18n ) {
    $i18n['just_now'] = __( 'a moment ago', 'my-plugin' );

    return $i18n;
} );

Usage tracking data

When a store has allowed usage tracking, the plugin sends a periodic check-in. These filters let a companion plugin add to that data. Values must be scalars, and they must not carry credentials or personal data.

HookTypeArgumentsWhat it does
stwlite_usage_checkin_dataFilter$dataFilters the whole check-in payload.
stwlite_usage_settingsFilter$settingsFilters the settings name and value pairs in the payload.
stwlite_usage_effectivenessFilter$data, $start, $endFilters the weekly effectiveness metrics. $start and $end are UTC dates in Y-m-d H:i:s format.

The “Usage Tracking” setting on the General tab controls consent. If a store sets it to No, the plugin does not report, whatever your filters return.

REST API

All routes use the namespace saveto-wishlist/v1, so the full path is https://your-site.com/wp-json/saveto-wishlist/v1/ followed by the path in the tables. They follow normal WordPress REST authentication. Browser code sends the X-WP-Nonce header for a logged-in user, and other clients can use an application password with HTTP Basic authentication. A refused request returns the rest_forbidden_context error with status 401 for a logged-out caller or 403 for a logged-in one.

Customer routes

These routes power the wishlist button and the wishlist page.

Method and pathWho can call itWhat it does
POST /frontend/endpointsLogged-in users. Guests are refused unless a permission filter allows them.Runs one wishlist action chosen by the action parameter. See the action table below.
GET /frontend/endpoints/wishlistsSame as above.Returns data, total, and currentPage for wishlists. Customers see their own wishlists. Passing is_public=true lists other customers’ public wishlists.
GET /frontend/endpoints/wishlist-itemsLogged-in users. A guest can read only a public wishlist named by collection_id.Returns the items of the wishlist in the required collection_id parameter. The caller must also be allowed to view that wishlist, or the route returns a 401 or 403 error.
POST /frontend/endpoints/guest-wishlist-productsGuests only. A logged-in user gets an error.Takes a products array of objects with productId, quantity, priority, and addedAt. Returns the matching product data for a wishlist stored in the browser.

The POST /frontend/endpoints route always answers HTTP 200 with a status field, so check status in the body. A caller who does not own the wishlist gets status set to error. Only users with the manage_options capability can write to another user’s wishlist.

action valueParameters it readsHook it fires first
add_wishlistname, description, status, is_default, is_public, url_code, emails, user_ids, allowed_roles, allowed_edit_emailsstwlite_before_api_save_collection_add_wishlist with $data
update_wishlistid plus any field from add_wishlist. Only fields you send are updated.stwlite_before_api_save_collection_update_wishlist with $data
remove_wishlistidsNone
add_wishlist_itemcollection_id, product_id, variation_id, quantity, priority, notesstwlite_before_api_save_collection_item_add_wishlist_item with $data
update_wishlist_itemid plus the fields from add_wishlist_itemstwlite_before_api_save_collection_item_update_wishlist_item with $data
move_wishlist_itemid, collection_idstwlite_before_api_save_collection_item_move_wishlist_item with $data
remove_wishlist_itemcollection_id, product_id, variation_idstwlite_after_product_removed_from_wishlist fires after the removal with $product_id, $collection_id, $variation_ids
update_wishlist_item_positionlistsNone

This request lists the wishlists of the account that owns the application password:

curl -u "username:application-password" \
  "https://your-site.com/wp-json/saveto-wishlist/v1/frontend/endpoints/wishlists"

These filters change who passes the customer route checks and what the guest route returns:

HookTypeArgumentsWhat it does
stwlite_api_user_permissions_checkFilter$response, $requestFinal result for POST /frontend/endpoints and GET /frontend/endpoints/wishlists. Return true or a WP_Error.
stwlite_api_user_force_accessFilter$force (default false), $requestReturn true to let a logged-out caller through those two routes.
stwlite_check_any_user_has_permissionFilter$response, $requestFinal result for the wishlist-items route.
stwlite_check_any_user_has_permission_validationFilter$valid (default false), $data, $requestReturn true to let a logged-out caller read a wishlist that is not public.
stwlite_check_guest_permissionFilter$response, $requestFinal result for the guest-wishlist-products route.
stwlite_guest_wishlist_products_dataFilter$products, $product_dataFilters the product list that the guest route returns.

Admin routes

The routes below serve the plugin’s own admin screens, and they may change between releases. Prefer the hooks above for integrations. Every admin route requires the manage_options capability. Use the stwlite_api_admin_permissions_check filter ($response, $request) to change that, and take care when you widen access, because these routes can write site options and delete wishlists.

Method and pathWhat it does
POST /admin/wishlistsCreates or updates a wishlist from the request body. It returns a 422 error if you add items that are not saved yet to a wishlist whose status is draft.
DELETE /admin/wishlistsDeletes the wishlists in the ids parameter. The response lists count and skipped. A wishlist is skipped when it is the last wishlist of its owner.
GET /admin/wishlists/collectionsReturns a page of wishlists with data, total, and currentPage.
GET /admin/wishlists/collection/{id}Returns one wishlist by numeric ID.
GET /admin/wishlists/itemsPlaceholder route that returns an empty data list.
POST /admin/settings/saveSaves settings. The body is a list of objects with key and value. A value of true or false is stored as yes or no.
POST /admin/settings/actionRuns the stwlite_trigger_{action} filter for the action parameter.
GET /admin/importer/sourcesLists the wishlist plugins the importer detected on the site.
GET /admin/importer/preview/{source}Returns the item counts and a preview for one source.
POST /admin/importer/importStarts an import. It takes source and an optional options object (merge_existing, preserve_default, preserve_privacy, batch_number, job_id). A user can start one import every 300 seconds.
GET /admin/importer/import/{job_id}Returns the progress of an import job.
DELETE /admin/importer/import/{job_id}Cancels an import job.

The built-in import sources are yith, ti, moreconvert, and wpc. Use the stwlite_available_importers filter, which receives and returns an array that maps a source slug to an importer class name, to register another one.

Frequently asked questions

Why does my placement filter do nothing?

The plugin reads the placement when it boots on the setup_theme hook at priority 100. Add the filter from a plugin file. A theme functions.php file loads after that point.

Why does my button markup lose a tag or attribute?

The plugin runs the button markup through wp_kses(). Add the tag or attribute to the allow list with the stwlite_allowed_button_html filter.

Why does the save route return HTTP 200 for a refused request?

POST /frontend/endpoints always answers with HTTP 200 and puts the result in the status field of the body. Read status and message instead of the HTTP code.

What’s next

Need help?

If you have a question or run into any issues, we’re here to help.

Was this article helpful?

Related Articles

Need Support?

Can't find the answer you're looking for?
Contact Support
Complete Your Purchase