Vendune
docs/product-management.mdView on GitHub ↗

Product management and category navigation

Vendune Studio now owns one Products workspace. The Products tab provides a server-filtered, cursor-paginated list; Categories provides the translated category tree. Select a product to edit the complete native aggregate, or choose Create product. A new product starts inactive. Set its number, name, price, stock and assignments, then save. Activation makes it available through the same Store API consumed by the storefront.

The interface supports English, German, French and Spanish. Content editors use every enabled shop content language. Change Content language to edit the corresponding name, short description, rich description, specifications and SEO fields. Missing/null fields inherit the shop main language; an explicitly empty description stays empty. Viewing a fallback never saves a fabricated translation. Existing rich descriptions remain language-specific. Changing the Studio interface language keeps the selected product, content language and unsaved draft open; switching shops or environments starts a separate scoped workspace.

Product detail workspace

  • General: name, unique shop-local product number, short description, visual rich description, manufacturer identity, manufacturer number, EAN/GTIN, digital product and free-shipping flags.
  • Prices & stock: gross base price, tax rate, stock, quantity minimum/steps/maximum, delivery days, list/regulation price, reference units and rule-bound discount tiers.
  • Media: ordered gallery, cover image, external HTTPS image URLs and actual PNG/JPEG/WebP uploads for saved products. Uploaded bytes start private and the gallery action explicitly publishes the selected digest.
  • Variants: list native child SKUs; create a child with its own number, option values, prices and stock; open its independent editor. A reviewable generator creates up to 50 Cartesian combinations per batch; existing combinations are skipped, child option edits are supported, and the server rejects concurrent duplicates.
  • Categories & channels: multiple category assignments and per-product sales-channel visibility. Existing channel-level explicit product lists can restrict this further; an empty channel-level list means the whole catalog. The persisted default storefront also respects activation and per-product channel visibility.
  • Specifications: translated specifications, generic product properties and native automation metadata including dimensions/weight.
  • SEO: translated title, description and slug metadata. This does not implement complete original Shopware SEO URL generation.
  • Cross-selling: search and select products from this tenant.
  • Attachments & downloads / Reviews: existing product-scoped asset publication, digital entitlement and review moderation capabilities inside the same workspace.

The TipTap editor writes a bounded JSON document, not arbitrary HTML. Supported formatting includes bold, italic, underline, headings, lists, links, images, video and undo/redo. Server admission validates document node types, marks, attributes, URL schemes, depth and size; the public React renderer uses explicit safe elements. Unsupported pasted HTML cannot become executable product content.

One product save atomically writes translations, priced fields, metadata, categories, channel overrides, knowledge-graph synchronization and a product.created or product.updated outbox event. Revision conflicts retain the browser draft and require a deliberate reload/review. Creation and updates share this operation with MCP. Durable flows can subscribe to the committed product events.

Categories: source-faithful bounded port

Category listings can also select products through a saved confirmed-fact query. The editor has one content-language field for a literal fact phrase, a supported node type and a minimum recorded confidence. This augments manual assignments; only confirmed current public document-backed claims qualify. Requested-language phrases use that claim language; absent phrases inherit the shop main language, while explicit empty phrases add no matches. Source archive/privacy changes remove graph membership immediately. Active products, channel restrictions and cursor pagination remain native. Definitions use the same category revision/history and selective staging release. This is a Vendune extension, not original Shopware dynamic-product-group equivalence. See connected cognition.

Reference: Shopware 6.7.14.2, commit 29535190246fcaf0b091d7bdf02dd16358c943c4, and the official category guide / product guide.

Shopware behavior Native implementation Exact scope
Parent/child category tree, position, translations categories/admin.rs; migration 026; CategoriesWorkspace.tsx Four languages, revision checks, same-tenant parent FK, ancestor/cycle validation and shop-local edit lock
Page / folder / external-link category types Category data and public navigation renderer page, folder, HTTPS link; legacy structuring remains readable
Multiple product-category associations product_categories; categories::assignments Tenant-bound many-to-many links, at most 100 assignments per product
Parent listing includes descendant assignments categories/listing.sql Active subtree, enabled by default; displayNestedProducts=false limits the listing to direct assignments
Active and visible are different categories/navigation.rs Inactive branches are inaccessible; invisible branches disappear from navigation but active category listings remain directly addressable
Sales-channel navigation root marketing/channels.rs, categories/navigation.rs, channel settings form Independent navigationCategoryId per storefront/headless channel; default root is catalog-root
Storefront navigation and category listing /store-api/navigation, /store-api/product with categoryId; CatalogNavigation.tsx Localized tree and real product filtering before pagination; no hard-coded category names
Catalog content staging staging/categories.rs, clone.rs, release.rs Category and product units can publish selectively in one transaction; dependency order, staged digest and live baseline checks; live stock stays live-owned

This is a behavioral port to native APIs and storage, not original DAL/UUID/wire-schema compatibility. Missing upstream features include product streams/dynamic product groups, complete CMS category layouts, breadcrumbs/deep category routes, service/footer navigation roots, arbitrary language/currency context, manufacturer/property-group entities, full inheritance masks, bulk import/export and deletion workflows. Automatic authenticated image previews inside private staging editors are not yet implemented; private source URLs do not become publicly accessible. Category navigation is deliberately bounded to 2,000 nodes per channel; large product catalogs remain cursor-paginated and are not loaded into the browser.

APIs and MCP

Operation HTTP MCP tool
Search/filter products GET /api/merchant/products?search=...&active=true&categoryId=...&lowStock=true&limit=25&after=... merchant.products
Read one editor aggregate GET /api/merchant/products/{id} merchant.product.content
Create POST /api/merchant/products merchant.product.create (product object)
Update PUT /api/merchant/products/{id} merchant.product.save (id, product)
List category tree GET /api/merchant/categories merchant.categories
Create/update category POST /api/merchant/categories; PUT /api/merchant/categories/{id} merchant.category.create; merchant.category.save
Public tree/listing POST /store-api/navigation; POST /store-api/product with { "categoryId": "..." } Public Store API

Reads require catalog.read; writes require catalog.write. Native product queries accept limits 1–100 (Studio uses 25), filter before limiting and return nextCursor; there is no expensive whole-catalog total. Category search/stock/status filters are server-side. Product numbers have a tenant-specific unique index; translated name/product-number search uses trigram indexes. Asset ?shop= scope allows ordinary image requests to identify their tenant without authentication headers; private environments still require authorized access. The header tenant wins if explicitly supplied.

Verification and boundaries

python3 scripts/catalog_management.py creates isolated synthetic tenants and runs actual HTTP/PostgreSQL/MCP/worker tests: creation, translations, formatting, duplicate numbers, cursor pages, active/channel admission, parent-category listings, nested listing control, hidden ancestry, cycles, tenant isolation, unsafe document rejection, uploaded byte delivery, event consumption and selective release of new products/categories. Existing commerce, account/marketing, merchant operations, protocol and scalability suites cover shared consumers. frontend/tests/unit/catalog-management.test.tsx exercises the actual list/editor, save conflicts and safe rich renderer; the browser check creates and reloads a real synthetic product.

These tests are regressions, not 100% coverage or complete Shopware equivalence. New SQL, asynchronous adapters and editor code are recorded as unproved in the formal manifest; the existing extracted Lean policy contracts remain unchanged.

New product pages without uploaded media render an explicit translated empty-image state, rather than a broken image or a fictitious product photo. Public detail and purchase views display the merchant's product number; the internal product ID continues to identify carts, associations and API routes.

Channel settings and media workspaces (2026-10-05)

See the settings/media guide for the current single-language editor, field-level checkout overrides, dependency-safe method removal, gallery and optional private image jobs. These are native prototype extensions; they do not establish additional full Shopware API/DAL parity, current tax law, paid-provider quality or whole-system formal certification.

For Markdown editing, guided variants, integration keys and channel onboarding, see the Studio integration guide.