---
title: "Minimal workflow file for product specifications | Customer&#x27;s Canvas Hub"
canonical: "https://customerscanvas.com/docs/hub/admin-guide/workflows/ui-framework/tutorials/creating-minimal-workflow-file/"
---

# Minimal workflow file for product specifications

> Let's assume that you have a business card product and want your customers to personalize this product. To do so, you need to create a workflow where customers personalize the default card template and confirm their changes before placing an order.

In this tutorial, you'll learn how to create such a basic workflow file that configures the necessary personalization steps.

## Defining steps

This workflow may contain two steps: `Editor` and `Approval`.

In the `Editor` step, you will display the Design Editor. 

In the `Approval` step, you will display preview images and the confirmation checkbox. Once approved, you will display the **Add to cart** button. 

```json
"steps": [
  {
    "name": "Editor"
  },
  {
    "name": "Approval"
  }
]
```

## Defining widgets

Let's select the widgets you need. To customize a product, use the `design-editor` widget. For displaying proof images, add the `slider` widget. To use the confirmation checkbox and the **Add to cart** button, embed the `finish-group` widget. 

Then, add the widgets that can help customers creare an order:

- the `steps` widget, which hides the **Finish** button in the Navigation panel.
- the `Order` widget, which passes the data to a cart. 

```json
"widgets": [
  {
    "name": "editor",
    "type": "design-editor",
    "params": {...}
  },
  {
    "name": "preview",
    "type": "slider",
    "params": {...}
  },
  {
    "name": "finish",
    "type": "finish-group",
    "params": {...}
  },
  {
    "name": "steps",
    "type": "steps",
    "params": {...}
  },
  {
    "name": "order",
    "type": "order",
    "params": {...}
  }
]
```

## Attributes

In this workflow file, you will need two attributes. First, add the `Design` attribute, which will allow you to select a template for your product. This is the design that your customers will personalize. 
Then, add the `Output Print File Resolution` attribute. This will be an input field to define a DPI value while creating a product. If the DPI is less than this value, warnings will appear. 

When you create a product in **Product specification**, you will see these attributes as follows:

![Attributes in Product Specification.](./images/how-to-attributes.png)

```json
"attributes": [
  {
    "name": "Design",
    "label": "Design",
    "type": "single-asset",
    "assetType": "design",
    "description": "Choose a design associated with this product. Your design file can contain text, shapes, images, and backgrounds.",
    "validations": [
      {
        "type": "required",
        "message": "Choose a design which should be used as a template."
      }
    ]
  },
  {
    "name": "Output Resolution",
    "label": "Output Print File Resolution",
    "defaultValue": 300,
    "validations": [
      {
        "type": "min",
        "min": 1,
        "message": "Resolution should be a positive number."
      }
    ],
    "type": "number"
  }
]
```

## Vars

Let's assign attribute values to variables in the `vars` block to use this shortened form later.

```json
"vars": {
    "design": "{{product.attributes?.find(x=>x.name==='Design')?.value}}",
    "outputResolution": "{{ product.attributes.find(x=>x.name==='Output Resolution').value}}"
  }
```

## Steps

### Editor step

The `Editor` step is the first step in our workflow file. It consists of the `design-editor` widget. This widget will be embedded in the `mainPanel`.

```json
{
  "name": "Editor",
  "mainPanel": {
    "name": "editor"
  }
}
```

Let's define its commands.

#### Design Editor

The `design-editor` widget allows customers to personalize a product template. Let's define basic settings for this widget.

First, define the `initial` command with the `productDefinition` property to open a design. This design is selected in the attributes, and you can get its ID through the variable. 

Next, add the `editorConfig` property. Here, you attach allowed fonts in the `fontList` property and set up the `rendering` parameters. In this example, we define the output resolution, file format, and color space and enable safety lines.

This is what the `design-editor` widget looks like:

![Design Editor in a workflow file.](./images/how-to-design-editor.gif)

```json
{
  "name": "editor",
  "type": "design-editor",
  "params": {
    "initial": {
      "productDefinition": "{{vars.design?.id}}",
      "editorConfig": {
        "fontList": {
          "appFonts": [
            "*"
          ]
        },
        "rendering": {
          "hiResOutputDpi": "{{vars.outputResolution}}",
          "hiResOutputFileFormat": "pdf",
          "hiResOutputColorSpace": "cmyk",
          "proofImageSafetyLinesEnabled": true
        }
      }
    }
  }
}
```

### Approval step

In this paragraph, you will fill the **Approval** step. 

In this step, the `slider` widget displays the personalization result, and the `finish-group` widget displays controls, which help you finish this process. 

Embed the `slider` widget into the `mainPanel` and the `finish-group` into the `bottomPanel`.

![Approval step.](./images/how-to-approval-step.png)

```json
{
  "name": "Approval",
  "mainPanel": {
    "name": "preview"
  },
  "bottomPanel": {
    "name": "my-finish-finish"
  }
}
```

#### Slider

`Slider` gets the proof images from the `design-editor` widget and creates one or two columns to display them.

```json
{
  "name": "preview",
  "type": "slider",
  "params": {
    "direction": "tile",
    "rows": 1,
    "columns": "{{Math.min(2,$['editor'].proofImageUrls.length)}}",
    "containerColor": "#eee",
    "images": {
      "{{#each $['editor'].proofImageUrls.map(s=>s[0]) as imageUrl }}": {
        "url": "{{imageUrl}}"
      }
    }
  }
}
```

#### Finish-group

The **Approval** step contains controls that allow your customers to add a product to their order. These are the confirmation checkbox and the **Add to cart** button.

These controls are components of the `finish-group` widget. This widget also starts rendering high resolution images and updates the `order` widget.

```json
{
  "name": "finish-group",
  "type": "finish-group",
  "params": {
    "checkboxPrompt": "I have reviewed and approve my design.",
    "value": false,
    "checkboxEnabled": true,
    "checkboxVisible": true,
    "buttonText": "Add to cart",
    "buttonClassStyle": "primary",
    "onClick": [
      "{{#function main.showPreloader(true, 'Creating print files...')}}",
      "{{#function $['editor'].getHiResImages(800,800)}}",
      "{{#function $['order'].widgetUpdated}}",
      "{{#function main.showPreloader(false)}}"
    ]
  }
}
```

#### Steps widget

The `steps` widget allows you to hide the **Finish** button in the **Navigation** panel, which appears by default.

```json
{
  "name": "steps",
  "type": "steps",
  "params": {
    "finishButton": {
      "visible": "false"
    }
  }
}
```

## Connecting steps

Let's connect the first and the second step to send data from the `design-editor` widget to the `slider` widget. To do so, use the `onActivate` event. When you click the **Next** button in the **Navigation** panel in the **Editor** step, the system will render proof images.

Then, the system clears the confirmation checkbox. It is needed for the situations when you see the result and decide to go back to the editor to make additional changes. After that, you go back to the **Approval** step, and the confirmation checkbox will be empty. 

```json
"steps": [
  {
    "name": "Editor",
    "mainPanel": {
      "name": "editor"
    }
  },
  {
    "name": "Approval",
    "mainPanel": {
      "name": "preview"
    },
    "bottomPanel": {
      "name": "finish-group"
    },
    "onActivate": [
      "{{#function $['editor'].getProofImages(800,800)}}",
      "{{#function $['finish'].value = false}}"
    ]
  }
]
```

## Creating an order

To finish the personalization process, define the `order` widget. Here, the widget passes high resolution images, proof images, and other related information. 

```json
{
  "name": "order",
  "type": "order",
  "params": {
    "data": "{{ $['editor'].hiResUrls.reduce((acc, cur, i) => { acc['_downloadUrls_' + i] = cur;return acc; }, {}) }}",
    "images": "{{ $['editor'].proofImageUrls.map(u=>u[0]) }}",
    "props": {
      "_stateId": [
        "{{ $['editor'].stateId }}"
      ],
      "_userId": "{{ $['editor'].userId }}",
      "_hidden": {
        "snapshot": "{{ main.editorState }}",
        "images": "{{ $['editor'].proofImageUrls.map(u=>u[0]) }}",
        "downloadUrls": "{{ $['editor'].hiResUrls }}"
      }
    }
  }
}
```

## Results

If you use this workflow file and the `Business card` product, the results will look as follows: 

![This is the result of the How to create a workflow.](./images/how-to-result.gif)

This is the whole workflow file we created.

```json
{
  "attributes": [
    {
      "name": "Design",
      "label": "Design",
      "type": "single-asset",
      "assetType": "design",
      "description": "Choose a design associated with this product. Your design file can contain text, shapes, images, and backgrounds.",
      "validations": [
        {
          "type": "required",
          "message": "Choose a design which should be used as a template."
        }
      ]
    },
    {
      "name": "Output Resolution",
      "label": "Output Print File Resolution",
      "defaultValue": 300,
      "validations": [
        {
          "type": "min",
          "min": 1,
          "message": "Resolution should be a positive number."
        }
      ],
      "type": "number"
    }
  ],
  "vars": {
    "design": "{{product.attributes?.find(x=>x.name==='Design')?.value}}",
    "backgrounds": "{{ product.attributes?.find(x=>x.name==='Backgrounds')?.value?.fullPath?.replace(/^\\/+|\\/+$/g, '') ?? ''}}",
    "outputResolution": "{{ product.attributes.find(x=>x.name==='Output Resolution').value}}"
  },
  "widgets": [
    {
      "name": "editor",
      "type": "design-editor",
      "params": {
        "initial": {
          "productDefinition": "{{vars.design?.id}}",
          "editorConfig": {
            "fontList": {
              "appFonts": [
                "*"
              ]
            },
            "rendering": {
              "hiResOutputDpi": "{{vars.outputResolution}}",
              "hiResOutputFileFormat": "pdf",
              "hiResOutputColorSpace": "cmyk",
              "proofImageSafetyLinesEnabled": true
            }
          }
        }
      }
    },
    {
      "name": "preview",
      "type": "slider",
      "params": {
        "direction": "tile",
        "rows": 1,
        "columns": "{{Math.min(2,$['editor'].proofImageUrls.length)}}",
        "containerColor": "#eee",
        "images": {
          "{{#each $['editor'].proofImageUrls.map(s=>s[0]) as imageUrl }}": {
            "url": "{{imageUrl}}"
          }
        }
      }
    },
    {
      "name": "finish-group",
      "type": "finish-group",
      "params": {
        "checkboxPrompt": "I have reviewed and approve my design.",
        "value": false,
        "checkboxEnabled": true,
        "checkboxVisible": true,
        "buttonText": "Add to cart",
        "buttonClassStyle": "primary",
        "onClick": [
          "{{#function main.showPreloader(true, 'Creating print files...')}}",
          "{{#function $['editor'].getHiResImages(800,800)}}",
          "{{#function $['order'].widgetUpdated}}",
          "{{#function main.showPreloader(false)}}"
        ]
      }
    },
    {
      "name": "steps",
      "type": "steps",
      "params": {
        "finishButton": {
          "visible": "false"
        }
      }
    },
    {
      "name": "order",
      "type": "order",
      "params": {
        "data": "{{ $['editor'].hiResUrls.reduce((acc, cur, i) => { acc['_downloadUrls_' + i] = cur;return acc; }, {}) }}",
        "images": "{{ $['editor'].proofImageUrls.map(u=>u[0]) }}",
        "props": {
          "_stateId": [
            "{{ $['editor'].stateId }}"
          ],
          "_userId": "{{ $['editor'].userId }}",
          "_hidden": {
            "snapshot": "{{ main.editorState }}",
            "images": "{{ $['editor'].proofImageUrls.map(u=>u[0]) }}",
            "downloadUrls": "{{ $['editor'].hiResUrls }}"
          }
        }
      }
    }
  ],
  "steps": [
    {
      "name": "Editor",
      "mainPanel": {
        "name": "editor"
      }
    },
    {
      "name": "Approval",
      "mainPanel": {
        "name": "preview"
      },
      "bottomPanel": {
        "name": "finish-group"
      },
      "onActivate": [
        "{{#function $['editor'].getProofImages(800,800)}}",
        "{{#function $['finish'].value = false}}"
      ]
    }
  ]
}
```
