Skip to content

Custom Product Selling Options

FluentCart provides a powerful hook system that allows developers to sell custom products that don't exist in the product catalog. This feature enables selling add-ons, gift wrapping, custom subscriptions, and any other custom items through your store.

Overview

Custom product selling allows you to add items to the cart that are not stored as regular products in the database. This is useful for:

  • Add-on Products - Gift wrapping, insurance, custom options
  • Custom Subscriptions - Recurring billing for non-catalog items
  • Dynamic Products - Products created on-the-fly based on user selections
  • Service-Based Items - One-time or recurring service purchases

Implementation Flow

The complete implementation follows this flow:

Add Button → Add Validation Hook → Handle Quantity Change Hook → Checkout Validation Hook

Step 1: Add Button (Frontend)

Add the appropriate button to your page based on the item type.

One-Time Custom Item (Add to Cart)

For one-time purchases that are added to the cart:

html
<button 
    data-cart-id="1000"
    data-quantity="1"
    data-is-custom="true"
    data-fluent-cart-add-to-cart-button
>
  Add Gift Wrapping
</button>

Subscription Custom Item (Instant Checkout)

For subscription items that go directly to checkout:

html
<a href="https://yourstore.com/?fluent-cart=instant_checkout&item_id=3001&quantity=1&is_custom=true">
  Buy Now
</a>

Note: Replace https://yourstore.com with your actual site URL and 3001 with your custom item ID.


Step 2: Add Validation Hook (Backend)

Use this filter to validate and return the custom product item when it's added to the cart.

Hook: fluent_cart/cart/validate_custom_item

Parameters:

  • $variation (object/array): The variation object to be modified
  • $item (array): The item data from the request

Returns: The modified variation object

Example: Validate New Custom Item

php
add_filter('fluent_cart/cart/validate_custom_item', 
function($variation, $item) {
    if ((bool)$item['is_custom']) {
        $variation = (object) [
            'item_id'      => absint($item['item_id']),
            'object_id'    => absint($item['item_id']),
            'post_id'      => 5000,
            'quantity'     => max(1, (int)$item['quantity']),
            'price'        => floatval(5000),
            'unit_price'   => floatval(5000),
            'line_total'   => floatval(5000) * max(1, (int)$item['quantity']),
            'post_title'   => sanitize_text_field('Weekly Protein Pack'),
            'title'        => sanitize_text_field('Subscription Version 1'),
            'is_custom'    => true,
            'payment_type' => 'subscription',
            "other_info"       => [
                "payment_type"      => "subscription",
                "repeat_interval"   => "monthly",
                "installment"     => "no",
                "manage_setup_fee" => "yes",
                 "signup_fee"       => 500
            ],

        ];
    }
    return $variation;
}, 10, 2);

Step 3: Handle Quantity Changes (Cart)

This step applies only to one-time items for quantity increment/decrement actions.

Hook: fluent_cart/cart/custom_item_quantity_changed

Parameters:

  • $variation (object/array): The variation object to be updated
  • $item (array): Item data including quantity changes

Returns: The modified variation object with updated quantity and totals

Example: Change Existing Item Quantity

php
add_filter('fluent_cart/cart/custom_item_quantity_changed', function ($variation, $item) {

    if (empty($item['is_custom'])) {
        return $variation;
    }

    // Ensure array
    $variation = (array) $variation;

    $newQty  = (int) ($item['new_quantity'] ?? 1);
    $oldQty  = (int) ($item['old_quantity'] ?? 1);
    $byInput = (bool) ($item['by_input'] ?? false);

    /**
     * If quantity changed via increment, decrement or delete buttons
     */
    if (!$byInput) {
        $newQty = $newQty == 0 ? 0 : ($oldQty+$newQty);
    }

    $price = (float) ($variation['price'] ?? 0);

    $variation['quantity']   = $newQty;
    $variation['line_total'] = $price * $newQty;

    return $variation;

}, 10, 2);

Step 4: Checkout Validation

This hook runs before payment processing for both one-time and subscription items.

Hook: fluent_cart/payment/validate_custom_item

Parameters:

  • $items (array): Array containing product and variation objects
  • $data (array): Additional checkout data

Returns: Modified items array

Example: Payment Validate Item

php
add_filter('fluent_cart/payment/validate_custom_item', function ($items, $data) {

    [$product, $variation] = $items;

    // Mark the item as custom visually
    $variation->title    .= ' (Custom Item)';
    $product->post_title .= ' [Custom]';

    return [$product, $variation];

}, 10, 2);

Step 5: Admin Order Update Handling (Optional)

Handle custom item updates from the WordPress admin panel after an order is placed.

Order Item Changed

Action: fluent_cart/order/custom_item_changed

Parameters:

  • $oldItem (array): The original item data
  • $item (array): The updated item data

Returns: The item to be saved

Example: Order Item Changed

php
add_filter('fluent_cart/order/custom_item_changed', function ($oldItem, $item) {

    $oldItem = (array) $oldItem;
    $item    = (array) $item;

    $isCustom = (bool) ($item['is_custom'] ?? false);
    $newQuantity  = (int) ($item['quantity'] ?? 1);
    $oldQuantity  = (int) ($oldItem['quantity'] ?? 1);

    /**
     * If item is a Custom Item → always save updated changes
     */
    if ($isCustom) {
        return $item;
    }

    /**
     * If quantity is unchanged → return old item (no update)
     */
    if ($oldQuantity === $newQuantity) {
        return $oldItem;
    }

    /**
     * If quantity changed → allow update
     */
    return $item;

}, 10, 2);

Before Custom Items Are Deleted

Action: fluent_cart/order/before_custom_items_deleted

Parameters:

  • $customItems (array): Array of custom items to be deleted
  • $order (object): The order object

Example: Before Order Item Deleted

php
add_action('fluent_cart/order/before_custom_items_deleted', function ($customItems, $order) {

    foreach ($customItems as $item) {
        error_log(
            sprintf(
                'Custom item about to be deleted. Order #%d | Item ID: %d | Qty: %d',
                $order->id,
                $item->id,
                $item->quantity
            )
        );

        // Restore external stock if needed
    }

}, 10, 2);

After Custom Items Are Deleted

Action: fluent_cart/order/after_custom_items_deleted

Parameters:

  • $customItems (array): Array of deleted custom items
  • $order (object): The order object

Example: After Order Item Deleted

php
add_action('fluent_cart/order/after_custom_items_deleted', function ($customItems, $order) {

    foreach ($customItems as $item) {
        error_log(
            sprintf(
                'Custom item deleted. Order #%d | Item ID: %d | Qty: %d',
                $order->id,
                $item->id,
                $item->quantity
            )
        );

        // Analytics or internal counters
        // Notify admin
        // wp_mail('[email protected]', 'Custom Item Deleted', 'Item ID ' . $item->id . ' was deleted');
    }

}, 10, 2);

Sample Datasets

One-Time Item

php
$item = [
    "item_id"          => 1000,
    "object_id"       => 1000,
    "post_id"          => 10000,
    "quantity"        => 1,
    "is_custom"      => true,
    "post_title"        => "Air Max 1 running shoe",
    "title"                 => "Version 1",
    "price"               => 2000,
    "unit_price"      => 2000,
    "line_total"        => 2000 * 1, // quantity is 1
    "payment_type"     => "onetime",
    "fulfillment_type" => "digital",
    "featured_media"   => "http://wordpress.test/wp-content/uploads/2025/11/white-navy-athletic-shoe-4-1.jpeg",
    "sold_individually"=> 0,
    "other_info"       => [
        "payment_type" => "onetime"
    ],
    "view_url"         => "https://www.abelandcole.co.uk/turkey-breast-joint-high-welfare-kellybronze?cid=9427"
];

Subscription Item

php
$item = [
    "item_id"          => 2000,
    "object_id"       => 2000,
    "post_id"          => 20000,
    "quantity"        => 1, // always 1 for subscription
    "is_custom"      => true,
    "post_title"        => "Weekly Protein Pack",
    "title"                 => "Subscription Version 1",
    "price"               => 2500,
    "unit_price"      => 2500,
    "line_total"       => 2500 * 1,
    "payment_type"     => "subscription",
    "fulfillment_type" => "digital",
    "featured_media"   => "http://wordpress.test/wp-content/uploads/2025/11/protein-pack-1.jpeg",
    "sold_individually"=> 1, // enforce quantity = 1
    "other_info"       => [
        "payment_type"      => "subscription",
        "repeat_interval"   => "monthly",
        "installment"     => "no",
        "manage_setup_fee" => "yes",
         "signup_fee"       => 500
    ],
    "view_url"         => "https://www.abelandcole.co.uk/weekly-protein-pack?cid=9428"
];

Payment Gateway Validation During Checkout

php
$variation = (object)[
    "item_id"          => 1000,
    "object_id"        => 1000,
    "post_id"          => 10000,
    "quantity"         => 1,
    "is_custom"        => true,
    "post_title"       => "Air Max 1 running shoe",
    "title"            => "Version 1",
    "price"            => 2000,
    "unit_price"       => 2000,
    "line_total"       => 2000 * 1,
    "payment_type"     => "onetime",
    "fulfillment_type" => "digital",
    "featured_media"   => "http://wordpress.test/wp-content/uploads/2025/11/white-navy-athletic-shoe-4-1.jpeg",
    "sold_individually"=> 0,
    "other_info"       => [
        "payment_type" => "onetime"
    ],
    "view_url"         => "https://www.example.com/product/air-max-1-running-shoe"
];

$product = (object)[
    "ID"           => 148,
    "post_title"   => "Air Max 1 running shoe",
    "post_status"  => "publish",
];

Key Properties Reference

Item Object Properties

PropertyTypeDescriptionExample
item_idintegerUnique identifier for the custom item1000
object_idintegerReference ID for the item1000
post_idintegerWordPress post ID reference5000
quantityintegerItem quantity1
is_custombooleanMarks item as customtrue
post_titlestringProduct title"Air Max 1 running shoe"
titlestringVariation title"Version 1"
pricefloatUnit price2000
unit_pricefloatUnit price (same as price)2000
line_totalfloatTotal price (price × quantity)2000
payment_typestringonetime or subscription"onetime"
fulfillment_typestringdigital or physical"digital"
featured_mediastringMedia URL"http://..."
sold_individuallyinteger0 for no, 1 for yes0
other_infoarrayAdditional informationSee below

other_info Properties for Subscriptions

PropertyTypeDescriptionExample
payment_typestringType of payment"subscription"
repeat_intervalstringBilling frequency"monthly"
installmentstringInstallment plan"no"
manage_setup_feestringEnable setup fee"yes"
signup_feefloatSetup fee amount500

Best Practices

  1. Always Validate Data: Use the validation hook to ensure item data is correct before adding to cart
  2. Handle Quantity Changes: Implement quantity change handlers for one-time items
  3. Mark Custom Items: Use is_custom flag to distinguish custom items from regular products
  4. Set Correct Payment Type: Ensure payment_type is set correctly (onetime or subscription)
  5. Calculate Totals: Always calculate line_total as price × quantity
  6. Use Sanitization: Apply sanitize_text_field() and absint() for security
  7. Handle Admin Actions: Implement order update handlers if admins will modify orders


Next Steps: Explore Payment Methods Module or return to Modules Overview

FluentCart developer documentation