App configuration

Edit on GitHub

Instead of writing different components for different App configurations, we use JSON to determine the form for the configuration of the apps. There is a playground for NgxSchemaForm. There are predefined form elements and also custom form elements.

Info

For the app configuration translation, see App configuration translation. For information about the translation, see Translation appendix.

Apps configuration form

Configuration widget JSON format:

{
    "properties": {
        <fieldId>: {
            "widget": <widgetId>,
            ...otherFieldProperties
        },
    },
};

Example of the configuration widget:

{
  "properties": {
    "client_name": {
      "type": "string",
      "title": "Client Name",
      "placeholder": "Enter the client name",
      "isRequired": true,
      "description": "Name of the client",
      "widget": "string"
    }
  }
}

Display on the frontend:

configuration-widget

Widget catalog

Common properties of a widget are:

  • type: widget type
  • widget: widgetId—see the available widgetIDs in the following sections
  • title: display title of the field
  • placeholder: string as a placeholder for the input field
  • description: additional description for the field
  • isRequired: determines if the field is required with the true and false values
  • default: default value of the field

Form input widget

widgetId display Description
string Default string input widget that allows the user to input a single line string.
number
Allows the user to enter a number.
date Allows the user to enter a date.
time Allows the user to enter time.
password Allows the user to enter a masked string that is used for passwords or API keys.
textarea Allows the user to enter a multi-line string.
file Allows the user to upload a file.
link As a tag:
As a button:
Displays a link as a tag or a button.
Required properties:
isButtonLink: false—display as a tag, true—display as a button
.url: the url
.target: set to _blankto open the url in a new tab
.variant: (for button only)—primary or secondary button.
See the link example under this table.
notification The notification widget is used to inform the user of some additional information. There cannot be any user input for this type of widget.
Properties:
  • notificationType:info/warning/error/success—type of the notification.
  • content: Content of the notification widget—accepts an HTML string.

  • Seenotification exampleunder this table.
link example
{
  "properties": {
    "credentials_clientName": {
      "type": "string",
      "widget": {
        "id": "link"
      },
      "target": "_blank",
      "url": "http://google.com",
      "isButtonLink": true,
      "variant": "primary",
      "title": "click to google"
    }
  }
}
notification example
{
  "properties": {
    "notification": {
      "type": "string",
      "widget": {
        "id": "notification"
      },
      "notificationType": "info",
      "content": "Don’t have credentials yet? Visit <a href=\"https://google.com/\" target=\"_blank\">admin.usercentrics.eu/#/login</a> to create your account."
    }
  }
}

Selection widget

widgetId display Description
checkbox/ boolean This widget is of type boolean, the input value can be either true or false.
radio The radio widgets rely on the oneOf property. The description is the displayed label for an option, while the enum is the value that is saved in the form. For example, if we select OSX in the form, the actual saved value is osx.
Properties:
oneOf: list of objects used as options.
See radio example under this table.
select Single selection: Image Multi-selection: Similar to the radio widget, but instead of radio buttons, you can use select for selection in a dropdown. Besides, this widget also allows multiple options selection by setting multiple: true.
Properties:
  • oneOf: list of objects used as options.
  • multiple: true or false
  • multipleOptions: list of strings to be used as options. Can be used instead of oneOf.
  • See example for select with oneOf under this table.
In case you don’t need to have separate labels and values, you can use multipleOptions or enum instead of oneOf.
See example for select without oneOf under this table.
app-status This custom widget shows only Active (true) or Inactive (false) value to fit the design.
See app-status example under this table.
radio example
{
  "properties": {
    "info": {
      "type": "string",
      "title": "Operation system",
      "description": "Operation system for the demo store",
      "widget": "radio",
      "oneOf": [
        {
          "enum": ["linux"],
          "description": "GNU/Linux"
        },
        {
          "enum": ["osx"],
          "description": "OSX"
        },
        {
          "enum": ["windows"],
          "description": "Windows"
        },
        {
          "enum": ["other"],
          "description": "Other"
        }
      ],
      "default": "other"
    }
  }
}

example for select with oneOf
{
  "properties": {
    "info": {
      "type": "string",
      "title": "Operation system",
      "description": "Operation system for the demo store",
      "widget": "select",
      "multiple": true,
      "oneOf": [
        {
          "enum": ["linux"],
          "description": "GNU/Linux"
        },
        {
          "enum": ["osx"],
          "description": "OSX"
        },
        {
          "enum": ["windows"],
          "description": "Windows"
        },
        {
          "enum": ["other"],
          "description": "Other"
        }
      ],
      "default": "other"
    }
  }
}

example for select without oneOf
{
  "properties": {
    "info": {
      "type": "string",
      "title": "Store",
      "description": "Store location",
      "widget": "select",
      "multipleOptions": [
        "DE",
        "AT",
        "US"
      ],
      "multiple": true
    }
  }
}

app-status example
{
  "properties": {
    "application_status_isActive": {
      "type": "boolean",
      "title": "Application status",
      "hint": "Setting to Active will make the app visible on your Storefront",
      "widget": {
        "id": "app-status"
      },
      "default": false
    }
  }
}

Form layout

For the form layout, in particular, you should take into consideration the array type and the order of fields.

Array type

Sometimes, you might ask a user to input one or more values of the same field sets. For example, when you ask for all the pets within a household. These values can differ from each other, as there could be 0, or 10, or 100 pets. In these cases, you can use the array type of the form:

{
  "properties": {
    "pet": {
      "type": "array",
      "addButtonText": "Add pet",
      "items": {
        "type": "object",
        "properties": {
          "petType": {
            "type": "string",
            "title": "Pet type",
            "placeholder": "Dog/ Cat, etc."
          },
          "petName": {
            "type": "string",
            "title": "Pet name"
          }
        }
      }
    }
  }
}

Where:

  • addButtonText: text of the Add button.
  • items: definition of fields within an item. The form returns an array of objects defined in items: array-type The returned object looks like this:
{
  "pet": [
    {
      "petType": "Dog",
      "petName": "Lulu"
    },
    {
      "petType": "Cat",
      "petName": "Meow meow"
    }
  ]
}

Order of fields

The properties within the form configuration are to define the form’s fields and their corresponding widgets. By default, the fields are displayed in that order also. In some cases, you can use the order or fieldsets property to change the order of the fields.

The order property changes the order of the fields within the form:

{
  "properties": {
    "firstName": {
      "type": "string",
      "description": "First name"
    },
    "lastName": {
      "type": "string",
      "description": "Last name"
    },
    "email": {
      "type": "string",
      "description": "Email"
    }
  },
  "order": ["lastName", "firstName", "email"]
}

Here is how it looks on the frontend:

order-property

To group different fields into different sections, you can define the fieldsets property:

{
  "properties": {
    "firstName": {
      "type": "string",
      "description": "First name"
    },
    "lastName": {
      "type": "string",
      "description": "Last name"
    },
    "email": {
      "type": "string",
      "description": "Email"
    }
  },
  "fieldsets": [
    {
      "id": "client_name",
      "title": "Client name",
      "fields": ["firstName", "lastName"]
    },
    {
      "id": "client_email",
      "title": "Client email",
      "fields": ["email"]
    }
  ]
}

This is how it looks on the frontend:

fieldsets-property

If you don’t want any layout for a section, you can set noLayout to the layout property:

{
  "properties": {
    "firstName": {
      "type": "string",
      "description": "First name"
    },
    "lastName": {
      "type": "string",
      "description": "Last name"
    },
    "email": {
      "type": "string",
      "description": "Email"
    }
  },
  "fieldsets": [
    {
      "id": "client_name",
      "title": "Client name",
      "layout": "noLayout",
      "fields": ["firstName", "lastName"]
    },
    {
      "id": "client_email",
      "title": "Client email",
      "fields": ["email"]
    }
  ]
}

This is how it looks on the frontend: layout-property

Full configuration example

Here is an example of the full configuration:

Full configuration example
{
  "properties": {
    "notification": {
      "type": "string",
      "widget": {
        "id": "notification"
      },
      "notificationType": "info",
      "content": "Don’t have credentials yet? Visit <a href=\"https://google.com/\" target=\"_blank\">admin.usercentrics.eu/#/login</a> to create your account."
    },
    "userCentricIntegrationType": {
      "type": "string",
      "widget": {
        "id": "radio"
      },
      "oneOf": [
        {
          "description": "Enable Smart Data Protector (Default)",
          "enum": ["SMART_DATA_PROTECTOR"]
        },
        {
          "description": "Enable Direct Integration (works only with Google Tag Manager)",
          "enum": ["DIRECT"]
        }
      ]
    },
    "storeSettings": {
      "type": "array",
      "addButtonText": "Add Store Settings",
      "items": {
        "type": "object",
        "properties": {
          "storeName": {
            "type": "string",
            "title": "Store",
            "widget": {
              "id": "select"
            },
            "multiple": true,
            "multipleOptions": [
              "AT",
              "DE",
              "US"
            ]
          },
          "userCentricSettingIdentifier": {
            "type": "string",
            "title": "Setting ID"
          },
          "isActive": {
            "type": "boolean",
            "title": "Store setting is active",
            "widget": {
              "id": "checkbox"
            },
            "default": false
          }
        },
        "fieldsets": [
          {
            "id": "storeSettings",
            "fields": [
              "storeName",
              "userCentricSettingIdentifier",
              "isActive"
            ]
          }
        ],
        "widget": {
          "id": "object"
        }
      },
      "widget": {
        "id": "array"
      }
    }
  },
  "fieldsets": [
    {
      "id": "notifications",
      "layout": "noLayout",
      "fields": ["notification"]
    },
    {
      "id": "usercentric_integration",
      "title": "Usercentric Integration",
      "name": "usercentric_integration",
      "fields": ["userCentricIntegrationType"]
    },
    {
      "id": "usercentric_stories",
      "title": "User consent managment per store",
      "name": "usercentric_integration",
      "fields": ["storeSettings"]
    }
  ]
}

This is how the configuration looks in the Back Office:

full-configuration

Translation appendix

Translation for an app configuration is provided in the app-store-suite/app/config/<app-name>/translation.json file. Each field defined in the JSON’s properties needs to match its corresponding translation entity in the translation.json file. For example, to translate the title of the widget isLiveMode, we provide "title": "isLiveMode" in the app configuration JSON.

translation.json file example:

{    
    "isLiveMode": {
        "de_DE": "Auswahl des Modus für Payone Umgebung",
        "en_US": "Select Payone Environment Mode"
    },
    "isLiveMode_test": {
        "de_DE": "Test",
        "en_US": "Test"
    },
    "isLiveMode_live": {
        "de_DE": "Live",
        "en_US": "Live"
    },
    "payoneEnvironmentMode": {
        "de_DE": "Payone Umgebung",
        "en_US": "Payone Environment Mode"
    }
}

configuration.json file example:

{
  "properties": {
      "isLiveMode": {
        "type": "string",
        "title": "isLiveMode",
        "widget": {
            "id": "radio"
        },
        "oneOf": [
            {
                "description": "isLiveMode_test",
                "enum": ["0"]
            },
            {
                "description": "isLiveMode_live",
                "enum": ["1"]
            }
        ]
      },

  }
}

This is how the result of the configuration looks on the frontend:

DE shop: configuration-de-shop

EN shop: configuration-en-shop

Form validation (server side)

The app will receive the request from the configuration form to the URL defined in your api.json file, for example, like this:

{
    "configuration": "/private/configure",
    "disconnection": "/private/disconnect"
}

In the cURL request, it will look like:

curl -x POST 'https://your-app.domain.name/private/configure' \
-H 'Accept-Language: en' \
-H 'X-Tenant-Identifier: tenant-uuid' \
--data-raw '{
  "data": {
    "attributes": {
      "configuration": "{\"fieldName1\":\"value1\", \"fieldName2\":\"value2\"}"
    }
  }
}'

The app response format for a valid request is just the HTTP status 200.

The app response format for an invalid request could be an error for the whole form:

{
  "errors": [
    {
      "code": 443,
      "message": "human readable message for a user, localized",
      "status": 400
    }
  ]
}

or field-specific messages in the following format:

{
  "errors": [
    {
      "code": 444,
      "message": "{"\"fieldName1\": \"errorMessage\", \"fieldName2\": \"errorMessage\"}",
      "status": 400
    }
  ]
}

In both cases HTTP status has to be 400.

Error number limitation

The current limitation for the number of displayed errors from the app response is 1.