---
title: "Ubercart-Customizing shopping cart for Marketplace Website · Appnovation"
url: "https://www.appnovation.com/insights/ubercart-customizing-shopping-cart-marketplace-website"
description: "How to customize the Ubercart shopping cart for a multi-seller marketplace, grouping items by seller with per-seller subtotals."
---

Article

# Ubercart-Customizing shopping cart for Marketplace Website

[![](https://cdn.sanity.io/images/ey850001/production/f0e6f26b2d1cd656f2bc33b49d25cf5cb8d44e93-68x68.png?w=68&auto=format) rllabore rllabore Developer](/people/rllabore-rllabore.md) Sep 12, 2011  
 12 min read 

![Appnovation Blog Default Header](https://cdn.sanity.io/images/ey850001/production/9efe1c26d9584ecfb3dfef5a63e698bf4567d305-1440x600.jpg?w=1400&auto=format) 

I am currently working on a [e-commerce](/services/technology-platform-development/ecommerce-development.md) website which requires me to group items on the cart by seller. The shopping cart should also display the subtotal and a submit button for each seller. Ubercart is designed assuming that your website is the only store. However, it is very flexible that you can support multiple stores/merchants on your site. There are many ways in which you can customize Ubercart. When you do, it is important to follow Ubercart methodologies first, by using Ubercart defined hooks. If no Ubercart hook is available, then use Drupal methodologies. Here are the steps that I took in order to customize the shopping cart page:

1.Implement hook\_add\_to\_cart\_data() - When an item is added to the cart, I need to be able to retrieve the seller user id or the store node id that the item is associated with. Since the owner of the item will unlikely change, I might as well put the seller user id or store node in the data array of an item.

2.Implement hook\_TYPE\_alter(), (TYPE = 'uc\_cart') – Ubercart allows developers to alter items loaded from the database (see uc\_cart\_get\_contents()). To make it easier for me to display items grouped by seller, I had to alter how raw data was arranged. On the cart page, I rearranged the items data structure so that they are grouped by seller already before it is processed at the form level.

3.Implement hook\_TYPE\_alter(), (TYPE = 'cart\_pane') – The cart page is designed so that other modules can display their information on the page using panes. I needed to override the “cart\_form” pane so that it would display my custom cart form which was derived from what Ubercart is using by default.

4.Implement my custom cart form – The reason why implemented my own custom cart form and not use hook\_form\_alter is because in order to display the desired output, I had to restructure the form data. The default cart form uses Table API to display the items on the cart. By default, there is only one table API element which holds all items on the cart. Since I had to group items by seller, the same number of table API elements should equal the number sellers.

5.Implement my custom table definition function (see uc\_cart\_view\_table) – I had to use my own cart view table function which is an argument in “tapir\_get\_table”. “tapir\_get\_table” function is responsible for building a table API element. I implemented my own table definition function because I had to add the “Price” column.

6.Implement the theme function of my custom cart form – The my theme function is derived from the theme function of default cart form. Since the form structure has been altered dramatically, I had to modify my theme function accordingly. SAMPLE CODE: STEP 1:

/\*\* \* Implementation of hook\_add\_to\_cart\_data() \*/ function MY\_MODULE\_add\_to\_cart\_data($form\_values) { $node = node\_load($form\_values\['nid'\]); return array('uid' => $node->uid); } 

STEP 2:

/\*\* \* Implementation of hook\_TYPE\_alter(). \* NOTE: TYPE == 'uc\_cart' \*/ function MY\_MODULE\_uc\_cart\_alter(&$items) { $cart\_page = 'cart'; /\* before showing to user, rearrange the items by owner; on cart page, I will control how the items data structure will be formatted \*/ if($\_GET\['q'\] == $cart\_page) { $temp\_basket = array(); foreach($items as $i => $item) { $temp\_basket\[$item->data\['uid'\]\]\[\] = $item; } $items = array(); $weight = 0; foreach($temp\_basket as $uid => $\_items) { foreach($\_items as $\_item) { $\_item->weight = $weight++; } } $items = $temp\_basket; } } 

STEP 3:

/\*\* \* Implementation of hook\_TYPE\_alter(). \* NOTE: TYPE == 'cart\_pane' \*/ function MY\_MODULE\_cart\_pane\_alter(&$panes, $items) { if(!count($panes) || !count($items)) { return; } foreach($panes as $k => &$pane) { if($pane\['id'\] == 'cart\_form') { $cart\_form = !is\_null($items) ? '

'. drupal\_get\_form('MY\_MODULE\_view\_cart\_form', $items) .'

': ''; $pane\['body'\] = $cart\_form; } } } 

STEP 4:

/\*\* \* Custom implementation of cart form. \* @see uc\_cart\_view\_form() \*/ function MY\_MODULE\_view\_cart\_form($form\_state, $items = NULL) { $form = array(); $i = 0; /\* items in the cart are grouped by node owner \*/ foreach ($items as $owner\_uid => $owner\_items) { /\* seller link \*/ $account = user\_load($owner\_uid); $link = l($account->name, 'profile/'. $account->uid); $form\[$owner\_uid\]\['seller'\] = array( '#value' => t('Order from ') . $link, ); /\* build tapir table element; contains items from each seller \*/ $form\[$owner\_uid\]\['items'\] = array( '#type' => 'tapir\_table', '#tree' => TRUE, ); $context = array( 'revision' => 'themed-original', 'type' => 'amount', ); /\* loop through each item that belongs to the current owner \*/ foreach($owner\_items as $item) { display\_item = module\_invoke($item->module, 'cart\_display', $item); if (!empty($display\_item)) { /\* its OK to node\_load each product since by the time this gets called, the node has been statically cached \*/ $product\_node = node\_load($display\_item\['nid'\]\['#value'\]); /\* modify the image size \*/ $image = theme('imagecache', 'MY\_MODULE\_product\_preset', $product\_node->field\_image\_cache\[0\]\['filepath'\]); $l\_path = 'node/'. $product\_node->nid; $l\_options = array( 'html' => TRUE, ); $form\[$owner\_uid\]\['items'\]\[$i\] = $display\_item; $form\[$owner\_uid\]\['items'\]\[$i\]\['image'\]\['#value'\] = l($image, $l\_path, $l\_options); $description = $display\_item\['title'\]\['#value'\] . $display\_item\['description'\]\['#value'\]; $form\[$owner\_uid\]\['items'\]\[$i\]\['desc'\]\['#value'\] = $description; $form\[$owner\_uid\]\['items'\]\[$i\]\['title'\]\['#type'\] = 'value'; $form\[$owner\_uid\]\['items'\]\[$i\]\['description'\]\['#type'\] = 'value'; if (empty($display\_item\['qty'\])) { $form\[$owner\_uid\]\['items'\]\[$i\]\['qty'\] = array( '#value' => '', ); } $form\[$owner\_uid\]\['items'\]\[$i\]\['price'\]\['#value'\] = sprintf('$%01.2f',$item->price); $form\[$owner\_uid\]\['items'\]\[$i\]\['total'\] = array( '#value' => uc\_price($display\_item\['#total'\], $context), '#theme' => 'uc\_cart\_view\_price', ); $i++; } } /\* build the tapir element for the given seller \*/ $form\[$owner\_uid\]\['items'\] = tapir\_get\_table('MY\_MODULE\_\_helpers\_view\_cart\_table', $form\[$owner\_uid\]\['items'\]); /\* submit order button for each seller \*/ $form\[$owner\_uid\]\['submit\_order'\] = array( '#type' => 'submit', '#value' => t('Submit Order'), '#submit' => array('MY\_MODULE\_\_forms\_cart\_order\_submit'), '#name' => $owner\_uid ); } // If the continue shopping element is enabled... if (($cs\_type = variable\_get('uc\_continue\_shopping\_type', 'link')) !== 'none') { // Setup the text used for the element. $cs\_text = variable\_get('uc\_continue\_shopping\_text', '') ? variable\_get('uc\_continue\_shopping\_text', '') : t('Continue shopping'); // Add the element to the form based on the element type. if (variable\_get('uc\_continue\_shopping\_type', 'link') == 'link') { $form\['continue\_shopping'\] = array( '#value' => l($cs\_text, uc\_cart\_continue\_shopping\_url()), ); } elseif (variable\_get('uc\_continue\_shopping\_type', 'link') == 'button') { $form\['continue\_shopping'\] = array( '#type' => 'submit', '#value' => $cs\_text, '#submit' => array('uc\_cart\_view\_form\_submit'), ); $form\['continue\_shopping\_text'\] = array( '#type' => 'hidden', '#value' => $cs\_text, ); } } // Add the control buttons for updating and proceeding to checkout. $form\['update'\] = array( '#type' => 'submit', '#value' => t('Update cart'), '#submit' => array('uc\_cart\_view\_form\_submit'), ); return $form; } /\*\* \* Submit handler for each order in the cart \*/ function MY\_MODULE\_\_forms\_cart\_order\_submit($form, &$form\_state) { // Remove the cart order variable if the customer came here during checkout. if (isset($\_SESSION\['cart\_order'\])) { unset($\_SESSION\['cart\_order'\]); } // Update the items in the shopping cart based on the form values. uc\_cart\_update\_item\_object((object)$form\_state\['values'\]); /\* NOTE: I need to know which submit button was clicked \*/ foreach($form\_state\['values'\] as $k => $info) { /\* #name was used to hold the seller uid. I will use the seller id to filter what needs to be checked out \*/ if(is\_numeric($k)) { $form\_state\['redirect'\] = variable\_get('uc\_checkout\_enabled', TRUE) ? array('cart/checkout','seller='. $k) : 'cart'; } } } 

STEP 5:

/\*\* \* List the products in the cart in a TAPIr table. \* @see uc\_cart\_view\_table() \*/ function MY\_MODULE\_\_helpers\_view\_cart\_table($table) { $table\['#attributes'\] = array('width' => '100%'); $table\['#columns'\] = array( 'remove' => array( 'cell' => t('Remove'), 'weight' => 0, ), 'image' => array( 'cell' => t('Products'), 'weight' => 1, ), 'desc' => array( 'cell' => '', 'weight' => 2, ), 'qty' => array( 'cell' => t('Qty.'), 'weight' => 3, ), 'price' => array( 'cell' => t('Price'), 'weight' => 3, ), 'total' => array( 'cell' => t('Total'), 'weight' => 4, ), ); foreach (element\_children($table) as $i) { $subtotal += $table\[$i\]\['#total'\]; $table\[$i\]\['remove'\]\['#cell\_attributes'\] = array('align' => 'center', 'class' => 'remove'); $table\[$i\]\['image'\]\['#cell\_attributes'\] = array('class' => 'image'); $table\[$i\]\['desc'\]\['#cell\_attributes'\] = array('class' => 'desc'); $table\[$i\]\['qty'\]\['#cell\_attributes'\] = array('class' => 'qty'); $table\[$i\]\['price'\]\['#cell\_attributes'\] = array('class' => 'qty'); $table\[$i\]\['total'\]\['#cell\_attributes'\] = array('class' => 'price'); $table\[$i\]\['#attributes'\] = array('valign' => 'top'); } $context = array( 'revision' => 'themed-original', 'type' => 'amount', ); /\* format subtotal per order \*/ $subtotal\_text = t('Subtotal'); $shipping\_text = t('shipping not included'); $price = uc\_price($subtotal, $context); $approx\_text = t('approximately'); $total\_text =" $subtotal\_text ($shipping\_text):$price USD $approx\_text $price usd "; $table\[\] = array( 'total' => array( '#value' => $total\_text, '#cell\_attributes' => array( 'colspan' => 'full', 'align' => 'right', 'class' => 'subtotal', ), ), ); return $table; } 

STEP 6:

/\*\* \* @ingroup themeable \* @see uc\_cart\_view\_form() \*/ function theme\_MY\_MODULE\_view\_cart\_form($form) { drupal\_add\_css(drupal\_get\_path('module', 'uc\_cart') .'/uc\_cart.css'); $output = ''; foreach($form as $id => $info) { if(is\_numeric($id)) { $seller = drupal\_render($form\[$id\]\['seller'\]); $items = drupal\_render($form\[$id\]\['items'\]); $submit = drupal\_render($form\[$id\]\['submit\_order'\]); $seller ="

$seller

"; $items ="

$items

"; $submit ="

$submit

"; $order ="

$seller $items $submit

"; $output.= $order; /\* make sure that these elements don't get displayed again \*/ foreach(element\_children($form\[$id\]\['items'\]) as $i) { foreach (array('title', 'options', 'remove', 'image', 'qty', 'price') as $column) { $form\[$id\]\['items'\]\[$i\]\[$column\]\['#printed'\] = TRUE; } $form\[$id\]\['items'\]\[$i\]\['#printed'\] = TRUE; } } } /\* Add the continue shopping element and cart submit buttons. \*/ if (($type = variable\_get('uc\_continue\_shopping\_type', 'link')) != 'none') { /\* Render the continue shopping element into a variable. \*/ $cs\_element = drupal\_render($form\['continue\_shopping'\]); /\* Add the element with the appropriate markup based on the display type. \*/ if ($type == 'link') { $rendered\_form = drupal\_render($form); $output .="

$cs\_element$rendered\_form

"; } elseif ($type == 'button') { $rendered\_form = drupal\_render($form); $output .="

$rendered\_form$cs\_element

"; } } else { $rendered\_form = drupal\_render($form); $output .="

$rendered\_form

"; } return $output; } 

[![](https://cdn.sanity.io/images/ey850001/production/f0e6f26b2d1cd656f2bc33b49d25cf5cb8d44e93-68x68.png?w=68&auto=format) rllabore rllabore Developer · Appnovation](/people/rllabore-rllabore.md) 

## Keep reading

[![What is Sovereign AI](https://cdn.sanity.io/images/ey850001/production/a5889abdd5e80376912546622e046e3fbef234b4-2496x1664.png?w=640&auto=format)](/insights/what-is-open-source-sovereign-ai.md)

 Article · 7 min AI 

### [What Is Open Source, Sovereign AI?](/insights/what-is-open-source-sovereign-ai.md)

Every company is encouraging more AI, and almost every AI bill is climbing. Sovereign AI is about two kinds of control: over your data and over your costs. Here's why open source sits at its foundation.

[Iain MacNeil](/people/iain-macneil.md) Article 

[![AI-first experience design](https://cdn.sanity.io/images/ey850001/production/8bbc2418e87924ef10f36115478a2e10d5bed95e-1584x624.jpg?w=640&auto=format)](/insights/how-build-ai-first-experience-without-throwing-out-everything-youve-already-built.md)

 Article · 6 min AI Sep 10, 2026 

### [How to Build an AI-First Experience Without Throwing Out Everything You've Already Built](/insights/how-build-ai-first-experience-without-throwing-out-everything-youve-already-built.md)

Users now expect a direct answer, not a list of links to click through. Here's how organizations can layer an AI-First experience onto their existing website or app, in weeks rather than years.

[Andrew Douglas](/people/andrew-douglas.md) Article 

[![What Becomes Possible When AI Can Read All the Data](https://cdn.sanity.io/images/ey850001/production/c28ab0bd26753421ee42937e13adc7a7f19b47f0-1296x510.jpg?w=640&auto=format)](/insights/what-becomes-possible-when-ai-can-read-all-data.md)

 Article · 4 min AI Sep 09, 2026 

### [What Becomes Possible When AI Can Read All the Data](/insights/what-becomes-possible-when-ai-can-read-all-data.md)

Most of the world's data has never been analyzed, only sampled. AI changes that. How AI-ready data platforms unlock questions no one could ask before.

[Appnovation](/people/appnovation.md) Article 

## Get the thinking,  
*before everyone else.*

One considered email a month. New essays, field notes from live engagements, and the occasional strong opinion. No noise.
