Cross-Sell and Up-Sell Blocks for E-Commerce

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Cross-Sell and Up-Sell Blocks for E-Commerce
Medium
~2-3 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Development of Cross-Sell and Up-Sell Blocks for E-Commerce

Cross-sell and up-sell are two distinct mechanisms for increasing average order value that are often confused. Cross-sell is the offer of complementary products ("this mouse works with this laptop"). Up-sell is the offer of a more expensive version of the same product ("for 2000 ₽ more — the Pro version"). Development of both blocks takes 4–6 business days.

Distinguishing Cross-Sell from Up-Sell

Parameter Cross-Sell Up-Sell
What is offered Complementary products Premium version of current
Where shown Product page, cart Product page, before adding to cart
Metric Increase number of items Increase amount per item
Example Phone case 128 GB instead of 64 GB

Data Model: Manual Relationships

For small catalogs — manual relationship assignment in the admin panel:

CREATE TABLE product_relations (
    id BIGSERIAL PRIMARY KEY,
    product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
    related_product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
    type VARCHAR(20) NOT NULL, -- 'cross_sell', 'up_sell', 'accessory', 'spare_part'
    sort_order SMALLINT DEFAULT 0,
    UNIQUE(product_id, related_product_id, type)
);
CREATE INDEX idx_product_relations_pid_type ON product_relations(product_id, type);

In the admin panel — product search and relationship addition via drag-and-drop with type selection.

Automatic Cross-Sell via Categories

If relationships are not manually set — automatic fallback based on co-purchases or accessory categories:

class CrossSellResolver
{
    public function resolve(Product $product, int $limit = 4): Collection
    {
        // 1. Manual relationships
        $manual = $product->relations()
            ->where('type', 'cross_sell')
            ->with('relatedProduct')
            ->orderBy('sort_order')
            ->limit($limit)
            ->get()
            ->map(fn($r) => $r->relatedProduct);

        if ($manual->count() >= $limit) return $manual;

        // 2. Automatic from cooccurrences (if sufficient data)
        $needed = $limit - $manual->count();
        $auto = DB::table('product_cooccurrences')
            ->where('product_a', $product->id)
            ->whereNotIn('product_b', $manual->pluck('id'))
            ->orderByDesc('cooccurrence_count')
            ->limit($needed)
            ->pluck('product_b');

        $autoProducts = Product::whereIn('id', $auto)->where('is_active', true)->get();
        return $manual->merge($autoProducts);
    }
}

Up-Sell: Variants of One Product

For variant products (phones with different storage) up-sell is navigation between variants with emphasis on premium:

const UpSellVariants = ({ currentVariant, variants }: UpSellProps) => {
  const betterVariants = variants.filter(v => v.price > currentVariant.price);

  if (!betterVariants.length) return null;

  return (
    <div className="border rounded-lg p-4 bg-amber-50">
      <p className="text-sm font-medium mb-2">Consider the upgraded version:</p>
      {betterVariants.slice(0, 2).map(variant => (
        <div key={variant.id} className="flex items-center justify-between py-2">
          <span className="text-sm">{variant.label}</span>
          <div className="flex items-center gap-2">
            <span className="text-xs text-gray-500">
              +{formatPrice(variant.price - currentVariant.price)}
            </span>
            <Button size="sm" variant="outline" onClick={() => selectVariant(variant)}>
              Select
            </Button>
          </div>
        </div>
      ))}
    </div>
  );
};

Cross-Sell in Cart: "Complete Your Order"

The most conversion-friendly moment for cross-sell is the cart page. The "Frequently Bought Together" block aggregates recommendations from all products in the cart:

public function getCartCrossSells(Cart $cart): Collection
{
    $cartProductIds = $cart->items->pluck('product_id');

    // Combine cross-sell recommendations from all cart items
    $recommendations = DB::table('product_relations')
        ->join('products', 'products.id', '=', 'product_relations.related_product_id')
        ->whereIn('product_relations.product_id', $cartProductIds)
        ->whereNotIn('product_relations.related_product_id', $cartProductIds)
        ->where('product_relations.type', 'cross_sell')
        ->where('products.is_active', true)
        ->where('products.stock', '>', 0)
        ->select('products.*', DB::raw('COUNT(*) as relevance_score'))
        ->groupBy('products.id')
        ->orderByDesc('relevance_score')
        ->limit(4)
        ->get();

    return $recommendations;
}

A product recommended by multiple cart items receives a higher relevance_score and is displayed first.

Quick-Add in Recommendation Block

"Add to Cart" button directly in cross-sell card — without going to product page:

const CrossSellCard = ({ product }: { product: Product }) => {
  const { addToCart, isLoading } = useCart();

  return (
    <div className="border rounded-lg p-3 flex gap-3">
      <img src={product.thumb} alt={product.name} className="w-16 h-16 object-cover rounded" />
      <div className="flex-1 min-w-0">
        <p className="text-sm font-medium truncate">{product.name}</p>
        <p className="text-sm text-gray-700">{formatPrice(product.price)}</p>
      </div>
      <Button
        size="sm"
        loading={isLoading(product.id)}
        onClick={() => addToCart(product.id, 1)}
      >
        + Add to Cart
      </Button>
    </div>
  );
};

Bundle: Fixed Sets

A separate type of cross-sell — a bundle with set discount:

CREATE TABLE product_bundles (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255),
    discount_percent NUMERIC(5,2) DEFAULT 0,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE product_bundle_items (
    bundle_id BIGINT REFERENCES product_bundles(id) ON DELETE CASCADE,
    product_id BIGINT REFERENCES products(id),
    is_primary BOOLEAN DEFAULT FALSE,
    PRIMARY KEY(bundle_id, product_id)
);

On the main product page, a "Buy as Bundle" block is shown: all products in the bundle + total price with discount. Adding to cart — with one button.

Analytics of Block Effectiveness

Each cross-sell/up-sell impression is logged. Metrics:

  • Impressions — how many times the block was shown
  • CTR — clicks on recommendations / impressions
  • Add-to-cart rate — additions / clicks
  • Uplift — average order value with cross-sell vs without

Data allows optimization of placement, number of recommendations, and algorithm selection. Usually 4 recommendations show better CTR than 2 or 8.