Sylius Setup Guide

Add a chat assistant to your Sylius store with the official plugin on Packagist.

You're in the developer setup guide. Looking for an overview of what Emporiqa does for your Sylius store? See the integration page.

See integration page

Quick Start

Install the plugin with Composer, drop in a small YAML config, embed the widget in your layout, run one sync command. The chat widget goes live on your storefront.

What you'll need

  • Sylius 1.12+ or 2.0+ on Symfony 6.4+ / 7.x with PHP 8.1 or higher
  • Shell access to run composer and bin/console
  • An Emporiqa account — create a free sandbox if you don't have one yet
1

Install the plugin

Pull the plugin in via Composer. It's published on Packagist.

composer require emporiqa/sylius-plugin
2

Register the plugin

Add the plugin entry to config/bundles.php. Symfony Flex may do this for you automatically.

// config/bundles.php
return [
    // ... other bundles
    Emporiqa\SyliusPlugin\EmporiqaPlugin::class => ['all' => true],
];
3

Configure credentials

Create config/packages/emporiqa.yaml with the minimum required settings. Your Store ID and Connection Secret both come from your Emporiqa dashboard under Store Settings → Integration.

# config/packages/emporiqa.yaml
emporiqa:
    store_id: '%env(EMPORIQA_STORE_ID)%'
    webhook_url: '%env(EMPORIQA_WEBHOOK_URL)%'
    webhook_secret: '%env(EMPORIQA_WEBHOOK_SECRET)%'
    enabled_languages: ['en_US', 'de_DE']

Then add the matching env vars to your .env (or .env.local).

# .env.local
EMPORIQA_STORE_ID=your_store_id
EMPORIQA_WEBHOOK_URL=https://emporiqa.com/webhooks/sync/
EMPORIQA_WEBHOOK_SECRET=your_connection_secret
4

Embed the widget

Add the Twig function before </body> in your shop layout (e.g. templates/bundles/SyliusShopBundle/Layout/base.html.twig). The emporiqa_cart_widget function registers the embed plus the in-chat cart handler.

{# templates/bundles/SyliusShopBundle/Layout/base.html.twig #}
{{ emporiqa_cart_widget() }}
5

Register routes

Create config/routes/emporiqa.yaml to mount the plugin's cart API and order tracking endpoint.

# config/routes/emporiqa.yaml
emporiqa:
    resource: '@EmporiqaPlugin/config/routes.yaml'
6

Install assets and run the initial sync

Install bundle assets, clear cache, then push your products and pages. The sync command streams in batches and prints progress.

bin/console assets:install
bin/console cache:clear
bin/console emporiqa:test-connection
bin/console emporiqa:sync:all

Open your storefront. The chat bubble appears in the bottom-right corner. Click it, ask a product question, and the salesperson answers from your catalog.

Shopper describes what they want, the online salesperson recommends the right product from your catalog
Chat widget on the Emporiqa demo store — the same widget runs on your Sylius storefront.

You're live

  • Product updates sync automatically via Sylius resource events
  • Customers can ask about products, policies, and their orders
  • Add-to-cart in chat; customers proceed to your store checkout

Need to sync custom page entities, decorate the order provider, or react to cart events?

See Developer Docs

Developer Documentation

Architecture, configuration reference, webhook payload schema, service decoration points, and Symfony events.

Architecture

The plugin is a Symfony bundle that hooks into Sylius via resource events (sylius.product.post_update, sylius.product_variant.post_update, etc.), Doctrine lifecycle events for page entities, and the Sylius checkout workflow (Symfony Workflow on 2.x, Winzou state machine on 1.x) for conversion tracking.

Webhook events use deferred delivery: event subscribers queue payloads in an in-memory WebhookEventQueue with deduplication, and the queue flushes to Emporiqa on kernel.terminate after the HTTP response is sent. Saving a product or completing checkout never blocks on the outbound HTTP call.

All translations and channels for a single product or page consolidate into one webhook event. Variant changes cascade to the parent: editing a variant triggers a parent re-sync so search always sees the full catalog state. Every service that formats, resolves, looks up, or transports data ships behind an interface so you can decorate any part of the pipeline without patching plugin code.

Requirements

Sylius 1.12+ or 2.0+
Symfony 6.4+ or 7.x
PHP 8.1 or higher
Emporiqa account Free sandbox or paid subscription

Configuration Reference

Configuration lives in config/packages/emporiqa.yaml. Only store_id, webhook_url, and webhook_secret are required. Everything else has sensible defaults.

Settings

Key Type Default Description
store_id string required Store identifier from the Emporiqa dashboard.
webhook_url string required Emporiqa webhook endpoint.
webhook_secret string required HMAC-SHA256 signing key for webhooks and order tracking.
base_url string '' Absolute base URL for link generation when no HTTP request is available (used by console commands).
media_base_path string /media/image/ Base path for product image URLs. Override for CDN, S3, or LiipImagine setups.
brand_attribute_code string brand Product attribute code that holds brand/manufacturer data.
enabled_languages string[] ['en_US', 'de_DE'] Sylius locale codes to sync. Codes are passed as-is; no truncation.
sync.products bool true Enable automatic product synchronization.
sync.pages bool true Enable automatic page synchronization (only fires for classes in page_entity_classes).
page_entity_classes string[] [] FQCNs of your page entities. Page sync is opt-in — if this list is empty, no page events fire and the PageFormatter, PageDoctrineListener, and PageUrlResolver services are not registered.
order_tracking.enabled bool true Mounts the order tracking API controller. Disable to remove the endpoint entirely.
cart.enabled bool true Mounts the cart API and order completion subscriber. Disable to remove both.

Full configuration example

# config/packages/emporiqa.yaml
emporiqa:
    store_id: '%env(EMPORIQA_STORE_ID)%'
    webhook_url: '%env(EMPORIQA_WEBHOOK_URL)%'
    webhook_secret: '%env(EMPORIQA_WEBHOOK_SECRET)%'
    base_url: 'https://myshop.com'
    media_base_path: '/media/image/'
    brand_attribute_code: 'brand'
    enabled_languages: ['en_US', 'de_DE']
    sync:
        products: true
        pages: true
    page_entity_classes:
        - App\Entity\StaticPage
        - App\Entity\BlogPost
    order_tracking:
        enabled: true
    cart:
        enabled: true

Page sync is opt-in. Sylius has no built-in page entity, so the plugin does not auto-detect your CMS pages. To sync policies, FAQ entries, or blog posts, implement Emporiqa\SyliusPlugin\Model\PageInterface on your entity and list its FQCN under page_entity_classes. See Customization.

Channels

The plugin uses the Sylius channel code directly as the Emporiqa channel identifier — no mapping configuration needed. Each channel's pricing, availability, and translations are consolidated into the same webhook event.

Features

Real-time Webhooks

Products sync via Sylius resource events (post_create, post_update, pre_delete). Variant changes cascade to the parent. Events are deduplicated in-request and flushed after the response via kernel.terminate.

Chat Widget

Embed via {{ emporiqa_cart_widget() }}. Locale, currency, channel, and a signed user_id token are injected from Sylius contexts. Anonymous pages are safe for Varnish/CDN caching.

Multi-Channel & Multi-Language

A single product event carries every channel's pricing, availability, and all translations for each configured locale. Sylius locale codes (en_US, de_DE) are passed through unchanged.

Cart & Checkout API

REST endpoints under /emporiqa/api/cart for add, update, remove, clear, view, and checkout URL. The chat widget calls these via window.EmporiqaCartHandler. CSRF-protected for authenticated users.

Order Tracking

HMAC-signed endpoint at POST /emporiqa/api/order/tracking. Resolves Sylius payment and shipping states into a normalized order status response. Replay-protected (5 minute window).

Console Commands

emporiqa:sync:all, emporiqa:sync:products, emporiqa:sync:pages, and emporiqa:test-connection. Memory-efficient batching with session-based reconciliation.

Conversion Tracking

order.completed webhooks on checkout completion. Subscribes to Symfony Workflow (Sylius 2.x) or Winzou state machine (Sylius 1.x). Reads the emporiqa_sid cookie for chat-to-purchase attribution.

Extensible by Design

Every formatter, resolver, provider, and sender ships behind an interface. Decorate them with your own implementation, or listen to PostFormatEvent, CartOperationEvent, and others for fine-grained control.

Webhook Payload

Webhook requests are POSTed to the configured webhook_url with an X-Webhook-Signature header containing the HMAC-SHA256 signature of the raw body. Each request carries a batch of events.

Batch envelope

{
  "events": [
    {"type": "product.updated", "data": {...}},
    {"type": "product.updated", "data": {...}}
  ]
}

Event types

Event Trigger
product.created / product.updated / product.deleted Sylius product or variant lifecycle events
page.created / page.updated / page.deleted Doctrine lifecycle events on classes listed in page_entity_classes
sync.start / sync.complete Console sync commands (reconciliation sessions)
order.completed Sylius checkout workflow completes

Product event

{
  "type": "product.updated",
  "data": {
    "identification_number": "product-123",
    "sku": "PROD-123",
    "channels": ["", "b2b"],
    "names":        {"": {"en_US": "Product Name", "de_DE": "Produktname"}, "b2b": {"en_US": "Product Name"}},
    "descriptions": {"": {"en_US": "Description...", "de_DE": "Beschreibung..."}},
    "links":        {"": {"en_US": "https://store.com/en_US/products/product-name", "de_DE": "https://store.com/de_DE/products/produktname"}},
    "categories":   {"": {"en_US": ["Electronics"], "de_DE": ["Elektronik"]}, "b2b": {"en_US": ["Electronics"]}},
    "attributes":   {"": {"en_US": {"Color": "Blue"}, "de_DE": {"Farbe": "Blau"}}},
    "brands":       {"": "Brand Name", "b2b": "Brand Name"},
    "prices":       {"": [{"currency": "EUR", "current_price": 79.99, "regular_price": 99.99}], "b2b": [{"currency": "USD", "current_price": 69.99, "regular_price": 89.99}]},
    "images":       {"": ["https://store.com/media/image/product.jpg"]},
    "availability_statuses": {"": "available", "b2b": "available"},
    "stock_quantities":      {"": 25, "b2b": 25},
    "parent_sku": null,
    "is_parent": false,
    "variation_attributes": {}
  }
}

Field structure

Type Pattern Fields
Translatable {channel: {locale: value}} names, descriptions, links, categories, attributes, variation_attributes
Shared {channel: value} brands, prices, images, availability_statuses, stock_quantities
Flat Direct value identification_number, sku, channels, is_parent, parent_sku

For variable products, the parent ships with is_parent: true and variation_attributes containing translated option names. Each variant ships separately with parent_sku set and variation_attributes: {}. Delete events contain only identification_number.

Customization

Every behavior customization goes through standard Symfony mechanisms: service decoration for replacing pipeline components, and event listeners for reacting to the sync lifecycle. No plugin code gets patched.

Service interfaces you can decorate

Interface What you can change
ProductFormatterInterface Product and variant payload shape, custom attributes, price logic.
PageFormatterInterface Page payload shape and custom fields.
PageUrlResolverInterface URL generation for page entities. Default returns '' — decorate this to emit real URLs.
OrderProviderInterface Order lookup logic, response format, verification fields.
WebhookSenderInterface HTTP transport, retry policy, logging.

Symfony events you can listen to

Event When it fires
PreSyncEvent Before each entity is formatted. Cancel sync for specific entities.
PostFormatEvent After the payload is formatted. Mutate the formatted events before they're queued.
PreWebhookSendEvent Right before a batch is POSTed. Filter or adjust the batch.
CartOperationEvent Before add, update, remove, or clear. Cancel or enforce business rules.
OrderTrackingEvent Before the order tracking response returns. Add custom fields or redact data.

Example: decorate the order provider

namespace App\Service;

use Emporiqa\SyliusPlugin\Service\OrderProviderInterface;

class CustomOrderProvider implements OrderProviderInterface
{
    public function __construct(
        private OrderProviderInterface $inner,
    ) {}

    public function findOrder(string $identifier, ?string $userId, array $verificationFields): ?array
    {
        $order = $this->inner->findOrder($identifier, $userId, $verificationFields);

        if ($order !== null) {
            $order['loyalty_points'] = $this->pointsFor($identifier);
        }

        return $order;
    }
}
# config/services.yaml
services:
    App\Service\CustomOrderProvider:
        decorates: Emporiqa\SyliusPlugin\Service\OrderProviderInterface

Example: mutate product data before send

namespace App\EventListener;

use Emporiqa\SyliusPlugin\Event\PostFormatEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener(event: PostFormatEvent::NAME)]
class ExtraProductFieldsListener
{
    public function __invoke(PostFormatEvent $event): void
    {
        $events = $event->getFormattedEvents();

        foreach ($events as &$webhookEvent) {
            $webhookEvent['data']['sustainability_score'] = 'A+';
        }

        $event->setFormattedEvents($events);
    }
}

Page sync setup

Implement Emporiqa\SyliusPlugin\Model\PageInterface on your page entity (the translations must expose getTitle(), getContent(), getSlug(), getLocale()), list its FQCN under page_entity_classes, and decorate PageUrlResolverInterface to emit real URLs. Without the decorator, the default resolver returns an empty string.

Full decoration examples for PageUrlResolverInterface, ProductFormatterInterface, and more live in the plugin README on GitLab.

Console Commands

All commands use memory-efficient batching and can run in reconciliation sessions — items not seen during the session can be marked deleted on the Emporiqa side.

Command What it does
emporiqa:sync:all Full reconciliation of products and pages.
emporiqa:sync:products Sync only products (and their variants).
emporiqa:sync:pages Sync only page entities listed in page_entity_classes.
emporiqa:test-connection Sends a real product in dry-run mode. Reports signature validation, detected languages, field coverage, and warnings.

Shared flags

Flag Description
--batch-size=50 Events per webhook request (default 50).
--dry-run Format data without sending anything. Useful for validating output.
--no-session Skip sync.start/sync.complete events. Use for incremental updates where you don't want deletion reconciliation.
# Full reconciliation
bin/console emporiqa:sync:all

# Products only, small batches, no session markers
bin/console emporiqa:sync:products --batch-size=25 --no-session

# Validate payload without sending
bin/console emporiqa:sync:products --dry-run

# End-to-end connection test
bin/console emporiqa:test-connection -v

Order Tracking

Emporiqa can look up order status on behalf of customers during chat conversations. The plugin mounts a signed endpoint that Sylius responds to when a customer asks about their order.

Endpoint

POST /emporiqa/api/order/tracking

Flow

  1. Customer asks "Where is my order #000001234?" in the chat.
  2. Emporiqa POSTs a signed payload (HMAC-SHA256, via X-Emporiqa-Signature) to the endpoint.
  3. The OrderProvider looks up the order through Sylius's OrderRepositoryInterface, verifies any verification_fields (e.g. billing email), and maps payment/shipping state into a normalized status.
  4. The salesperson presents the order information to the customer.

Setup

The endpoint is active as soon as the plugin is installed (unless order_tracking.enabled is false). To let Emporiqa call it, set the Order Tracking API URL in your dashboard (Store Settings → Integration) to:

https://your-store.com/emporiqa/api/order/tracking

Status mapping

Sylius state Returned status
Awaiting paymentpending_payment
Paid, not shippedprocessing
Partially shippedpartially_shipped
Shippedshipped
Refundedrefunded
Cancelledcancelled

Requests older than 300 seconds are rejected (replay protection). To customize lookup logic, decorate OrderProviderInterface — see Customization. See the Webhook Setup Guide for the full request/response schema.

Order tracking is optional. Disable it with order_tracking.enabled: false or leave the dashboard URL blank — order questions then route to the Customer Support agent instead.

Troubleshooting

Connection test fails

  • Run bin/console emporiqa:test-connection -v for a verbose report.
  • Verify Store ID, webhook URL, and connection secret match the Emporiqa dashboard.
  • Confirm your server can reach the webhook URL via outbound HTTPS.
  • Check Symfony logs (var/log/{env}.log) for HMAC or transport errors.

Products not syncing

  • Confirm sync.products: true in config/packages/emporiqa.yaml.
  • Verify the product is enabled and has at least one enabled variant with a price.
  • Trigger a full sync: bin/console emporiqa:sync:products.
  • Clear cache after config changes: bin/console cache:clear.

Pages not syncing

  • Confirm your entity class FQCN is listed in page_entity_classes.
  • Ensure the entity implements Emporiqa\SyliusPlugin\Model\PageInterface and its translations expose getTitle(), getContent(), getSlug(), getLocale().
  • Run bin/console emporiqa:sync:pages and check the output.
  • If page URLs are empty, decorate PageUrlResolverInterface.

Widget not appearing

  • Confirm {{ emporiqa_cart_widget() }} is in your shop layout before </body>.
  • Run bin/console assets:install so emporiqa-cart.js is published to public/bundles/emporiqa/js/.
  • View page source and look for the <script async src="...emporiqa.com/chat/embed/..."> tag.
  • Check the browser console for JavaScript errors.

Cart operations fail with 403

  • Authenticated users need a CSRF token — fetch one from GET /emporiqa/api/csrf-token and send it as X-CSRF-Token.
  • Confirm cart.enabled: true in config.
  • Check that routes are imported via config/routes/emporiqa.yaml.

Related Articles

Need Help?

Having trouble with the integration? Our team is here to help.

Contact Support