PHPackages                             markup-carve/shopware-carve - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Templating &amp; Views](/categories/templating)
4. /
5. markup-carve/shopware-carve

ActiveShopware-platform-plugin[Templating &amp; Views](/categories/templating)

markup-carve/shopware-carve
===========================

Render Carve markup to safe HTML in Shopware: Twig filters, CMS element, product/category fields, live preview, mail, inline product references.

0.1.1(2w ago)02[1 issues](https://github.com/markup-carve/shopware-carve/issues)MITPHPPHP ^8.2CI passing

Since Jul 14Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/markup-carve/shopware-carve)[ Packagist](https://packagist.org/packages/markup-carve/shopware-carve)[ Docs](https://github.com/markup-carve/shopware-carve)[ RSS](/packages/markup-carve-shopware-carve/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (10)Versions (3)Used By (0)

shopware-carve
==============

[](#shopware-carve)

[![Packagist Version](https://camo.githubusercontent.com/1ff6b0af23926b8a07b18e454d2bd7541e0ea3e668e1923ce5cca7fb4af07278/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d61726b75702d63617276652f73686f70776172652d63617276652e737667)](https://packagist.org/packages/markup-carve/shopware-carve)[![CI](https://github.com/markup-carve/shopware-carve/actions/workflows/ci.yml/badge.svg)](https://github.com/markup-carve/shopware-carve/actions/workflows/ci.yml)[![PHP Version](https://camo.githubusercontent.com/2b5ecfa2ab947adfedcd2916f8e76fa0532da21b6ce89ce53316742f4fc9f895/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6d61726b75702d63617276652f73686f70776172652d63617276652e737667)](https://packagist.org/packages/markup-carve/shopware-carve)[![Total Downloads](https://camo.githubusercontent.com/ba44ddc31219768c656ff4386f72d50d3dbfd9f70cb486b6dd9dc44b95b2244e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d61726b75702d63617276652f73686f70776172652d63617276652e737667)](https://packagist.org/packages/markup-carve/shopware-carve)[![License](https://camo.githubusercontent.com/d06bb8af1e2af5bfbaf541a7a35d35eff2a809387ceccc9c23f0ea184394cb18/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6d61726b75702d63617276652f73686f70776172652d63617276652e737667)](LICENSE)

Render [Carve](https://github.com/markup-carve/carve) markup to safe, semantic HTML in Shopware 6. One source - ten surfaces: Twig filters, CMS elements, product/category/manufacturer fields, admin live preview, transactional mail, inline product references, product reviews, and a CLI renderer.

**Safe by default.** Raw HTML passthrough is off. `javascript:`, `data:`, `vbscript:`, `file:` URL schemes are neutralized. `on*`, `srcdoc`, and `formaction` attributes are stripped. These protections are always-on baselines independent of any plugin setting. No separate sanitizer needed. The `|carve`filter is `is_safe => html` because carve-php's URL/attribute hardening is unconditional. For the full threat model, a Carve-vs-Markdown comparison with examples, and the honest limits, see [`docs/security.md`](docs/security.md).

- Composer: `markup-carve/shopware-carve`
- License: MIT
- Shopware: 6.6 and 6.7
- PHP: ^8.2
- Namespace: `MarkupCarve\Shopware\`

> **Pre-1.0 caveat.** Syntax and output format can still change before 1.0. Pin versions explicitly and review the carve-php changelog before upgrades.

---

Gallery
-------

[](#gallery)

One plain-text source, rendered safely across the shop. The product description (left) and the admin authoring view with live preview (right) come from the same Carve source:

Storefront product descriptionAdmin live preview[![Product carve_body](docs/img/03-product-carve-body.png)](docs/img/03-product-carve-body.png)[![Admin live preview](docs/img/05-admin-live-preview.png)](docs/img/05-admin-live-preview.png)Untrusted review text is hardened automatically - bold/italic/links survive, while headings, images, and raw `` degrade to inert text:

[![UGC review hardened by the comment profile](docs/img/09-review-ugc.png)](docs/img/09-review-ugc.png)

See **[`GALLERY.md`](GALLERY.md)** for all surfaces (CMS element, category and manufacturer copy, transactional mail, inline product references, and the CLI renderer) with the Carve source behind each screenshot.

---

Enabled extensions
------------------

[](#enabled-extensions)

The following carve-php extensions are registered unconditionally on every HTML converter (both `CarveRenderer` and `CarveContextRenderer`). They are pure-PHP and require no extra JavaScript.

ExtensionWhat it does`AdmonitionExtension`Converts `::: note`, `::: tip`, `::: warning`, `::: danger`, `::: info`, `::: success` divs to `` with a `` header and an appropriate ARIA role.`DetailsExtension`Converts `::: details "Title"` to a native `Title...` disclosure widget.`ListTableExtension`Converts `::: list-table` blocks (nested lists) to real `` markup with ``/``/``/`` and rowspan/colspan support.`InlineFootnotesExtension`Allows inline footnote syntax `[content]{.fn}` to generate numbered footnote references and an end-of-document footnotes section, sharing the numbering sequence with regular footnotes.`AutolinkExtension`Detects bare `https://`, `http://`, and `mailto:` URLs in text and turns them into clickable `` links.`ExternalLinksExtension`Adds `rel="nofollow noopener"` and `target="_blank"` to all external HTTP/HTTPS links, including those produced by `AutolinkExtension`.`TableOfContentsExtension`Collects headings and makes a `` available via `getTocHtml()` (or auto-inserts at `position: 'top'`/`'bottom'` when configured). Not auto-inserted by default - use `position` option or call `getTocHtml()` manually.`SpoilerExtension`Block `::: spoiler "Title"` becomes a native `Title...` (collapsed by default - no JS). Inline `:spoiler[text]` becomes `text` (CSS blur-until-hover, no JS).`CodeGroupExtension`Converts `::: code-group` with labeled fenced blocks (e.g. ````bash [npm]`) to a tabbed group with `` / `npm` / `` markup. Tab switching is CSS-only (radio hack) - no JavaScript.`TabsExtension`Converts `:::: tabs` / `::: tab "Title"` blocks to the same CSS radio-tab pattern with classes `tabs-radio` / `tabs-label` / `tabs-panel`. No JavaScript.Config-driven extensions (added only when enabled in the plugin settings): smart quotes (`ShopwareCarve.config.smartQuotes`), Mermaid diagrams (`ShopwareCarve.config.enableMermaid`), charts (`ShopwareCarve.config.enableCharts`) and PlantUML diagrams (`ShopwareCarve.config.enablePlantuml`). Mermaid/chart lazy-load their library from a CDN, and PlantUML renders via the external Kroki service - both only when a diagram is present. See the Configuration section.

---

Surfaces
--------

[](#surfaces)

### 1 - Twig filters `|carve`, `|carve_text`, `|carve_md`

[](#1---twig-filters-carve-carve_text-carve_md)

**Benefit:** Render Carve to safe HTML, plain text, or Markdown from any theme template. The universal primitive everything else builds on - safe output with no bolt-on sanitizer; plain and Markdown variants enable channel reuse (mail, export).

```
{# HTML output (safe, is_safe => html) #}
{{ product.translated.description | carve }}

{# Plain text (e.g. for meta descriptions) #}
{{ product.translated.description | carve_text }}

{# Markdown (e.g. for export) #}
{{ product.translated.description | carve_md }}
```

For content that contains `:product[SKU]` inline references, use `|carve_ctx(context)` to pass the sales channel context so product links resolve correctly (see Surface 8).

---

### 2 - Carve CMS element (shopping experiences)

[](#2---carve-cms-element-shopping-experiences)

**Benefit:** Drag and drop a safe rich-text block into any CMS page or product layout via the admin. Non-developers author headings, bold text, tables, and admonitions with zero XSS surface and no code execution.

Add the element type `carve` from the element panel in the Shopping Experiences editor. The element renders its `content` field through `CarveRenderer::toHtml()` server-side. The admin config panel shows a live preview (Surface 6).

---

### 3 - Product custom field `carve_body`

[](#3---product-custom-field-carve_body)

**Benefit:** Structured, diffable, translator-friendly product copy rendered under the product description on the storefront. Source is plain text (versionable in git); identical in admin preview and storefront.

After running migrations (see Install), a `carve_body` text area appears on the product detail admin page. The plugin's storefront override renders it below the core description:

```
{# storefront/page/product-detail/description.html.twig - rendered automatically #}
{{ product.customFields.carve_body | carve }}
```

---

### 4 - Category custom field `carve_category_body`

[](#4---category-custom-field-carve_category_body)

**Benefit:** Safe rich text for category landing copy - same safety and determinism as product fields but on category pages.

The plugin adds a `carve_category_body` field to category entities (via migration) and renders it in the category CMS listing template automatically.

---

### 5 - Manufacturer/brand custom field `carve_manufacturer_body`

[](#5---manufacturerbrand-custom-field-carve_manufacturer_body)

**Benefit:** Authored brand copy on the manufacturer entity - same Carve safety and admin UX as the product and category fields.

The migration adds a `carve_manufacturer_body` text area to `product_manufacturer` entities in the `carve` custom field set. It appears in the manufacturer admin form after running migrations.

Shopware core has no dedicated storefront manufacturer page and no stable override block that renders manufacturer copy as rich text. The field is therefore **filter-only**: theme developers render it at whatever point the brand is shown in their layout:

```
{# In any storefront template that has the manufacturer in scope #}
{% if product.manufacturer.translated.customFields.carve_manufacturer_body %}

        {{ product.manufacturer.translated.customFields.carve_manufacturer_body|carve }}

{% endif %}
```

For product references inside the manufacturer copy, use `|carve_ctx(context)` instead of `|carve`.

---

### 6 - Admin live preview (carve-js)

[](#6---admin-live-preview-carve-js)

**Benefit:** While typing in the CMS element or custom fields, the preview updates instantly and is byte-identical to the storefront output. WYSIWYG confidence via PHP/JS parity with no API roundtrip.

The admin CMS element config component (`sw-cms-el-config-carve`) imports `carveToHtml` from `@markup-carve/carve` and calls it on every `input` event. The shared cross-implementation test corpus guarantees that carve-js and carve-php produce the same bytes for the same source.

---

### 7 - Transactional mail rendering

[](#7---transactional-mail-rendering)

**Benefit:** One Carve source feeds both the HTML part and the plain-text part of a multipart mail. Safe interpolation of user/order data into mail bodies.

See [`docs/mail.md`](docs/mail.md) for the full setup. Short example:

```
{# HTML part of a mail template #}
{% set body %}
## Order {{ order.orderNumber }}

Dear **{{ order.orderCustomer.firstName }}**,

your order is on its way.
{% endset %}
{{ body | carve }}

{# Plain-text part of the same mail template #}
{{ body | carve_text }}
```

---

### 8 - Commerce inline type `:product[SKU]`

[](#8---commerce-inline-type-productsku)

**Benefit:** Authors embed a live product reference (link with name and price) inline in any Carve content, resolved against the current sales channel at render time. Markdown has no safe, first-class way to embed live commerce entities in authored copy.

Use the `|carve_ctx(context)` filter (available in storefront templates as `context`) rather than the plain `|carve` filter when the content may contain product references:

```
{{ product.customFields.carve_body | carve_ctx(context) }}
```

Unknown or out-of-stock SKUs degrade gracefully to inert text - no exceptions thrown.

---

### 9 - Product reviews (UGC)

[](#9---product-reviews-ugc)

**Benefit:** Shopware lets customers write review text as plain text; with this surface, review bodies are rendered as rich Carve HTML using a hardened profile so customers can use basic formatting (bold, italic, links, lists) without introducing XSS or structural noise (no headings, no images, no raw HTML).

Enable via `ShopwareCarve.config.renderReviews` (default `false`). When enabled, the plugin's storefront override of `component/review/review-item.html.twig` passes `review.content` through the `|carve_ugc` filter instead of escaping it as plain text. When disabled, the template falls back to `{{ review.content|e }}` - the same safe output as unmodified Shopware.

```
{# Rendered automatically when renderReviews is on - no manual change needed #}
{{ review.content|carve_ugc }}
```

The `|carve_ugc` filter always uses:

- **Safe mode on** - raw HTML is never passed through, regardless of the global `allowRawHtml` setting.
- **Comment profile** - headings, images, tables, footnotes, raw HTML blocks, divs, thematic breaks, and math are all denied and degrade to plain text. Only basic inline formatting and block-level paragraphs, lists, blockquotes, and code blocks are allowed. External links get `rel="nofollow ugc"`. This is enforced unconditionally - the global `profile` config has no effect on `|carve_ugc`.

Note: Shopware has a built-in product review system but no native Q&amp;A. For Q&amp;A rendering you would need a dedicated Q&amp;A plugin that exposes review-like entities; wire those to `|carve_ugc` in the same way.

---

### 10 - Multi-target CLI `carve:render`

[](#10---multi-target-cli-carverender)

**Benefit:** Render a `.crv` file or piped source to HTML, Markdown, plain text, or ANSI from the console. Write once, show anywhere: storefront, email, terminal, and export from a single source.

```
# Render to HTML
bin/console carve:render path/to/content.crv --html

# Render to plain text
bin/console carve:render path/to/content.crv --plain

# Render to Markdown
bin/console carve:render path/to/content.crv --md

# Render with ANSI color (terminal output)
# (--term, not --ansi: the latter is reserved by Symfony's console to force color globally)
bin/console carve:render path/to/content.crv --term
```

---

Using `|carve` in your own templates
------------------------------------

[](#using-carve-in-your-own-templates)

The `|carve`, `|carve_text`, `|carve_md`, `|carve_ctx`, and `|carve_ugc` filters are registered globally in the Twig environment. They work in any Shopware template surface - storefront themes, transactional mail templates, Flow Builder mail bodies, document (invoice/delivery note) templates, and on any custom entity's text field. No extra configuration is needed beyond installing the plugin.

**Custom storefront template** - render any text field from any entity:

```
{# Any storefront .html.twig with a Carve source in scope #}
{{ myEntity.translated.someCarveField|carve }}

{# With :product[SKU] inline references resolved against the current sales channel #}
{{ myEntity.translated.someCarveField|carve_ctx(context) }}
```

**Flow Builder / mail template** - one source, two mail parts:

```
{# HTML part of an email template #}
{% set body = order.customFields.carve_message_body ?? '' %}
{{ body|carve }}

{# Plain-text part of the same email template #}
{{ body|carve_text }}
```

**Document (invoice / delivery note) template** - same filter, works in Twig-based document overrides. Because documents are rendered once and stored as PDF, cache the output when the source is long:

```
{# In a custom document Twig override #}
{% set manufacturerCopy = order.lineItems.first.product.manufacturer.translated.customFields.carve_manufacturer_body ?? '' %}
{% if manufacturerCopy %}
    {{ manufacturerCopy|carve }}
{% endif %}
```

The manufacturer field added by the `carve_manufacturer_body` migration (Surface 5 above) follows the same pattern - set the source in the admin, render it wherever your theme shows the brand.

### Building your own elements

[](#building-your-own-elements)

Carve can resolve your own inline/block elements against live Shopware data (prices, stock, product cards, legal snippets) at render time - the same mechanism that powers `:product[SKU]`. See **[`docs/custom-elements.md`](docs/custom-elements.md)**for the render-hook pattern, two complete worked examples (`:badge[...]` pure markup and `:price[SKU]` live data), how to make an element opt-in via config, and a catalog of commerce-specific element ideas.

---

Install
-------

[](#install)

### Prerequisites

[](#prerequisites)

shopware-carve depends on two libraries that are not yet published to Packagist or npm. Install them from local clones until they are released.

#### PHP dependency: carve-php

[](#php-dependency-carve-php)

Clone carve-php alongside your Shopware project root (adjust the path to suit your layout):

```
git clone https://github.com/markup-carve/carve-php ../carve-php
```

Add a `path` repository to your project's `composer.json` so Composer resolves it locally:

```
{
    "repositories": [
        {
            "type": "path",
            "url": "../carve-php",
            "options": { "symlink": false }
        }
    ],
    "minimum-stability": "dev",
    "prefer-stable": true
}
```

Then require the plugin:

```
composer require markup-carve/shopware-carve
```

Verify the library loaded correctly:

```
