Overview

DevExchange

Overview

The DevExchange is your chance to catch up with old friends and meet new people, while discussing the topics that matter to you.

Timing

The sessions are planned to run from 16:00 - 18:00 on the pre-event day (Thursday 16th) as per the official agenda.

Prerequisites

None, just turn up!

Tables & Sessions

Tables will be split into the topics suggested. Join Hyvä Slack and use the #hyva-dev-paradise channel to suggest topics prior to the event

Workshops

Overview

These workshop sessions are designed to allow developers to get hands-on experience with various Hyvä focused topics.

Each session topic will:

  • Focus on teaching a key concept while building/undertaking a real-world example
  • Be guided by one or more members of the Hyvä team, who will be on hand to assist you

If none of the sessions are of interest to you, you are also welcome to utilise the workshop space during this time to:

  • AMA (Ask Me Anything) - ask questions to any of our available team members
  • Share your feedback (e.g. feature suggestions)
  • Maybe even get a sneak peek at some of our in-progress work

Timing

The sessions are planned to run from 15:00 - 16:40 on the main event day (Friday 17th) as per the official agenda, but will be broken down as follows:

  • Intro and topics overview - 15 mins
  • Session 1 - 40 mins
  • Changeover (optional) - 5 mins
  • Session 2 - 40 mins

Each topic will run throughout the entire workshop, but there is a changeover point halfway if you want to switch to another topic.

Prerequisites

  • Yourself
  • An open and inquisitive mind! (to learn)
  • A laptop with a development environment that has the latest versions of Hyvä Theme / Checkout / Commerce installed
  • See each session (below) for the specific environment requirements (e.g. not all sessions require Hyvä Commerce)
  • If you can prepare these before the day, it means you can make more of the session time learning
  • If you don’t have a laptop with you, you are still welcome to join, watch what others are doing and engage with our team
  • If you don’t have access to Checkout or Commerce, reach out on Slack (see below)
  • Note: we can only provide access if you are associated with one of our partner agencies (Bronze+) or technology providers

Tables & Sessions

Note:

  • Some tables will have dedicated workshop tasks, others will just be open tables focused on a specific area
  • Final tables and sessions may change depending on space available

Workshop Guides

Note: many of these are a WIP and will remain so until the event.

  • Admin Dashboard: How to build an Admin Dashboard widget
  • Hyvä CMS: How to build a Hyvä CMS component
  • Security: How to write CSP & security friendly code
  • Hyvä Theme & UI: How to migrate to Tailwind v4
  • Checkout Integration: How to create a checkout integration with an API

Admin Dashboard

How to build an Admin Dashboard widget

Overview

Get hands-on with the Admin Dashboard by learning the key concepts of our framework and building your own widget.

Session Guide

What this is

An introduction of what the admin dashboard is and how to make widgets. With a focus on existing display types.

What you’ll learn

Learn to build a Hyva Admin Dashboard Widget.

  • In this workshop, you will create an “Orders by Status” widget that displays a bar chart of all existing orders and their current statuses.

Guide

Step 1: Create the module directory structure

  app/code/Paradise/OrdersByStatus/

  ├── registration.php

  ├── etc/

  │   ├── module.xml

  │   └── adminhtml/

  │       └── hyva_dashboard_widget.xml

  └── Model/

      └── Widget/

          └── OrdersByStatus.php

Step 2: Declare the module with its dependencies (etc/module.xml)

Both framework modules are required. Hyva_AdminDashboardFramework provides the base classes; Hyva_AdminDashboardWidgets provides the dashboard UI that renders them.

<module name="Paradise_OrdersByStatus">

  <sequence>

    <module name="Hyva_AdminDashboardFramework"/>

    <module name="Hyva_AdminDashboardWidgets"/>

    <module name="Magento_Sales"/>

  </sequence>

</module>

Step 3: Register the widget (etc/adminhtml/hyva_dashboard_widget.xml)

This tells the dashboard the widget exists, what it's called, how it looks, and what display type to render.

  <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:noNamespaceSchemaLocation="urn:magento:module:Hyva_AdminDashboardFramework:etc/adminhtml/hyva_dashboard_widget.xsd">

      <widget id="orders_by_status">

          <title>Orders by Status</title>

          <class>Paradise\OrdersByStatus\Model\Widget\OrdersByStatus</class>

          <display_type>bar_chart</display_type>

          <min_height>6</min_height>

          <min_width>1</min_width>

          <icon>chart-bar</icon>

          <trailing_action>

              <label>View All Orders</label>

              <route>sales/order</route>

          </trailing_action>

      </widget>

  </config>

 The id here must match the protected string $id in your PHP class.

title

Provides a user-friendly name for the widget.

class

Defines which PHP class is responsible for implementing the behaviour of the widget.

display_type

Determines which template is used to display the widget once the admin user has added it to their dashboard.

In this case we use bar_chart 

https://apexcharts.com/docs/chart-types/bar-chart/

Min_height / min_width

Specify the minimum number or rows and columns a widget occupies.

icon

Assigns an icon to the widget that is visible in the new widget popup.

trailing_action

Allows developer to define a number of key value to the footer section of a widget

Step 4: Create the widget class (Model/Widget/OrdersByStatus.php)

Extend AbstractBarChart and implement two methods:

namespace Paradise\OrdersByStatus\Model\Widget;

use Hyva\AdminDashboardFramework\Model\Widget\AbstractBarChart;

use Hyva\AdminDashboardFramework\Model\WidgetAuth;

use Hyva\AdminDashboardFramework\Model\WidgetConfig;

use Hyva\AdminDashboardFramework\Model\WidgetInstance\WidgetInstanceInterface;

use Magento\Framework\DB\Sql\Expression;

use Magento\Sales\Model\ResourceModel\Order\CollectionFactory as OrderCollectionFactory;

  class YourWidget extends AbstractBarChart

  {

      protected string $id = 'orders_by_status'; // must match widget XML id

      public function __construct(

          WidgetAuth $widgetAuth,

          WidgetConfig $widgetConfig,

          protected readonly OrderCollectionFactory $orderCollectionFactory,

          string $id = '',

          array $data = [],

      ) {

          parent::__construct($widgetAuth, $widgetConfig, $id, $data);

      }

Step 5: Define configurable properties

Widget properties refer to a set of input options users can use to configure and customize their widget. There are two properties:

Here we add the store view property to allow users to filter between storeviews.

public function getConfigurableProperties(): array

{

  return array_merge(

    parent::getConfigurableProperties(),

      [

         'store_ids' => [

           'label' => __('Store Views'),

           'input' => [

             'type' => 'scope', // Hyvä-specific: renders a store view picker

             'attributes' => [

               'multiple' => true,

               'size'     => '10',

               'required' => true,

             ],

          ],

       ],
    ],

  );
}

Step 6: Define display properties

Here we define an order count to allow users to choose how many orders they want to include

public function getDisplayProperties(): array

{

  return array_merge(

   parent::getDisplayProperties(),

     [

       'order_count' => [

        'label' => __('Number of Orders to Include'),

         'input' => [

           'type'    => 'text',

           'subtype' => 'number',

           ‘'attributes' => [

              ‘'value' => 30,

Step 7: Implement getDisplayData()

This function is responsible for providing widget data. In this case we use
$widgetInstance->getPropertyValue(self::KEY_CONFIGURABLE_PROPERTIES, 'store_ids'); and ($widgetInstance->getPropertyValue(self::KEY_DISPLAY_PROPERTIES, 'order_count') ?? 30); to filter and limit the recent order. Then group them.

public function getDisplayData(WidgetInstanceInterface $widgetInstance): array

{

  $storeIds   =  $widgetInstance->getPropertyValue(self::KEY_CONFIGURABLE_PROPERTIES, 'store_ids');

$orderCount = (int) ($widgetInstance->getPropertyValue(self::KEY_DISPLAY_PROPERTIES, 'order_count') ?? 30);

 $recentOrders = $this->orderCollectionFactory->create();

 $recentOrders->addFieldToFilter('store_id', ['in' => $storeIds]);

 $recentOrders->getSelect() ->reset('columns')

              ->columns(['status'])

              ->order('created_at DESC')

              ->limit($orderCount);

          // Outer query: group the subset by status and count

          $connection  = $recentOrders->getConnection();

          $outerSelect = $connection->select()

              ->from(

                  ['recent_orders' => $recentOrders->getSelect()],

                  ['status', 'count' => new Expression('COUNT(*)')]

              )

              ->group('status');

          $rows = $connection->fetchAll($outerSelect);

          // Return data in the shape AbstractBarChart expects (ApexCharts format)

          return [

              'series' => [['name' => (string) __('Orders'), 'data' => array_column($rows, 'count')]],

              'xaxis'  => ['categories' => array_column($rows, 'status')],

          ];

      }

  }

Bonus Tasks

Extending the widget

The bar_chart and line_chart display types both use the same formatting of data but the pie_chart is slightly different. See if you can convert the widget to use the pie_chart display type.

Resources:

Top tip:

  • The pie chart doesn’t use ‘xaxis’ the numeric data goes in ‘series’

Newsletter Subscribers Widget

Try to create a widget that shows the latest newsletter subscribers.

  • Using the inbuilt table display type
  • Add the following columns
  • Email address
  • Name (or N/A if not provided)
  • Subscribed (Status): Yes/No
  • Add a Configurable Property to limit the number of results
  • Add a Trailing Action that directs to the main newsletter subscribers admin grid page
  • Add it to the ‘Marketing’ category, or try creating your own category
  • Change the display type to template, create a custom .phtml file and change the output to a <ul> instead

Resources:

 

Hyvä CMS

How to build cool components

Overview

Get hands-on with Hyvä CMS by learning the key concepts of the framework and building your own reusable, modern components. This session focuses on practical implementation, helping you go from understanding the structure to confidently creating and customising components.

Session Guide

What this is

A practical introduction to Hyvä CMS, combined with a deeper dive into how components work under the hood. We’ll explore how data, templates, and configuration come together, with a focus on building flexible, reusable UI components using Hyvä’s modern stack (TailwindCSS, Alpine.js, and Magento).

What you’ll learn

  • How Hyvä CMS components are structured and rendered
  • How to create a custom component from scratch
  • How to pass and manage data within components
  • How to style components using TailwindCSS

What you’ll learn

You will learn on how to develop a custom component

Guide

This guide can be followed in 2 different ways:

  1. Installing Hyvä CMS onto a local environment
  2. Go to https://workshop.calz.dev/ for a workshop specific playground

Guide 1 (Local environment)

Step 1: Create a module

Create an app/code module (e.g. Vendor_Example)

app/

└── code/

    └── Vendor/

        └── Example/

            ├── registration.php

            ├── Observer/

            │   └── RegisterModuleForHyvaConfig.php

            ├── etc/

            │   ├── module.xml

            │   ├── frontend/

            │   │   └── events.xml

            │   └── hyva_cms/

            │       └── components.json

            └── view/

                └── frontend/

                    ├── templates/

                    │   └── elements/

                    │       ├── stl.phtml

                    │       └── stl-single.phtml

                    └── tailwind/

                        └── tailwind.config.js

Or setup from script:

curl -sL https://raw.githubusercontent.com/callanzimmermann/workshop-module/refs/heads/main/setup.sh | bash

Step 2: Component Schema

{

  "stl": {

    "label": "Shop the Look",

    "description": "Editable component for shop the look, must be used for a large container",

    "template": "Vendor_Example::elements/stl.phtml",

    "children": true,

    "content": {

      "text": {

        "type": "richtext",

        "label": "Text"

      },

      "image": {

        "type": "image",

        "label": "Image"

      },

      "classes": {

        "type": "text",

        "label": "Classes"

      }

    }

  },

  "stl_single": {

    "label": "STL Child",

    "description": "Editable component for shop the look, must be used within shop the look component",

    "template": "Vendor_Example::elements/stl-single.phtml",

    "content": {

      "x_axis": {

        "type": "number",

        "label": "X Axis"

      },

      "y_axis": {

        "type": "number",

        "label": "Y Axis"

      }

    }

  }

}

Step 3: Templates

Stl.phtml:

<?php

declare(strict_types=1);

use Hyva\CmsLiveviewEditor\Block\Element;

use Hyva\Theme\Model\ViewModelRegistry;

use Magento\Framework\Escaper;

/** @var Element $block */

/** @var Escaper $escaper */

/** @var ViewModelRegistry $viewModels*/

$children = $block->getChildren();

$image = $block->getImage() ?? [];

$imageSrc = @$image['src'] ?? '';

?>

<div

    class="relative min-h-[450px] flex items-center justify-center overflow-hidden <?= $escaper->escapeHtmlAttr($block->getClasses()) ?>"

    data-liveview-element="stl"

    <?= /** @noEscape */ $block->getEditorAttrs() ?>

    <?= /** @noEscape */ $block->renderBlockId() ?>

>

    <img src="<?= $block->getImagePath($imageSrc) ?>"

        class="object-cover w-full absolute -z-10"

        <?= /** @noEscape */ $block->getEditorAttrs('image') ?> />

    <div <?= /** @noEscape */ $block->getEditorAttrs('text') ?>

        class="text-3xl font-bold text-white">

        <?= $block->renderRichText($block->getText()) ?>

    </div>

    <?php foreach ($children ?: [] as $elementData): ?>

        <?= $block->createChildHtml($elementData, 'stl_child') ?>

    <?php endforeach; ?>

</div>

Slt-single.phtml:

<?php

declare(strict_types=1);

use Hyva\CmsLiveviewEditor\Block\Element;

use Hyva\Theme\Model\ViewModelRegistry;

use Magento\Framework\Escaper;

/** @var Element $block */

/** @var Escaper $escaper */

/** @var ViewModelRegistry $viewModels*/

?>

<div

    class="size-4 rounded-full bg-red-500 absolute"

    data-liveview-element="stl-single"

    style="top:<?= $block->getYAxis() ?>%;left:<?= $block->getXAxis() ?>%"

    <?= /** @noEscape */ $block->getEditorAttrs() ?>

    <?= /** @noEscape */ $block->renderBlockId() ?>

></div>

CSP & Security

How to write CSP & security friendly code

Overview

Get hands-on with writing CSP-compliant code by debugging and resolving a pre-prepared issue.

CSP Migration Workshop — Session Guide

Copy of https://github.com/friends-of-hyva/magento2-paradise-csp-workshop-meta/blob/main/SESSION-GUIDE.md

What this is

You write Hyva. You know Alpine. Everything works great — until someone enables strict Content Security Policy and half your JavaScript goes silent. The console fills up. The page looks fine. Nothing works. You have no idea why.

Welcome to Paradise.

This hands-on workshop walks you through exactly why that happens and how to fix it. You'll work with intentionally broken code, fix it yourself, and come out the other side knowing every Alpine pattern that trips up CSP and how to handle each one. By the end, you'll be able to look at any Hyva component and know whether it's CSP-safe — and migrate it if it isn't.

PCI-DSS 4.0 made strict CSP a compliance requirement for payment pages as of April 2025. If your store handles payments, this is no longer optional. The good news: Hyva is built for this. You just need to know the patterns.


What you'll learn

Eight Alpine patterns that break under strict CSP — what each one is, why it breaks, and how to fix it:

  1. Inline expressions in event handlers — @click="location.href = '/checkout'" → extract to a named method
  2. Negation operator — x-show="!open" → replace with a dedicated boolean property
  3. x-model directive — not available in the Alpine CSP build → replace with :value + @input
  4. Method arguments inline — @click="select('tab')" → move the argument to a data-* attribute
  5. Conditional classes — :class="{ 'active': isOpen, 'closed': !isOpen }" → extract to a method
  6. Complex expressions — x-text="items[index].name" → extract to a method
  7. Inline script registration — <script> blocks without $hyvaCsp->registerInlineScript() → add the call after every </script>
  8. Alpine component registration — components defined in script blocks without alpine:init → register via Alpine.data() inside the event

Plus: the special considerations for Hyva Checkout components (scripts must move out of Magewire templates entirely).


Before you arrive

Please have the following ready before the session starts. A broken environment wastes everyone's time — including yours.

  • Magento Open Source 2.4.7 or higher
  • Hyva default theme installed and set as the active storefront theme
  • Composer available on the command line
  • PHP CLI access to run the migration tool
  • The workshop packages installed:
    composer require friends-of-hyva/magento2-paradise-csp-workshop-meta
  • bin/magento setup:upgrade

    The Hyva CSP theme is included as a dependency — it will be installed automatically.
  • After installation, set Hyva/default-csp as the active theme in Content > Design > Configuration
  • Strict CSP enforced — add the following to app/etc/env.php and run bin/magento app:config:import:
    'system' => [
  •     'default' => [
  •         'csp' => [
  •             'mode' => ['storefront' => ['report_only' => 0]],
  •             'policies' => ['storefront' => ['scripts' => ['eval' => 0, 'inline' => 0]]]
  •         ]
  •     ]
  • ]
  • Open /paradise/ in your browser and confirm things look broken — that means CSP is working (broken is good, for once)

Don't have Hyva Checkout? No problem — you can still have a great time. Install the frontend package on its own and everything in /paradise/ works without it:

composer require friends-of-hyva/magento2-paradise-csp-workshop-frontend


On the day

The session has two tracks — pick the one that fits where you are.

The Guided Tour (recommended if you're new to CSP): work through the workshop modules in order. Broken code, clear comments pointing at the violations, a quiz, and immediate feedback when you get a fix right. Everything is prepared.

Bring Your Own Code: already familiar with CSP and want practical help migrating a real project? Bring a Hyva module or theme you're working on. There will be time for hands-on migration with help available.

The guided tour works well as preparation for the hands-on track — it gives you the vocabulary and patterns, so the time spent on your own code is spent on actual migration rather than the basics.


Full guides

  • Study Guide — for participants: prerequisites, how the workshop works, how to run the migration tool
  • Teacher Guide — for whoever is running the session: the story to tell, all 8 patterns explained with before/after, how to help students who are stuck

Tailwind v4 Migration

How to migrate to Tailwind v4

Overview

Learn how to migrate your theme to Tailwind v4 (the right way!) using the automated upgrade-helper tool to handle the heavy lifting.

  • Moderator(s): Sean
  • Environment requirements: Hyvä Theme 1.3.x

Tailwind v4 Migration Workshop — Session Guide

What this is

Tailwind v4 is a total rewrite designed for the modern web. It replaces the heavy JavaScript configuration with a CSS-native configuration.

In this workshop, we’ll move through the actual migration of a Hyvä theme. You will use the automated migration script which transforms your tailwind.config.js into the new @theme variables and handles the npm cleanup automatically.

Your job is to verify the output, fix pathing issues in your imports, and manually restore your custom styles from the backup created by the tool.

What you'll learn

  • The New Configuration: Understanding the shift from JS objects to CSS variables (--color-primary).
  • Automated Migration: Running the Hyvä upgrade-helper to handle file structure, config conversion, and dependency updates in one go.
  • Pathing & Imports: Why v4 is stricter about file paths (the ./ requirement) and how to fix broken module imports.
  • Legacy Class Strategy: Reviewing the tailwind-deprecated-report.md generated by the tool and refactoring using AI or manual search-and-replace.
  • Manual Style Restoration: How to safely bring back your custom CSS from the backup folder into the new v4 structure.

Before you arrive

Please have the following ready. A broken environment wastes everyone's time.

  • Hyvä Theme: A theme based on Hyvä 1.3.x or a child theme still using Tailwind v3.
  • Migration Tool: Ensure you have installed the standalone upgrade-helper via Composer:

composer require --dev hyva-themes/upgrade-helper-tools:dev-main

  • Node.js 20+: Tailwind v4 requires a modern Node environment.
  • Clean Git State: Commit all changes before starting so you can easily diff the changes made by the migration script.
  • Preparation Note: It is handy to have the Hyvä Default Theme v1.4.0+ already updated in your vendor folder. This avoids heavy downloads on the day and ensures the environment is ready for the upgrade helper.

On the day

1: Running the Migration Tool

We'll execute the script provided by the upgrade-helper. This tool updates your package.json, installs Tailwind v4, cleans up node_modules, and automatically migrates your tailwind.config.js into your CSS file.

Note: Ensure you are in the Magento root directory when running this command.

# Example for a theme located at app/design/frontend/Vendor/theme

./vendor/bin/update-to-tailwind-v4.js app/design/frontend/Vendor/theme

2: Verifying the CSS Variables

The tool moves your config to the @theme block in tailwind-source.css. We will verify the output, ensuring brand tokens are correctly mapped and any complex logic from your old JS config has translated correctly.

3: Build & Troubleshooting Imports

We'll run the first v4 build. This is where most errors appear:

  • The ./ Requirement: v4 is strict.
  • If you see an npm error regarding missing imports, it’s usually because the path is missing the leading ./ (e.g., @import "ui.css" must become @import "./ui.css").
  • Missing Module Styles: We will check if the automated sourcing correctly identifies all your Hyvä module paths and how to manually add them if they are missed.

4: Reviewing Legacy Classes

The migration script generates a tailwind-deprecated-report.md with all of the deprecated and removed classes. We will:

  • Review the report for what needs to be updated.
  • Demonstrate how to quickly update these throughout your templates using manual search-and-replace or AI assistance to map the old syntax to the new v4 standards.

5: Adding back your custom classes

The migration tool creates a backup of your old web/tailwind folder and starts tailwind-source.css from a clean slate. Your custom classes will not be moved automatically.

  • Locate your backed-up CSS files.
  • Manually copy your custom @layer components or utilities back into the new tailwind-source.css.
  • Ensure they are placed correctly within the new v4 @layer syntax to maintain the correct CSS cascade.

(Optional) Explore Design Tokens

With this update, you get support for using Design Tokens with Tailwind v4. If time permits, we will explore options based on the Hyvä Design Token Docs. We will use the Simple Tokens format, as it doesn't require design tools like Figma and the syntax is very similar to what you know from the tailwind.config.js.


Full guides

Checkout Integration

How to create a checkout integration with an API

Overview

Get hands-on with checkout integration(s) and learn how to create an integration with an example API

Session Guide

What this is

You know all about Hyvä Theme and you have a client who has a shop with Hyvä Checkout with B2B functionality where B2B customers can buy on purchase orders. However these orders need to be approved or within spending limits according to the rules.

Welcome to Paradise.

This workshop will introduce you to checkout integrations and how to go about them

What you’ll learn

We will cover various aspects of the Hyvä Checkout process:

  • Magewire & alpineJS
  • Layout XML, PHP ViewModels, Block classes and templates
  • Module development for Hyvä Checkout
  • One-step or multi-step layout
  • CSP-compliancy
  • Testing and error-handling


Before you arrive

Be sure to have the following ready before the session starts:

  • Magento Open Source 2.4.7 or higher
  • Hyvä Checkout latest installed
  • Composer available on the command line
  • PHP CLI access
  • (optional) Hyvä Theme installed

The Hyva Checkout Docs


On the day

The Session starts with a small introduction and then we will get started. If you have code and you have questions we will be happy to help.