Skip to content

Step 1 — Add products

By the end of Step 1 you can:

  1. See your products in the POS catalog before the end of the day — a cashier at the counter can scan a barcode or pick from the product grid and get the right price + stock + tax displayed.
  2. Open the full edit page for any product and confidently navigate every accordion section: Details, Taxes and fees, Categories, Item variants, Stores & inventory, Promotions, Other settings.
  3. Explain the difference between retail price and wholesale price and which channel each one applies to.
  4. Create variants (Size, Color, …) from a master attribute and understand the variant matrix that the system generates automatically.

If you only have time to read one section, jump to Section VI — The full edit page. That section is the heart of Step 1; everything else is either the entry point into that page or a sub-area that supports it.

WayWhen to useTimePersists to catalog?
Quick add from POSYou’re testing the counter — one or two products only.30 sNo (lives on the order)
Add item details modal (Clover-style, on the Items list page)You sell ~30 SKUs and want control over each.1–2 minYes (creates the catalog row)
Excel importYou already have a master list or current catalog.5–15 minYes (bulk)

Read more: Catalog & products — the main reference for the entire Items area.

The rest of this guide focuses on the Add item details modal + the full edit page that follows, because that is the path that gives you access to every catalog capability. The other two paths are convenience shortcuts for high-volume or counter-only flows.

III. Items list — your catalog home base

Section titled “III. Items list — your catalog home base”

Before you create anything, locate the catalog home base. From the left sidebar open Items → Item list — the route is VENDOR_ROUTES.items.list (/dashboard/items/list).

Items list The items list — the main dashboard for the entire catalog. The ”+ Add item” button lives in the top-right of the page header (green primary button, + icon), right next to the overflow menu (Export / Import / Print). The filter row above the table holds search, Columns, Filters, and the + Add item trigger. Status filtering (Active / Draft / Archived / In stock / Low stock / Out of stock) is in the Filters drawer.

What you can do from this page without entering the edit form:

  • Filter by status, category, brand, stock status, and price range (drawer).
  • Search by name, SKU, or barcode (debounced).
  • Bulk select rows (max 200 per batch — see Dashboard table pagination rule) and batch delete or batch change status from the selection toolbar.
  • Export to Excel (overflow menu) — downloads a dated XLSX of the current filter set.
  • Import Excel (overflow menu) — see Section XV.
  • Open the edit page by clicking a row (detail) and then the Edit button, or directly via the row’s overflow menu.

This is a counter-only flow — the product is created on the fly for a single sale and is not saved to the master catalog unless you press Save to catalog at the end.

In POS Workspace (route VENDOR_ROUTES.pointOfSale.sale, /point-of-sale/sale), click Quick add (or the + next to the cart) and fill in:

FieldRequired?Example
NameYes“Test product”
PriceYes10000
UnitYeseach / kg / pack
(optional) CategoryNounclassified
(optional) BarcodeNoSKU-001

The product only exists for that sale. It is not added to the catalog unless you press Save to catalog at the end of the order.

Use this when you are stress-testing the counter before the real product master is ready. It is also a fast way to test what an unknown barcode would do at checkout.

V. Add item details modal — the Clover-style fast create

Section titled “V. Add item details modal — the Clover-style fast create”

Click + Add item in the top-right of the Items list header. The system opens the “Add item details” modal (AddItemDetailsModal) — a full-screen Clover-style dialog. It collects the minimum data needed to create a product, then navigates you straight into the full edit page so you can keep filling in the rest.

Add item details modal The “Add item details” modal — required fields are Name and Price. Toggle “Add items with variants” to seed up to N variant axes from a master attribute. Click “Next” to create the product and open the full edit page.

FieldTypeRequiredNotes
NametextYes1–127 characters. This becomes the product’s display name at the counter and on the online menu.
PricenumberYesPositive decimal. Maps to the retail price column. The Wholesale price is not set here — set it later in the edit page.
Add items with variantscheckboxNoWhen checked, the modal reveals a per-row picker (master attribute + tag-style values editor).
(per row) Select attributedropdownRequired when variants are enabledLists all master attributes defined in Settings → Attributes. Already-picked attributes are filtered out of sibling rows so each row maps to a unique axis.
(per row) Option valuestag inputRequired when variants are enabledPre-fills from the attribute’s preset values (Size → Small / Medium / Large). Press Enter to add a free-text tag.

Important: the modal does not let you set unit, category, taxes, supplier, or stock at this stage. The first release is intentionally tiny — it gets you into the full edit page as quickly as possible. Everything else (taxes, variants matrix, stores, etc.) is filled out on the full edit page after the row exists.

Enabling “Add items with variants” auto-seeds the first row with the first master attribute and its preset values. To add a second axis (e.g. Size + Color):

  1. Click + Add another — a new row appears with an empty attribute select.
  2. Pick the second attribute. The picker excludes any attribute already used by another row.
  3. Either accept the preset values or type new ones in the tag input (press Enter to commit each tag).

The matrix is not materialised on this screen — the modal only records the axes (attributes + choice_options). The full variant rows (one per option combination) are generated by the edit page when you save — see Section X.6.

If you do not have any master attributes yet, the modal shows a “No attributes yet” empty state with a Create attribute button that opens the Attribute modal without leaving the flow.

Clicking Next runs createVendorProduct({ name, price, attributes, choice_options }):

  1. The product is persisted with status = 1 (active) and type = RETAIL by default.
  2. The list cache is invalidated so the new row shows up.
  3. The router navigates to vendorCatalogItemEditPath(created.id) — i.e. /dashboard/items/:id/edit — the full edit page.
  4. A success toast confirms the creation.

From here on, you are on the full edit page, and the next sections walk you through every accordion.

VI. The full edit page (the heart of Step 1)

Section titled “VI. The full edit page (the heart of Step 1)”

The full edit page is rendered by apps/vendor-web/src/app/dashboard/items/product-form-page.tsx and is mounted at /dashboard/items/:id/edit (helper: vendorCatalogItemEditPath(id)).

Edit product page The full edit page — the catalog workhorse. It is one long form split into accordion sections. By default every section is open, so the whole card hierarchy scrolls into view. The sticky footer holds Cancel and Save changes; the page header holds a back button, breadcrumb, Duplicate, and an overflow menu with Delete.

From top to bottom:

  1. Page header — back button to the items list, the product name (live bound to the form), and an overflow menu with Duplicate and Delete. Delete opens DeleteProductModal; Duplicate opens DuplicateProductModal which clones the row with " (copy)" suffixed to the name and re-mints SKU/barcode where applicable.
  2. Status banner (only when the product is archived) — a yellow callout that the item is hidden from the counter and from the online menu. You can restore it from the Status toggle in the Details section.
  3. Accordion sections (in order):
    • Details — open by default
    • Taxes and fees — open by default
    • Categories — open by default
    • Item variants — open by default
    • Stores & inventory — open by default
    • Promotions & discounts — open by default
    • Other settings — open by default (contains conversion units + suppliers)
  4. Sticky footer — Cancel (returns to the detail or list page) and Save changes (Button with type="submit"). The footer is border-t on the body so it always sits below the last accordion.

Each section is rendered by a dedicated component under apps/vendor-web/src/components/dashboard/products/, all wired into the same React Hook Form context. That means a field you change in one section is immediately visible in another (e.g. a new variant in the Item variants section is reflected in the Stores & inventory matrix the next time you scroll down).

  • The save button is the only place that actually calls the API (updateVendorProduct). Every accordion is a local edit until you press save.
  • Cancel returns to the detail page if the product exists (vendorCatalogItemDetailPath(id)) or to the items list if you opened the page from “create” without saving.
  • A validation error summary appears above the save button when at least one Zod check fails — the offending fields also highlight in red.
  • The page is wired to the shared useForm + zodResolver schema (product-form.schema.ts); required fields are marked with a red * in the label.

The first accordion. It mirrors the Clover item.html field order and is implemented by ProductFormDetailsSection.

Details accordion The Details accordion — Pin on POS, Name, Alternative name, Price, Price type, Wholesale, Cost, Unit, then the Online ordering block (image + description).

FieldWhat it does
Pin on POS toggle (is_pos_pinned)When on, the product is treated as a “featured” SKU and is prioritised on the cashier’s quick-pick row in the POS workspace. When off, the product still appears in the catalog but not on the featured row.

“Show on POS” was renamed to Pin on POS in a recent refactor — they map to the same is_pos_pinned backend column.

FieldRequiredNotes
NameYesThe display name on the receipt, the POS grid, and the online menu. Up to 127 characters.
Alternative nameNoFallback display used in some receipts and reports when the primary name is missing or too long to fit. Does not affect search or filtering.

VII.3 Price & Price type — Fixed / Variable / Per unit

Section titled “VII.3 Price & Price type — Fixed / Variable / Per unit”
FieldRequiredNotes
Price (price)YesRetail price in the store’s currency. Display uses the store currency symbol from VENDOR_CATALOG_CURRENCY_CODE.
Price type (price_type)NoOne of Fixed (default), Variable, Per unit.

Behaviour per price type:

  • Fixed — the cashier sees a single price; tapping the line on the POS grid adds the item at that price. Used for >95% of products.
  • Variable — the cashier is prompted to enter a price at checkout (donations, custom services). The Price field becomes a suggested price.
  • Per unit — the cashier is prompted to enter a quantity at checkout (weighed produce, fabric cut to length). The Unit field becomes required at the form level — see Section VII.6.

A separate field that lives next to Price. The wholesale price is only applied when the product is sold through the Wholesale channel — i.e. orders.type = WHOLESALE. In the Retail channel (default POS and online menu), the regular price is used.

The product can be sold in both channels without duplicating rows. The form’s “Catalog types” field controls this — see Section XIV.

If you only sell retail, leave Wholesale empty. If you sell wholesale and want a different price list per channel, the system also supports per-channel Price lists at /dashboard/items/price-lists (more advanced — outside the scope of Step 1).

FieldRequiredNotes
Cost (cost)NoThe cost-of-goods-sold (COGS) figure used for gross profit margin in finance reports.
Non-revenue item toggleNoWhen on, the product is excluded from revenue totals in reports (e.g. a free sample given with every order, or a container deposit).

RBAC: the cost field is gated by vendorCanViewProductCost. If your role does not have the cost-view permission, the entire Cost block is hidden, and the Taxes and fees section shows a “Tax fields respect your cost-view permission” hint.

FieldRequiredNotes
Unit (item_unit_id)Required when price_type = Per unit, otherwise optionalMaps to the master units table. Use it for kg, m, hour, pack, etc. The unit select shows the units defined under VENDOR_ROUTES.items.units.

A small + button next to the unit field opens the Unit modal so you can define a new unit without leaving the form.

VII.7 Online ordering block — image & description

Section titled “VII.7 Online ordering block — image & description”

The bottom half of the Details accordion is the Online ordering block, mirroring the Clover layout.

FieldRequiredNotes
Item imageNoCover image. Picked from the vendor media gallery (VendorGalleryImagePickerModal); supports a single cover and a multi-image album.
DescriptionNoRich-text editor (ProductFormRichTextEditor) — supports formatting, links, and lists. Sanitised through sanitizeRichHtml before save. Displayed on the online menu and on receipts (configurable).

Removed per recent refactor (so this guide matches the current UI): Item color, Age-restricted, and Online name. Their columns may still exist in the database for backward compatibility, but they are no longer rendered.

Implemented by ProductFormTaxesAndFeesSection. One accordion, three concepts.

Taxes and fees accordion The Taxes and fees accordion. Empty state on the left; a configured product on the right with a custom inline tax and a link to store tax settings. The “Tax exempt” toggle is the first row.

The data model supports two independent tax inputs on every product:

  1. A store tax profile (store_tax_id) — a reusable tax rule defined under Settings → Taxes and fees. The store tax knows its own rate and type. See Section XVI for where to manage these.
  2. An inline custom tax (tax_type + tax_value) — a per-item override used when the product needs a special rate that does not belong in the global store tax table.

At checkout, the engine resolves the effective tax for the product as follows:

  • If is_tax_exempt = true → no tax is applied (and the section renders a “Tax exempt — no tax applied” note).
  • Else if store_tax_id is set → that profile’s rate is used.
  • Else if tax_value > 0 → the inline tax_value (with tax_type = percent or amount) is used.
  • Else → no tax is applied.

The accordion always shows what will actually be applied. That’s why the body is a tiny table — not a settings panel.

Click Assign taxes and fees to open AssignTaxesAndFeesDrawer:

  • The drawer lists every store tax defined under VENDOR_ROUTES.items.taxes (/dashboard/items/taxes).
  • Selecting a profile writes store_tax_id to the form; the inline custom tax fields are not cleared, but the resolved row is the profile.
  • A “Go to Settings” link inside the drawer jumps to the store tax manager so you can create a new profile if the one you need does not exist.

Use this when you need a one-off tax for a single product. The drawer exposes:

  • Tax type — Percent (e.g. 5 = 5%) or Fixed amount (e.g. 1000 = 1000₫ flat).
  • Tax value — the number. Validation rejects negative numbers.
  • Add — writes the inline tax to the form; the table now shows the custom row.

Anti-pattern: use the store tax profile for anything that affects more than one product. The inline field is intentionally limited to one tax per product — there is no “stack” of inline taxes.

A single Tax exempt toggle in the body. When on:

  • The resolved tax row in the table is replaced by the “Tax exempt” note.
  • The store tax and inline custom tax fields are not cleared (they re-apply if you toggle exempt off).

Implemented by ProductFormCategoriesSection. Mirrors the same pattern as the other assignment accordions (Taxes, Modifier groups, Stores).

Categories accordion The Categories accordion. Each assigned category has a position number (#1 is the primary category).

Categories are used for three things in the rest of the system:

  1. POS filter — the cashier can narrow the product grid by category.
  2. Online menu grouping — the menu page groups products by primary category.
  3. Sales reports — revenue and units sold are rolled up by category in the Sales Report.

The first category in the list is the primary category. It is the one shown in POS filters, used in the online menu grouping, and reported on in the Sales Report category breakdown.

The list preserves the order you select. To reorder, remove and re-select in the desired order — or use the Edit action in the selection toolbar.

Categories are managed under VENDOR_ROUTES.items.categories (/dashboard/items/categories).

X. Section: Item variants — the deep dive

Section titled “X. Section: Item variants — the deep dive”

This is the most important accordion for any store that sells “the same product in different sizes / colours / capacities” — e.g. a t-shirt in S/M/L/XL, a beverage in 330 ml / 500 ml / 1 L. It is implemented by ProductFormItemVariantsSection and is the most complex section in the form.

Item variants accordion The Item variants accordion — Attributes subsection (top) and Variants subsection (bottom). The empty state shown is what you get on a brand-new product with no attributes yet.

X.1 Attributes vs Variants — the mental model

Section titled “X.1 Attributes vs Variants — the mental model”

This is the concept that catches new users the most often, so it is worth a clear diagram.

Master attribute → "Size" (id: ATTR_SIZE)
Preset values → ["Small", "Medium", "Large"]
Choice options on item → choice_options: [
{ id: ATTR_SIZE, name: "Size",
values: ["Small", "Medium", "Large"] }
]
Generated variant matrix → 3 rows:
- Small
- Medium
- Large

If you add a second attribute — e.g. Color with ["Red", "Blue"] — the matrix becomes the cartesian product: 3 × 2 = 6 variants (Small/Red, Small/Blue, Medium/Red, …). Every combination is its own sellable row with its own SKU, barcode, price, and stock.

The top half of the accordion. Lists the attributes currently bound to this product.

ColumnMeaning
AttributeHuman-readable name (e.g. “Size”). Falls back to the attribute id if the lookup has not yet hydrated.
OptionsComma-separated list of values that are active for this item. Click Edit in the row to open the inline option editor (add/remove values; changes feed into choice_options).

Toolbar actions:

  • + Add attribute (top right, green) — opens AssignAttributesDrawer. The drawer lists every master attribute defined under VENDOR_ROUTES.items.attributes and lets you multi-select.
  • Edit (selection toolbar) — re-opens the drawer in “edit” mode so you can add/remove attributes.
  • Remove (selection toolbar) — drops the selected attributes. Dropping an attribute regenerates the variant matrix — see Section X.6.

The bottom half of the accordion. Lists the materialised variant rows (one per combination of the active attributes’ values).

ColumnEditable in cell?Notes
NameYes (drawer)Display name; defaults to the combination of the option values (e.g. “Red / Large”).
BarcodeYes (drawer)Scannable barcode. Leave blank to auto-generate EAN-13 on save.
CostYes (drawer)Per-variant COGS. Falls back to the product-level cost if blank.
PriceYes (drawer)Per-variant retail price. Falls back to the product-level price if blank.
WholesaleYes (drawer)Per-variant wholesale price. Falls back to the product-level wholesale price if blank.
In stockYes (drawer)The on-hand count for this variant at the default warehouse. See Section XI.3 for how this interacts with the per-warehouse matrix.

Search (“Search items”) and pagination (25 / 50 / 100 per page) live above the table.

X.4 Edit variant drawer — what every cell means

Section titled “X.4 Edit variant drawer — what every cell means”

Click any variant row (or select rows + Edit) to open EditVariantDrawer. The drawer exposes every per-variant field that overrides the product-level defaults:

FieldMaps toNotes
Variant namevariation.nameDefaults to the option combination. Editable.
SKUvariation.skuStock-keeping unit. Used for inventory and supplier catalogs.
PLUvariation.pluPrice-look-up code (used by some scales and POS peripherals).
Barcodevariation.barcodeScannable. Use Random to auto-generate an EAN-13 (passes the Luhn checksum).
Costvariation.costPer-variant COGS.
Pricevariation.pricePer-variant retail price.
Wholesalevariation.wholesale_pricePer-variant wholesale price.
Stockvariation.stockQuick-edit for the default warehouse only.
Low stock thresholdvariation.low_stock_thresholdPer-variant override. Falls back to the product-level value.
Maximum cart quantityvariation.max_cart_qtyPer-variant cap. Falls back to the product-level value.
Availablevariation.is_availableWhen off, the variant is hidden from POS and online without disabling the parent item.

Click Save in the drawer to write the per-variant overrides. The variant row in the accordion reflects the new values immediately.

  • Per row — the trash icon in the row opens DeleteVariantConfirmModal.
  • Batch — select multiple rows + Delete in the selection toolbar. The batch modal asks “Delete {{count}} variants?”.

Deleting a variant is destructive — it removes the variant’s SKU, barcode, per-warehouse stock, and any history. Use the Available = off toggle in the drawer for “soft delete” if you might need the variant back.

The matrix is derived from attributes + choice_options on the product. The generation happens at three moments:

  1. On save (server side) — the backend expands the cross product of every active attribute’s values into variations. Variant rows that were previously persisted but no longer appear in the matrix (because an attribute was removed or a value was deleted) are dropped; new combinations are inserted with the per-variant fields left null (so they inherit from the product).
  2. On attribute add/remove in the editor — when you click Add attribute or Remove in the attribute toolbar, the form calls a helper that recomputes variations locally so the Variants table updates before you save. This is the “live preview” experience.
  3. On attribute options change — the inline option editor in the Attributes subsection updates choice_options without immediately re-running the matrix (to avoid an expensive deep re-render). The matrix is rebuilt on save or when the user clicks Build variants if it is exposed.

The shared helper that does the actual cartesian expansion is buildCatalogVariantBlueprints + buildUnitAxisSegments in packages/shared. Every variant row produced is also given:

  • A stable key derived from the option combination (normalizeCatalogVariantKeyForForm) so React renders are stable across reorders.
  • A PLU if the product uses a custom EAN-13 (per-unit scan flow) — see pos-per-unit-barcode-scan.util.ts.

For stores that need to apply the same percentage discount to every variant at once (e.g. seasonal sale), the Other settings section exposes a bulk-price tool (applyBulkPricesToCatalogVariations). It accepts a single patch and writes it to every variant in one save. See Section XIII for the entry point.

Implemented by ProductFormStoresAndInventorySection. The accordion is split into two halves: store assignment at the top, per-warehouse stock matrix at the bottom.

Stores & inventory accordion The Stores & inventory accordion. The top half lists the stores the product is assigned to; the bottom half is the per-warehouse stock matrix for the currently selected store tab.

ActionWhat it does
+ Assign storesOpens a drawer listing every store in your tenant. Multi-select the stores where this product should exist.
Edit (selection toolbar)Re-opens the drawer so you can add/remove stores.
Remove (selection toolbar)Drops the selected stores. Existing per-warehouse stock for the dropped store is preserved (not deleted) so re-adding the store later keeps the history.

If the product was created via the Add item details modal, it is automatically assigned to the first active store in the tenant. You can override that here.

The bottom half of the accordion is a per-store tab strip + a per-warehouse stock table:

  1. Store tabs — one tab per store assigned to the product. Click a tab to switch the warehouse matrix below.
  2. Warehouse rows — one row per warehouse linked to the selected store. Each row has:
    • Warehouse name (read-only)
    • Stock qty (editable) — on-hand units at that warehouse
    • Low stock threshold (editable) — alert threshold for low-stock notifications
  3. Total — the sum of every warehouse’s stock for the current store, shown in the table footer

RBAC: the Total line respects cost-view permission just like the Cost field in the Details section. A user without cost-view sees the same numbers but in a more compact summary.

There are two stock modes, and the form picks one based on whether the product has variants:

  • Product-level (no variants) — every warehouse row maps directly to the product. The accordion shows one editable stock qty cell per warehouse.
  • Variant-level (with variants) — every warehouse row is split per variant. The accordion shows a matrix with one column per variant and one row per warehouse; each cell is the on-hand count for that variant at that warehouse. The header of the matrix lists every variant name; the row footer sums per warehouse.

If you are switching an existing product from “no variants” to “with variants”, the existing per-warehouse stock is migrated to the default variant (the first one in the matrix) — review and redistribute manually if needed.

Implemented by ProductFormPromotionsSection. The accordion exposes three independent promotion kinds — all optional, all additive.

Promotion kindWhat it doesEmpty state
Line discountA fixed % or amount off the unit price at checkout (e.g. 10% off always, or 5 000₫ off on Fridays).“No line discount configured”
Maximum cart quantityA hard cap on how many units a single cart may contain. Useful for “limit 2 per customer” rules.“No purchase limit”
Quantity & bundle promotionsBuy 2 get 1 free; 3 for the price of 2; buy 2 of X, get 50% off Y; etc. Multiple promotions can stack.“No quantity promotions configured”

Click Edit promotions to open the Promotions drawer. The drawer is a guided form — one tab per promotion kind — and the active count is shown in a green badge in the accordion header.

Promotions configured here are per product. Store-wide promotions (e.g. “10% off everything in August”) are managed under the Marketing area — see /dashboard/discounts.

XIII. Section: Other settings — conversion units & suppliers

Section titled “XIII. Section: Other settings — conversion units & suppliers”

Implemented by ProductFormOtherSubsectionHeading + the conversion units and suppliers sub-components. This is the section that holds concepts that don’t fit the Clover reference layout.

Other settings accordion The Other settings accordion. Top: conversion units (pack → each). Bottom: suppliers.

Many products are sold in multiple units at the same time. A bag of rice might be sold per bag to retail customers and per kg to wholesale customers. The conversion unit table lets you declare those relationships.

ColumnMeaning
UnitThe secondary unit (e.g. “Bag”).
Units per packHow many base units fit in one pack (e.g. 1 bag = 50 kg).
POS defaultWhether this unit is the default in the Wholesale workspace (and the secondary default in Retail).
Import defaultWhether this unit is the default for receiving stock from a purchase lot.
UnpackWhether the cashier is allowed to break a pack open at the counter (sell 1 kg from a 50 kg bag).

To add a conversion row, click + Add unit next to the section heading and pick a unit from the list. The form pre-fills units per pack = 1; adjust as needed. Conversion rows are stored as additional variations on the product with a special kind = conversion flag — they participate in stock counts but do not show up in the regular Item variants accordion.

Anti-pattern: do not use conversion units to represent true variants (Size / Color). The two systems are independent — conversion units are about quantity, variants are about identity.

XIII.2 Suppliers & supplier catalog numbers

Section titled “XIII.2 Suppliers & supplier catalog numbers”

The bottom half of the Other settings accordion. Each row links a supplier to this product and records the supplier catalog number — the SKU the supplier uses internally. This is essential for:

  • Re-ordering (the purchase order uses the supplier catalog number).
  • Cross-referencing receipts from the supplier back to your catalog.
  • Tracking cost-per-supplier when the same product has multiple suppliers.

Click + Add supplier to pick from the supplier list. The drawer shows each supplier’s name and (if set) their contact info.

Suppliers are managed under VENDOR_ROUTES.items.suppliers (/dashboard/items/suppliers). To enable the Smart import workflow (auto-create products from a supplier’s catalog file), ask the supplier to upload their catalog at /dashboard/items/suppliers/smart-import.

The product form exposes a single type (Catalog type) picker that controls which channels the product is sold on:

Catalog typeWhere it showsPrice used
RETAIL (default)POS workspace (/point-of-sale/sale), online menuprice (retail)
WHOLESALEWholesale POS workspace (/dashboard/orders/create-wholesale), B2B portalwholesale_price

The form lets you enable both types on the same product — in that case the same product row appears in both the retail and wholesale workspaces, and the right price is picked automatically based on the channel.

Default for new products is [RETAIL] only (see VENDOR_PRODUCT_DEFAULT_TYPES in packages/shared). If you sell wholesale, remember to also enable the WHOLESALE catalog type on every product you want to expose to that channel — the system does not auto-mirror.

If you already have a product list (CSV/Excel), use the import template:

  1. Items → Item list → Import menu (top-right) → download the Excel template.
  2. Fill in the columns: name | sku | barcode | category | unit | price | stock | status.
  3. Upload → preview the rows → confirm.

Limit: the API accepts up to 200 rows per upload. Split large files into 200-row chunks. The vendor web enforces the same cap on the batch operations toolbar (see Dashboard table pagination rule).

The endpoint is POST /api/v1/vendor/products/import-excel — see API catalog → “Vendor products”.

The Items group in the sidebar has more than just the list. Each sub-area is a Settings-style page that the product form links into.

Product categories Categories — the master list of categories you can assign to products.

Units Units of measure (each, kg, pack…).

Suppliers Suppliers — used when stock-in arrives in Step 2.

Sub-areaRouteWhat it does
Categories/dashboard/items/categoriesMaster list of categories (drag-reorder, primary flag).
Modifier groups/dashboard/items/modifier-groupsAdd-on / option groups (e.g. “Toppings” with Pepperoni / Mushroom).
Units/dashboard/items/unitsMaster list of units. The product form’s Unit picker reads from here.
Taxes and fees/dashboard/items/taxesReusable store tax profiles. The product form’s Taxes and fees accordion reads from here.
Attributes/dashboard/items/attributesMaster list of attributes (Size, Color, …) used to generate variants.
Tags/dashboard/items/tagsFree-form tags for filtering and reporting.
Brands/dashboard/items/brandsBrand list. Used in the Products filter drawer.
Pricing rules/dashboard/items/pricing-rulesRule-based bulk price changes (e.g. 10% off everything in a category).
Price lists/dashboard/items/price-listsMulti-tier price lists per channel.
Price approvals/dashboard/items/price-approvalsReview queue for price changes that exceed approval thresholds.
Addons/dashboard/items/addonsPOS add-on catalog.
Gallery/dashboard/items/galleryVendor media gallery — pick images for product covers and albums.
Suppliers/dashboard/items/suppliersSupplier master data (name, contact, payment terms, catalog files).
Suppliers smart import/dashboard/items/suppliers/smart-importBulk-create products from a supplier’s catalog file.
  • At least one product shows in the POS catalog.
  • Sale price matches what you entered.
  • Barcode (if any) scans correctly at the counter.
  • Stock is greater than 0 (or Track stock is off).
  • Product is in Active status (not Draft).
  • You opened the full edit page and recognised every accordion section.
  • (If you sell wholesale) the Wholesale catalog type is enabled and the Wholesale price is set.
  • (If you sell variants) at least one attribute is bound and the variant matrix is generated and reviewed.
  • (If you sell in multiple stores) the product is assigned to every relevant store, and per-warehouse stock is set.

→ Step 2 — Stock in (Lots) to put real stock on the shelf: step 2.