Custom widget development
This content is intended for advanced Studio users.
Widgets are embeddable UI components that run inside Better Studio forms. Better Studio provides a library of pre-built widgets in the Marketplace, covering clinical tools such as body charts, timeline views, and specialist scoring tools. When no Marketplace widget meets your requirement, you can build your own.
Prerequisites: Custom widget development requires JavaScript, HTML, and CSS skills. Familiarity with the Studio Script API is recommended for widgets that send actions to or receive events from the form. Before building a widget, check the Marketplace to confirm that an existing widget does not already cover your use case.
When to build a custom widget
| Situation | Recommendation |
|---|---|
| A Marketplace widget already meets your requirement | Use the Marketplace widget |
| A Marketplace widget partially meets your requirement | Contact the widget author or raise a feature request before building from scratch |
| No Marketplace widget exists for your clinical or UI need | Build a custom widget |
| You need to integrate with a proprietary external system | Build a custom widget |
| Your organisation has strict UI/UX constraints that Marketplace widgets cannot meet | Build a custom widget |
Widget architecture
A widget is a web component with up to two inputs, two outputs, and a schema descriptor. The inputs and outputs define how the widget communicates with the Studio form host. The schema descriptor defines what configuration options Studio exposes to the user in Form builder.
The four interaction properties, plus the schema, are:
| Property | Direction | Description |
|---|---|---|
config | Host → Widget | Static configuration set by the Studio user in Form builder. Not dynamic; cannot change during widget runtime. |
inputData | Host → Widget | Dynamic data passed to the widget, bound to a form field, variable, or data connector. Updates as the source changes. |
outputData | Widget → Host | Data emitted by the widget back to the form. Must match the same schema as inputData. Stored in the composition in the same way as generic field data. |
outputAction | Widget → Host | Action events emitted by the widget to trigger form behaviour. |
useDefaultData | Host → Widget | When not set to false, the widget should display its built-in placeholder data. Set to false by Studio in Playground and in live forms. |
Configuration is not mandatory. If your widget has no configurable settings, you do not need to implement config.
Framework templates
Better provides starter templates for the three most common frameworks. All templates implement the same input/output interface and schema specification.
- Angular: https://github.com/better-care/angular-widget-template
- Svelte: https://github.com/better-care/svelte-widget-template
- React: https://github.com/better-care/react-widget-template
Documentation and examples in the templates use Angular conventions, but the concepts apply to any framework. The end result in all cases is a self-contained web component.
The descriptor schema
The descriptor schema defines what Studio renders in the configuration panel when a user adds your widget to a form. It has two root properties:
valueModel: Defines the schema for inputData (and therefore outputData)configuration: Defines the schema for config
Each property in the schema uses one of six types: boolean, string, number, enum, object, or list. Primitives (boolean, string, number, enum) can also define defaultValue, required, and metadata (title and description for display in Studio).
Example: A pie chart descriptor
descriptor = {
valueModel: {
type: 'list',
listModel: {
type: 'object',
objectModel: {
value: { type: 'string' },
label: { type: 'string' }
}
}
},
configuration: {
chartType: {
type: 'enum',
values: ['pie', 'doughnut'],
defaultValue: 'pie',
metadata: {
title: 'Chart type',
descriptor: 'Defines whether the chart is displayed as a pie or doughnut.'
}
},
hideLegend: { type: 'boolean' }
}
};
Studio renders this descriptor as a configuration panel in Form builder. The chartType property appears as a dropdown with the two enum options. The hideLegend property appears as a toggle.
Connecting a widget to form data
Bind inputData to a form field, variable, or data connector in the Form builder data mapping panel. Studio passes the bound value to your widget each time it changes. Your widget reads the value from the inputData input and renders accordingly.
If inputData is bound to a parent component (such as a column list or stacked list), the widget inherits the data binding properties of that parent.
Writing data back
For input widgets, emit the updated value via outputData. The schema of outputData must match valueModel. Studio stores the emitted value in the composition in the same location as a generic field bound to the same data path.
Angular example:
@Input()
inputData: any;
@Output()
outputData: EventEmitter<any> = new EventEmitter<any>();
onUserSelection(value: any) {
this.outputData.emit(value);
}
Default data
Your widget must handle the case where it is placed on the canvas but no data has been bound yet. Use useDefaultData to detect this state and render appropriate placeholder content.
@Input()
useDefaultData: boolean | undefined;
When useDefaultData is not false, show your widget's built-in default data. Studio sets it to false when rendering the form in Form playground or in a live deployment.
Uploading to the Marketplace
Once your widget is built and compiled to a web component bundle, upload it to your library on Marketplace from within Studio.
You always have access to the public library in Marketplace. However, submitting or uploading content to it is restricted. For more information on how to share your widgets in the public library, check the Marketplace section.
To upload a widget onto your private library:
- Open Marketplace from the Studio main screen or from the Widgets tab in the Generics fields panel.
- Click Sign in (top right). Studio creates a Marketplace account using your Studio credentials if one does not already exist.
- Once signed in, click Upload.
- Select your widget file.
- In the upload modal, choose which library to publish to. Libraries are organisation-controlled repositories. Members of a library can see and install content published there.
- Complete the metadata: Name, description, version, keywords, and any supporting content (AQL views, API calls).
- Optionally add documentation, screenshots, a "What's new" note, and attachments.
- Submit the upload.
Upload permissions are required. To request your own library, or for any admin actions beyond adding library members and managing content, contact tools@better.care.
Widget presets
A preset is a saved configuration object for a widget. Presets let Studio users apply a known configuration in one click rather than filling in every option manually.
To create a preset:
- Open the widget in the Marketplace.
- Navigate to the presets section.
- Save the current configuration as a named preset.
Presets can be set as the default configuration for the widget. When a widget with presets is installed, the user can select a preset during installation.
Preset supporting content: If a preset's configuration references an AQL view or an API call, both can be bundled with the preset as supporting content when it is uploaded. This ensures that users who install the preset have all the dependencies they need.
Frequently asked questions (FAQs)
What is the difference between a read-only widget, an input widget, and an action widget?
Read-only widgets display data without accepting user input — for example, a patient banner or a chart. Input widgets accept user input and store the entered data in the composition in the same way as generic fields. Action widgets trigger actions — for example, buttons that initiate a workflow step.
Does outputData need to match the schema of inputData?
Yes. outputData must conform to the same schema as inputData as defined in the valueModel of your descriptor. If your widget is an input widget, the data it emits is stored in the composition using the same structure as the data it receives.
What is useDefaultData and why does my widget need to handle it?
useDefaultData signals whether your widget is being rendered in the Form builder canvas (design time) or in a live form or Form playground (runtime). When it is not set to false, your widget should display its built-in placeholder data so that the widget appears meaningful on the canvas before any real data is bound. Studio sets it to false in Form playground and in live deployments.
Can I update my widget's configuration at runtime from a script?
Yes. Use api.setWidgetConfig(tagOrPath, config) from the Script API to update a widget's configuration object at runtime. You can also send named actions to a widget using api.sendActionToWidget(tagOrPath, action, value).
Can I bind my widget to a data connector or AQL view rather than a form field?
Yes. The inputData binding supports form fields, variables, and data connector or API results. Configure the binding in the data mapping panel in Form Builder when placing the widget on the canvas.
What happens if my widget is placed inside a column list or stacked list?
If a widget resides inside a parent component such as a column list or stacked list, it inherits the data binding properties of that parent. You do not need to configure the binding separately for each row.
What are presets and how do I make my widget support them?
A preset is a saved configuration object for a widget. To support presets, set "supportsPresets": true in your widget manifest. Studio users can then save their configuration as a named preset and upload it to a Marketplace library. If a preset references an AQL view or API call, both can be bundled as supporting content. Users installing the widget can select a preset to apply during installation.
Can I set a default preset for my widget?
Yes. In the Marketplace, presets can be set as the default configuration for a widget. When a user adds the widget to a form, the default preset configuration is applied automatically and can then be adjusted as needed.
How do I make my widget available to other users in my organisation?
Upload your widget to your organisation's Marketplace library. Members of that library can then find, install, and use it in their forms. To make your widget available to users outside your library, contact tools@better.care.
I have a suggestion for a new widget capability, or I found an issue with the documentation on this page. Where should I report it?
Use the feedback link on the side of this page.