Botpress is an excellent bot framework, which is on-prem, and open-source, with plenty of options for customizations. This already has some built-in components like text, card, carousel, image, and dropdown, but what if we want something entirely different. Let us create a custom component in Botpress?
In this tutorial, we are going to create a chart component to visualize our data. We are going to use primefaces to build this component which internally uses Chart.js.
Note: Botpress uses Reactjs to create components. Check out What, Why and First React Code tutorial for a quick 10 minutes read on getting started with Reactjs.
Before we can jump in to create our custom component, one needs to create a custom module. Please follow along this guide to create your custom module, then come back and resume.
Ok assuming you have created a custom module named infa-module, you should get the details of your module in the modules\infa-module\src\backend\index.ts file.
In order to use primereact first add it as your dependency
yarn add primereact
A custom component needs 3 items
- A controller
- A content type
- Registration of this component in your bot.
Controller
Create your component controller inside your module modules\infa-module\src\views\lite\components\InfaChart.jsx
Note: For custom css classes please refer here.
import React from 'react'
import { Chart } from 'primereact/chart';
const noAnswer = "Sorry no answer found."
const NoAnswerMessage = () => {
return (
<div className="infaTextMain">
<p className="infaTextMessage">{noAnswer}</p>
<small className="infaTextTimestamp"></small>
</div>
)
}
export class InfaChart extends React.Component {
data = this.props.response.data.user.response.data;
options = this.props.response.data.user.response.options;
type = this.props.response.data.user.response.type;
componentDidMount() {
this.options = this.props.response.data.user.response.options;
}
getTimestamp = () => {
let date = new Date();
let options = {
month: "short",
day: "numeric", hour: "2-digit", minute: "2-digit"
};
return date.toLocaleTimeString("en-us", options);
}
render() {
if (this.data.datasets[0].data.length > 0) {
return (
<Chart type={this.type} data={this.data} options={this.options} />
)
} else {
return <NoAnswerMessage />;
}
}
}
add this component controller in your lite index file modules\infa-module\src\views\lite\index.jsx
export { InfaChart } from './components/InfaChart'
Content Type
Create a content type in modules\infa-module\src\content-types\infa-chart.js
const base = require('./_base');
function render(data) {
const events = [];
return [...events, {
type: 'custom',
module: 'infa-module',
component: 'InfaChart',
text: data.text,
response: {
data
}
}]
}
function renderMessenger(data) {
return [{
type: 'typing',
value: data.typing
}, {
text: data.text
}];
}
function renderElement(data, channel) {
if (channel === 'web' || channel === 'api') {
return render(data);
} else if (channel === 'messenger') {
return renderMessenger(data);
}
return [];
}
module.exports = {
id: 'infa_chart',
group: 'Custom Component',
title: 'Infa Chart',
jsonSchema: {
description: 'Chart type is defined using the type property. Currently there are 2 options available; "doughtnut", "bar".',
type: 'object',
required: ['text'],
properties: {
text: {
type: 'string',
title: 'Message'
},
variations: {
type: 'array',
title: 'Alternates (optional)',
items: {
type: 'string',
default: ''
}
},
...base.typingIndicators
}
},
uiSchema: {
text: {
'ui:field': 'i18n_field',
$subtype: 'textarea'
},
variations: {
'ui:options': {
orderable: false
}
}
},
computePreviewText: formData => formData.text && 'Infa Chart: ' + formData.text,
renderElement: renderElement
}
Registration
Enable this component by adding the component id inside #contentTypes of out\bp\data\bots\<BOT_ID>\bot.config.json file.
That’s it, now build and start Botpress server.
yarn && yarn build && yarn start
If everything is fine you should see your new shiny custom component under the Pick Content menu.


modules\infa-module\src\actions\getsampledata.js
//const axios = require('axios');
/**
* @title Get sample data for chart components
* @category Utility
* @author: Abhishek Raj Simon
* @param {any} data - Query value you want to send in payload
*/
const defaultColor = '#42A5F5';
const color = ['#EC407A',
'#AB47BC',
'#42A5F5',
'#7E57C2',
'#66BB6A',
'#FFCA28',
'#26A69A'
];
const abbreviatedNumber = (value) => {
let newValue = value;
console.log(value);
if (value >= 1000) {
let suffixes = ["", "k", "m", "b", "t"];
let suffixNum = Math.floor(("" + value).length / 3);
let shortValue = '';
for (let precision = 2; precision >= 1; precision--) {
shortValue = parseFloat((suffixNum !== 0 ? (value / Math.pow(1000, suffixNum)) : value).toPrecision(precision));
let dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g, '');
if (dotLessShortValue.length <= 2) {
break;
}
}
if (shortValue % 1 !== 0) shortValue = shortValue.toFixed(1);
newValue = shortValue + suffixes[suffixNum];
}
return newValue;
}
const noAnswer = "Sorry no answer found."
const barOptions = {
plugins: {
labels: {
render: 'value',
fontSize: 10,
fontStyle: 'bold',
fontColor: '#000',
fontFamily: '"Lucida Console", Monaco, monospace'
}
},
legend: {
display: false,
position: 'bottom'
},
scales: {
xAxes: [{
type: 'time',
display: true,
time: {
unit: 'month'
}
}],
yAxes: [{
ticks: {
callback: function (label, index, labels) {
console.log(label);
return this.abbreviatedNumber(label);
}
},
scaleLabel: {
display: false,
labelString: '1k = 1000'
}
}]
}
};
const barData = {
datasets: [{
label: "Rows Processed",
backgroundColor: defaultColor,
data: [{
x: 1546300800000,
y: 216915890
},
{
x: 1548979200000,
y: 1306458666
},
{
x: 1551398400000,
y: 1780817685
},
{
x: 1554076800000,
y: 1677704491
},
{
x: 1556668800000,
y: 2027622415
},
{
x: 1559347200000,
y: 1954768048
},
{
x: 1561939200000,
y: 683285217
}
]
}]
};
const getsampledata = async () => {
console.log(args.data);
const doughnut = 'doughnut';
const bar = 'bar'
try {
/* Here you can extend your component to have multiple chart types
if (args.data === doughnut) {
user.response = {
data: doughnutData,
options: doughnutOptions,
type: doughnut
};
} else */if (args.data === bar) {
user.response = {
data: barData,
options: barOptions,
type: bar
};
}
} catch (e) {
console.log(e);
}
};
return getsampledata();
Result

That’s all is required to create a custom component in Botpress. Let me know in the comments section if you face any issues creating your custom component in Botpress.
Note: Using Botpress version 12.5.0
25 Comments
srini · August 23, 2019 at 3:53 pm
hi im new to bot press and tried to use ur help and created chart. but i dont get anything displayed. i even tried with simple text instead of chart inside the div tags. Will you be able to help?
Abhishek Simon · August 24, 2019 at 12:18 am
Sure will try to help you. Just so that we are on the same page, you successfully created a custom module and then created a custom component using the div tags. Is this correct? Do you see any exceptions in browser console or the botpress server?
srini · August 24, 2019 at 12:03 pm
thanks for the reply…. yes i did created the custom module and the component. In the chat window i get like a oval shape structure but not the chart. I do get errors in the browser. can you ping me in my email address or can you please share u email address i can share the screenshots.
Thanks a ton…….
Abhishek Simon · August 24, 2019 at 6:31 pm
Drop me a mail at abhisheksimion@gmail.com
srini · August 24, 2019 at 1:02 pm
this is the error i get in browser http://localhost:3000/assets/modules/cs_chart/web/lite.bundle.js net::ERR_ABORTED 404 (Not Found) could be the module not registered properly
Abhishek Simon · September 17, 2019 at 5:22 pm
Can you share your module without the node_modules folder to abhisheksimion@gmail.com. Also please check my github repository
https://github.com/abhisheksimion/botpress_infa_module
viet · August 26, 2019 at 2:30 pm
what is component id here ?
Abhishek Simon · August 26, 2019 at 4:46 pm
@viet: Component id is defined in the content type file `modules\infa-module\src\content-types\infa-chart.js` under `module.exports.id` in above example it is `infa_chart`.
Nguyễn Tiến Việt · August 26, 2019 at 5:24 pm
i had implemented with your code but everythings dont work for me, could you share your github folder ?
Abhishek Simon · September 17, 2019 at 5:20 pm
Github repository: https://github.com/abhisheksimion/botpress_infa_module
Priyanka Paul · November 6, 2019 at 6:33 pm
hi I want to make one dynamic dropdown that means the number of items in the dropdown will depend on the number of data fetched from the database. For that do I need to make a custom component ? and will this work on the current version as well?
Abhishek Simon · November 17, 2019 at 8:22 pm
Yes you can create a custom component for your own dynamic dropdown and it will work with the all 11.x+ versions, given you are following this guide.
priyanka · November 15, 2019 at 8:40 pm
Hi I am trying build one custom dynamic dropdown component. For that I am fetching data using an action and the data is stored in my session.lastMessages as an array of object where each coloumn comes as an attribute of the object.
I built a custom module and I wrote my jsx as
dynamicDropdown component:
import React from ‘react’
import Select from ‘react-select’
import Creatable from ‘react-select/lib/Creatable’
export class dynamicDropdownQuestions extends React.Component {
state = {
options: []
}
componentDidMount() {
if (this.props.session.lastMessages){
let Questions = [];
Questions = this.props.session.lastMessages.map(x => {
return {
value: x.Questions,
label: x.Questions
}
})
this.setState({ options: Questions })
}
}
handleChange = selectedOption => {
this.setState({ selectedOption }, () => {
if (!this.props.buttonText) {
this.sendChoice()
}
})
}
sendChoice = () => {
const { selectedOption } = this.state
if (!selectedOption) {
return
}
let { label, value } = this.state.selectedOption
if (selectedOption.length) {
label = selectedOption.map(x => x.label).join(‘,’)
value = selectedOption.map(x => x.value || x.label).join(‘,’)
}
this.props.onSendData && this.props.onSendData({ type: ‘quick_reply’, text: label, payload: value || label })
}
renderSelect(inKeyboard) {
return (
{this.props.allowCreation ? (
) : (
)}
{this.props.buttonText && (
{this.props.buttonText}
)}
)
}
render() {
const shouldDisplay = this.props.isLastGroup && this.props.isLastOfGroup
if (this.props.displayInKeyboard) {
const Keyboard = this.props.keyboard
return (
{this.props.message}
)
}
return (
{this.props.message}
{shouldDisplay && this.renderSelect()}
)
}
}
for the content type i have just copy pasted the already existing content type of the dropdown.If i try to change any thing my file is not building. and if I keep the same it still looks like an empty dropdown and the submit button does not work. Can anyone please help me in this.
Abhishek Simon · November 17, 2019 at 8:24 pm
Can you share your custom module as a zip to my email id abhisheksimion@gmail.com, I will try to look into it.
ปั้มไลค์ · May 31, 2020 at 11:18 pm
Like!! Really appreciate you sharing this blog post.Really thank you! Keep writing.
Simon · May 31, 2020 at 11:31 pm
You are welcome.
Harald · June 8, 2020 at 5:05 pm
i have clone https://github.com/abhisheksimion/botpress_infa_module
and do
Quick Start
Copy the contents to folder modules\infa-module
Open a terminal in the folder modules/infa-module and type yarn && yarn build
and i get this error?
harald@Entwicklung:~/botpress/modules/botpress_infa_module$ yarn build
yarn run v1.22.4
$ ./node_modules/.bin/module-builder build
[botpress_infa_module] Generated backend (581 ms)
[botpress_infa_module] Child
3 modules
Child
11 modules
ERROR in /home/harald/botpress/node_modules/primereact/components/chart/Chart.js
Module not found: Error: Can’t resolve ‘chart.js’ in ‘/home/harald/botpress/node_modules/primereact/components/chart’
ERROR in ./src/views/lite/components/InfaChart.jsx
Module not found: Error: Can’t resolve ‘chartjs-plugin-labels’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
ERROR in /home/harald/botpress/node_modules/primereact/components/chart/Chart.js
Module not found: Error: Can’t resolve ‘classnames’ in ‘/home/harald/botpress/node_modules/primereact/components/chart’
ERROR in /home/harald/botpress/node_modules/primereact/components/chart/Chart.js
Module not found: Error: Can’t resolve ‘prop-types’ in ‘/home/harald/botpress/node_modules/primereact/components/chart’
ERROR in ./src/views/lite/components/InfaLinkPreview.jsx
Module not found: Error: Can’t resolve ‘react-horizontal-scrolling-menu’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
ERROR in ./src/views/lite/components/InfaDropdownMenu.jsx
Module not found: Error: Can’t resolve ‘react-select’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
ERROR in ./src/views/lite/components/InfaLinkPreview.jsx
Module not found: Error: Can’t resolve ‘truncate’ in ‘/home/harald/botpress/modules/botpress_infa_module/src/views/lite/components’
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Simon · June 8, 2020 at 5:39 pm
You need to add the missing components
eg:
yarn add primereact,yarn add truncateetcHarald · June 8, 2020 at 5:54 pm
primereact I had already installed ,truncate I have installed. This does not fix the error. Where can I find an overview of the other modules you mean by etc.?
Simon · June 8, 2020 at 6:58 pm
Try the following and check this for more details on primereact
yarn add primereactyarn add primeicons
yarn add chart.js
yarn add classnames
yarn add prop-types
yarn add chartjs-plugin-labels
yarn add react-horizontal-scrolling-menu
yarn add react-select
yarn add truncate
Harald · June 8, 2020 at 7:55 pm
thanks for your patience. Now that I have installed it, the editor also shows the corresponding files. But in the module properties there is only ” Some interface” and in the Pick Content there are no new entries.
Harald · June 8, 2020 at 8:03 pm
In an new empty bot i see the entrys in content pick.
In the logs is a error:
16:29:57.699 HTTP [customactiondemo] [Error, Content type “infa_dropdownmenu” is not a valid registered content type ID]
STACK TRACE
Error: Content type “infa_dropdownmenu” is not a valid registered content type ID
at CMSService.getContentType (/home/harald/botpress/out/bp/core/services/cms.js:328:19)
at Promise.map.x (/home/harald/botpress/out/bp/core/services/cms.js:320:60)
at tryCatcher (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/util.js:16:23)
at MappingPromiseArray._promiseFulfilled (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/map.js:61:38)
at MappingPromiseArray.PromiseArray._iterate (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/promise_array.js:114:31)
at MappingPromiseArray.init (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/promise_array.js:78:10)
at MappingPromiseArray._asyncInit (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/map.js:30:10)
at _drainQueueStep (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:142:12)
at _drainQueue (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:131:9)
at Async._drainQueues (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:147:5)
at Immediate.Async.drainQueues [as _onImmediate] (/home/harald/botpress/modules/nlu/node_modules/bluebird/js/release/async.js:17:14)
at runCallback (timers.js:694:18)
at tryOnImmediate (timers.js:665:5)
at processImmediate (timers.js:647:5)
at process.topLevelDomainCallback (domain.js:121:23)
Beto Fernandes · May 1, 2021 at 12:17 am
Hi, why I see this code in Github?
Simon · May 14, 2021 at 12:03 pm
I did not get your question. Can you please elaborate on your issue?
Deepak K T · November 22, 2022 at 7:47 pm
Hi Simon, I am new to Botpress and I want to add some custom analytics. Hence can you please write one blog on how to add custom analytics in Botpress analytics that would me so much helpful.