Bixby Developer Center

Guides
References

Writing Templates

Bixby Developer Studio includes Capsule Templates that help create capsules for specific domains, taking user input through a series of forms and then initializing the capsule with models and code based on that input.

A Bixby Template consists of:

  • A JSON package manifest file that describes the template's metadata (such as its name, description, and version number), along with custom keys that describe individual steps in a workflow for the template.
  • One or more template forms written using JSON Schema
  • Optional preprocessing and postprocessing scripts written in JavaScript which can modify the form the inputs before the template is generated.
  • A generation script, written in JavaScript, which takes the input from the template forms and outputs a JavaScript Map object with the filenames and file contents for the new capsule.

Many of the official Bixby Templates are written in TypeScript and compiled to JavaScript.

The Package Directory Structure

The package.json manifest file must be at the top level. Other files do not have requirements, but here are the conventions we follow.

  • package.json: The manifest file.
  • src/: JavaScript or TypeScript files used in template creation, including pre- and post-processing scripts and the generator script.
  • capsule/: Asset files to create the capsule, such as its models, resources, and action implementations. These can be in subdirectories within capsule/ that match Bixby's capsule structure, such as code/, models/, resources/base/, and so on; this makes it easy to copy them with the right structure by specifying them in the manifest.
  • templates/: The template form files.
  • dist/: If your capsule is written in TypeScript, by convention the JavaScript files can be output here. The example package.json file below uses this convention.

The Package Manifest

The manifest is an NPM package.json file with a bixbyTemplate key that defines template-specific metadata. Here's an example of a real package.json file from a Bixby Template:


{
"name": "fact-capsule-template",
"description": "Do you have some interesting facts about a hobby or passion to share? The Facts Template is the perfect place to share that content by building a Bixby capsule.",
"license": "UNLICENSED",
"author": "Bixby Team",
"version": "1.0.8",
"bixbyTemplate": {
"displayName": "Facts Template",
"compatibleVersion": ">=7.3.0",
"enabled": true,
"workflow": [
{
"label": "Configure your Capsule Language",
"description": "Select a language for this capsule. This will provide training examples, views, and dialogs to get started with in that language.",
"template": "./templates/language-select-template.json"
},
{
"label": "Facts",
"description": "Enter facts and optional images below to produce a capsule with facts and images. You can categorize facts by using tags and the user can ask for facts by tag, for example, \"tell me a funny fact\". [See details](https://bixbydevelopers.com/dev/docs/sample-capsules/templates/facts)",
"template": "./templates/fact-content-template.json"
}
]
},
"main": "./dist/generator.js",
"scripts": {
"build": "rollup -c",
"watch": "rollup -c -w",
"clean": "rm -rf ./dist",
"prepack": "yarn run clean && yarn run build"
},
"devDependencies": {
"rollup": "1.12.2",
"rollup-plugin-commonjs": "10.0.0",
"rollup-plugin-typescript": "1.0.1",
"tslib": "1.9.3",
"typescript": "3.4.5"
},
"files": [
"dist/*.js",
"templates/*.json",
"capsule/**/*.bxb",
"capsule/**/*.png",
"capsule/*.md"
]
}

All keys that are valid in an NPM package are valid in a Bixby Template package, along with a few extra Bixby-specific subkeys under the bixbyTemplate key.

  • name, description, license, author, and version are metadata that describe the template. Some of these are displayed in the template selection screen in Bixby Studio.
  • main is the generator script that takes the template and the user-entered data and creates a capsule.
  • files is a list of file patterns to be included as-is in capsules generated by your template. The patterns here include all .js files in dist/, all .json files in templates/, all .md files in capsule/, and all .bxb and .png files in every subdirectory of capsule/. The subdirectories will be copied in a way that preserves the path: capsule/resources/en/capsule-info.bxb will be copied to resources/en/capsule-info.bxb.
  • scripts are shell scripts and commands to be run at specific points in the package generation process. Read npm-scripts for more details.
  • bixbyTemplate contains keys specific to to Bixby Templates.
    • displayName is the name shown in Bixby Studio's UI.
    • compatibleVersion specifies the versions of Bixby Studio this template is compatible with. This should be a Semantic Version Range. ">=7.3.0" specifies that this template is compatible with Bixby Studio version 7.3.0 and higher.
    • enabled is a boolean which must be set to true for the template to be available in Bixby Studio.
    • For workflow, see below.
  • devDependencies are Node dependencies that should be installed for development. In this example, these dependencies are for TypeScript support; see Using Typescript for more details.

Like other NPM packages, version should follow semantic versioning practices.

Defining the Workflow

Each step in the workflow is a JSON object in the workflow subkey of bixbyTemplate. The example above has two steps in its workflow:

{
"workflow": [
{
"label": "Configure your Capsule Language",
"description": "Select a language for this capsule. This will provide training examples, views, and dialogs to get started with in that language.",
"template": "./templates/language-select-template.json"
},
{
"label": "Facts",
"description": "Enter facts and optional images below to produce a capsule with facts and images. You can categorize facts by using tags and the user can ask for facts by tag, for example, \"tell me a funny fact\". [See details](https://bixbydevelopers.com/dev/docs/sample-capsules/templates/facts)",
"template": "./templates/fact-content-template.json"
}
]
}

The first step prompts the user to select a language for the capsule, using the form defined in language-select-template.json; the second step lets the user enter facts, using the form defined in fact-content-template.json.

There are three required keys and three optional keys for each step:

  • label sets the title used for this step when displayed in Bixby Studio.
  • description displays underneath the label in Bixby Studio.
  • template is the template form. Usually, as in this example, this is set to a string giving the file path of a separate JSON template file. However, this can be a JSON object definition of the actual template.
  • preprocess is an optional file path for a script to run before the template is displayed to the user. See Processing Scripts.
  • postprocess is an optional file path for a script to run after the template is displayed to the user. See Processing Scripts.
  • nextButtonLabel is an optional label to use instead of "Next" for continuing onto the next step in the workflow.

Template Forms

Each step in a workflow has a form associated with it, defined using JSON Schema. JSON Schema is a way to define metadata about JSON objects, such as their names, types, and possible values, by using JSON itself.

Let's look at both of the forms from the Facts template described above. The first is the language selection form. This should return a JSON object with just one key-value pair: the key language and one of the possible languages, a string that's either English (en) or Korean (ko-KR).

{ "language": "English (en)" }

The JSON Schema for this object looks like this:

{
"title": "Select a language",
"type": "object",
"properties": {
"language": {
"type": "string",
"title": "Available Languages",
"enum": [
"English (en)",
"Korean (ko-KR)"
],
"default": "English (en)"
}
}
}

And this schema is also a valid Bixby Template form: it's the entire contents of language-select-template.json.

  • The form's title is "Select a language".
  • It returns a value type of (JSON) object. All Bixby Template forms must return this.
  • The returned object has one property whose key is language:
    • The value of language must be a string.
    • The language is one of two possible enum values, so it will be rendered as a dropdown element in the created form.
    • The title for the form element is "Available Languages".
    • The default value is "English (en)".

Bixby Studio renders this form like this:

Because there's another step in this workflow, clicking "Next Step" will advance to the next form, defined in fact-content-template.json. This file has a more complicated schema:

{
"type": "object",
"properties": {
"facts": {
"minItems": 1,
"type": "array",
"items": {
"type": "object",
"properties": {
"text": {
"type": "string",
"title": "Fact Text"
},
"image": {
"type": "object",
"title": "Fact Image",
"bxb:ui": {
"mode": "media-picker",
"options": {
"extensions": [
"png",
"gif",
"jpg"
]
}
}
},
"tags": {
"title": "Tags",
"type": "array",
"minItems": 0,
"maxItems": 10,
"items": {
"title": "Fact Tag",
"type": "string"
},
"bxb:ui": {
"options": {
"addLabel": "Add Tag",
"removeLabel": "Remove Tag"
}
}
}
},
"required": [
"text"
]
}
}
},
"required": [
"facts"
]
}
  • The JSON object returned here has one key, facts, whose value is an array.
  • The valid items in the array have a type of object.
  • Each item has up to three properties, which can be the following:
    • text is a string, so it will simply be a free-form text input field, with a title of "Fact Text".
    • image is an object, with a title of "Fact Image". This has a special key, bxb:ui, which tells Bixby Studio how to present special UI elements.
      • The mode of media-picker tells Bixby Studio to allow a file upload here.
      • The options require uploaded files to have extensions of png, gif, or jpg.
    • tags is also an array, which can have 0 (zero) to 10 strings. This also has a bxb:ui key, which tells Bixby Studio to present custom labels for adding and removing tags.
  • Finally, only the text property is required for each object in items.
  • The top-level facts key is required, which means there must be at least one fact in the generated facts capsule.

This renders the following form:

Because facts is an array, Bixby Studio provides a button for adding new items to the array. Also, because tags is an array, each item presents an "Add Tag" button, defined in the bxb:ui key under tags.

JSON Schema has an extensive specification that can be overwhelming. It's helpful to read the "Getting Started Step-by-Step" guide first!

There are a few other Bixby-specific form elements you can create with the bxb:ui key. Read Custom Form Elements for more details.

Note

Bixby Templates does not support all JSON Schema features. In particular, you can't use:

  • Multiple values for type
  • $ref definitions
  • Dependent properties
  • Pattern properties
  • Schema combinations

Processing Scripts

Each workflow step can have a preprocess script and a postprocess script associated with it that can take additional steps in processing the form data both before and after the workflow step is presented to the user.

Preprocessing

The preprocess script can be used to customize the template structure based on the input to previous workflow steps. For instance, a template for creating flight capsules might change the list of airport codes presented in one step based on a selection in the previous step. This script receives two objects: the previous step's form input and the current step's template form object. It returns a template form object to display to the user.

A preprocessing script exports a function named preprocess which receives two parameters:

  • model: Either an Object containing the previous workflow step's form input or null
  • template: An Object containing the current workflow step's template

The function must return an Object containing the current workflow step's template. This can be the template object passed to the function, either unchanged or modified by the script.

This example preprocess script adds an additional property, availableUsStates, to the template in its workflow step if the property uniqueUsStates exists on the previous model's form input.

export function preprocess(model, template) {
if (!model || !model.uniqueUsStates) {
return template
}
const availableUsStates = {
type: 'array',
items: {
enum: model.uniqueUsStates
}
}
return {
...template,
availableUsStates
}
}

Postprocessing

The postprocess script can be used to modify the user's form input before it's passed on to the next workflow step or before the capsule is generated. It can also be used to validate input. If this script throws an error, the form will be redisplayed to the user along with the error message.

A postprocess script exports a function named postprocess which receives one parameter:

  • model: An Object containing the current workflow step's form input

The function must return an Object containing the current workflow step's form input. This can be the model object passed to the function, either unchanged or modified by the script.

This example postprocess script checks the form input to ensure that importantValue has been set, and throws an error if it was not.

export function postprocess(model) {
if (!model.importantValue) {
throw new Error(`The value "importantValue" has not been set`)
}
return model
}

The Generator Script

The generator script is specified with the main top-level key in the package.json manifest file.

This script exports a function named generate, which receives two parameters:

  • context: an Object containing the results of all the evaluated steps. The context object has two keys:
    • capsuleId: A string that specifies the generated capsule ID, provided by the user.
    • evaluatedWorkflowSteps: an array of Object items consisting of the values input to each form for each workflow step (after postprocessing, if applicable)
  • workflow: An array of Object items consisting of each template object from each workflow step (after preprocessing, if applicable)

This function should return a Map object containing the new capsule's files:

  • keys are complete file paths, relative to the capsule's root directory, that give the filename of a file to create in the generated capsule (for instance, models/Content.mode.bxb)
  • values provide the contents of the files. Values can be either:
    • A string containing the file content
    • A function returning a string
    • A string using the special file-template:// URI prefix

The file-template:// URI specifies a file in the template to simply copy as-is to the generated capsule. This can be used for media files, icons, static *.bxb files, and so on.

This is part of the generator.ts script from the Facts capsule. It's written in TypeScript; see Using TypeScript for details on how to use this superset of JavaScript to produce templates.

interface IGenerateContext {
capsuleId: string
evaluatedWorkflowSteps: {
schema: object
formInput: any
}[]
}

export function generate(context: IGenerateContext): Map<string, string> {

const languagesForm = context.evaluatedWorkflowSteps[0].formInput
const factContentTemplate = context.evaluatedWorkflowSteps[1]

const filesystemMap = new Map()

const { facts } = factContentTemplate.formInput

facts.forEach((fact) => {
if (fact.image) {
filesystemMap.set(`assets/images/${fact.image.filename}`, fact.image.filepath)
}
})

// Map facts object to the expected format in the JS file
const factsJson = facts.map((fact) => {
let result = {
text: fact.text,
}

if (fact.image && fact.image.filename && fact.image.filepath) {
result['image'] = {}
result['image']['url'] = 'images/' + fact.image.filename
}

if (fact.tags) {
// Filter out tags that are empty
result['tags'] = fact.tags.filter(Boolean)
}

return result
})

filesystemMap.set('code/content.js',
`module.exports = ${JSON.stringify(factsJson, null, 2)}`)

// Copy static files using template-file://
filesystemMap.set(`/resources/${resourceFolder}/capsule-info.bxb`,
`template-file://capsule/resources/${resourceFolder}/capsule-info.bxb`)
filesystemMap.set(`/resources/${resourceFolder}/example-fact.hints.bxb`,
`template-file://capsule/resources/${resourceFolder}/example-fact.hints.bxb`)

return filesystemMap
}

Every file in the generated capsule must be in the returned Map object. The actual generator.ts script is longer, creating language-specific resource folders, generating the capsule.bxb file, copying a capsule-specific README file, and more.

Custom Form Elements

While Bixby Studio can generate most form elements from standard JSON Schema, there are a few Bixby-specific elements you can use.

CSV Upload

You can allow template creators to upload data in CSV files that can be processed by your scripts. This is used, for example, in the Import and Search Template, which generates models from CVS files.

To use the CSV upload form, define a object type with the bxb:ui object, and set its mode key to the value spreadsheet-loader.

You can set the label for the upload button by adding an options object with a key of selectLabel.

{
"csv": {
"title": "Related Concept Data CSV File",
"type": "object",
"bxb:ui": {
"mode": "spreadsheet-loader",
"options": {
"selectLabel": "Upload CSV"
}
}
}
}

In this example, the returned object is csv. The data is available as a two-dimensional array in the object's data property; CSV metadata is available in the object's meta property. For a CSV file of:

ID,HotelName,Stars
10,Sheraton,3.5
11,Hilton,4.0
12,Marriott,4.0

The data property would be:

[
["ID", "HotelName", "Stars"],
["10", "Sheraton", "3.5"],
["11", "Hilton", "4.0"],
["12", "Marriott", "4.0"]
]

You can access this with zero-based array indexing:

csv.data[1] == ["10", "Sheraton", "3.5"];
csv.data[1][1] == "Sheraton";

The meta key will contain a key-value dictionary with some information about the CSV file:

  • delimiter: the delimiter character used
  • linebreak: the linebreak sequence used

Media Picker

Your template can prompt users to upload media files for use in their generated capsules. The Facts template, for example, allows creators to add their own images for display.

To add a media picker for the form, add an object element to the form with the custom bxb:ui field. This should have a mode key defined with media-picker as the value.

To limit which file types can be selected by a user, add an object key with an extensions array listing the valid file extensions.

"image": {
"type": "object",
"title": "Fact Image",
"bxb:ui": {
"mode": "media-picker",
"options": {
"extensions": [
"png",
"gif",
"jpg"
]
}
}
}

In this example, the returned object is image. This will have two properties: filename, the user-provided name for the uploaded file, and filepath, the path to the actual uploaded file. You can use these to copy the user's upload to a target filepath in the generated capsule.

const filesystemMap = new Map()

filesystemMap.set(`assets/${formInput.image.filename}`,
formInput.image.filepath)

Testing a Template

While you're developing your Bixby Template, you can load it into Bixby Studio from the "Select a template" screen:

  1. Choose "New Capsule from Template..." in the File menu or "Create Capsule from Template" in the Add to Workspace menu.
  2. Scroll to the bottom of the list of templates and click Developing a template locally?
  3. A new prompt for the Template Path will appear. Click Select Template and choose the directory that contains the package.json file for your new template.
  4. Click Load Template.
  5. Bixby Studio will run a validation check on your template before loading it. If there are errors, they will appear on this screen.

Using TypeScript

As shown above, you can use TypeScript to write templates. The files in the generated capsule, however, must use JavaScript, and the generator script run by Bixby Studio must be JavaScript. Therefore, you need to compile your TypeScript before loading the template into Bixby Studio.

In the Facts capsule generator example we've been using, the devDependencies block in the package.json file specifies dependencies for compiling TypeScript using Yarn and Rollup, supplying clean and build commands for yarn in the scripts block. Rollup is configured to take the src/generator.ts file as input and output dist/generator.js:

// rollup.config.js
import commonjs from 'rollup-plugin-commonjs'
import typescript from 'rollup-plugin-typescript'

export default [
{
input: 'src/generator.ts',
output: {
file: 'dist/generator.js',
format: 'iife',
exports: 'named',
name: 'context',
footer: '\ngenerate = context.generate'
},
plugins: [
typescript({
module: 'CommonJS'
}),
commonjs({
extensions: ['.js', '.ts']
})
]
}
]

After installing packages with yarn install, you'll be able to generate the JavaScript files in the dist/ directory with yarn build.