Creating Your First Plugin
Learn to build custom plugins for WebVB Studio from scratch. We'll create a Toggle Switch control and a set of utility functions, step by step.
Before You Start
Read the Plugin System Overview to understand the architecture, type system, and plugin lifecycle. This tutorial assumes familiarity with the plugin JSON format and basic HTML/JavaScript.
Part 1: Functions-Only Plugin
The simplest plugin type provides custom functions without any visual controls. This is the best place to start learning.
Step 1: Create the Plugin Skeleton
Every plugin starts with the same skeleton. Open the Plugin Manager in WebVB Studio, go to the Create tab, and paste this template:
{
"pluginApiVersion": "1.0",
"metadata": {
"id": "com.myname.textutils",
"name": "Text Utilities",
"version": "1.0.0",
"description": "Handy text manipulation functions",
"author": { "name": "Your Name" },
"icon": "📜",
"category": "Functions",
"tags": ["text", "string", "utility"],
"license": "MIT"
},
"functions": [],
"helpContent": "<h3>Text Utilities</h3><p>Helper functions for text.</p>"
} Key Points
- ●
pluginApiVersionmust be"1.0" - ●
metadata.idshould be a unique reverse-domain identifier - ● No
controlsfield needed for a functions-only plugin
Step 2: Add Your Functions
Each function needs a name,
params, and
jsCode. The jsCode
is a JavaScript function body — parameters are available as named variables.
"functions": [
{
"name": "ReverseText",
"description": "Reverse a string",
"params": [
{ "name": "text", "type": "String", "description": "Text to reverse" }
],
"returnType": "String",
"jsCode": "return String(text || '').split('').reverse().join('');",
"category": "String"
},
{
"name": "WordCount",
"description": "Count words in a string",
"params": [
{ "name": "text", "type": "String", "description": "Text to count words in" }
],
"returnType": "Integer",
"jsCode": "var s = String(text || '').trim(); return s.length === 0 ? 0 : s.split(/\\s+/).length;",
"category": "String"
},
{
"name": "RepeatText",
"description": "Repeat text N times with optional separator",
"params": [
{ "name": "text", "type": "String", "description": "Text to repeat" },
{ "name": "count", "type": "Integer", "description": "Number of repetitions" },
{ "name": "separator", "type": "String", "description": "Separator between repetitions", "optional": true, "defaultValue": "" }
],
"returnType": "String",
"jsCode": "var arr = []; for (var i = 0; i < (parseInt(count) || 1); i++) arr.push(String(text || '')); return arr.join(separator || '');",
"category": "String"
}
] Important: jsCode Rules
- ● The code is a function body, not a full function. No
functionkeyword. - ● Parameter names from
paramsbecome local variables automatically. - ● Use
returnto produce a value. - ● Escape backslashes in JSON:
\\snot\s. - ● Always validate inputs (check for null/undefined, use parseInt/parseFloat).
Step 3: Install and Test
Click the Install Plugin button in the Create tab. If the JSON is valid, you'll see a success message and the plugin will appear in the Installed tab.
Now test your functions in a VB or Python project:
Private Sub cmdTest_Click()
' Test ReverseText
lblResult.Caption = ReverseText("Hello!")
' Shows: "!olleH"
' Test WordCount
Dim count As Integer
count = WordCount("The quick brown fox")
MsgBox "Words: " & count ' Shows: 4
' Test RepeatText
lblStars.Caption = RepeatText("*", 5, " ")
' Shows: "* * * * *"
End Sub def cmdTest_Click():
# Test ReverseText
lblResult.Caption = ReverseText("Hello!")
# Shows: "!olleH"
# Test WordCount
count = WordCount("The quick brown fox")
MsgBox(f"Words: {count}") # Shows: 4
# Test RepeatText
lblStars.Caption = RepeatText("*", 5, " ")
# Shows: "* * * * *" Step 4: Export and Share
Go to the Installed tab, find your plugin, and click
Export. This downloads a .vbplugin
file that anyone can import into their WebVB Studio.
Congratulations! You've created your first plugin. Functions-only plugins are great for adding reusable utilities across all your projects.
Part 2: Custom Control Plugin
Now let's build something more substantial: a Toggle Switch control that renders in the designer, responds to clicks at runtime, and fires events.
Step 1: Define the Plugin Metadata
{
"pluginApiVersion": "1.0",
"metadata": {
"id": "com.myname.toggleswitch",
"name": "Toggle Switch",
"version": "1.0.0",
"description": "A modern toggle switch control with smooth animation",
"author": { "name": "Your Name", "url": "https://yoursite.com" },
"icon": "🔘",
"category": "Input",
"tags": ["toggle", "switch", "boolean", "on", "off"],
"license": "MIT"
},
"controls": [ ... ],
"styles": "...",
"helpContent": "..."
} Step 2: Define Control Properties
Properties appear in the Properties panel when the user selects your control in the designer. Think about what users need to customize.
"properties": [
{
"id": "Value",
"label": "Value",
"type": "boolean",
"defaultValue": false,
"description": "Current toggle state (true = on, false = off)",
"category": "Data"
},
{
"id": "OnColor",
"label": "On Color",
"type": "color",
"defaultValue": "#22c55e",
"description": "Color when toggle is ON",
"category": "Appearance"
},
{
"id": "OffColor",
"label": "Off Color",
"type": "color",
"defaultValue": "#d1d5db",
"description": "Color when toggle is OFF",
"category": "Appearance"
},
{
"id": "OnLabel",
"label": "On Label",
"type": "text",
"defaultValue": "ON",
"description": "Label text when ON",
"category": "Appearance"
},
{
"id": "OffLabel",
"label": "Off Label",
"type": "text",
"defaultValue": "OFF",
"description": "Label text when OFF",
"category": "Appearance"
},
{
"id": "Disabled",
"label": "Disabled",
"type": "boolean",
"defaultValue": false,
"description": "Disable user interaction",
"category": "Behavior"
}
] Best Practices for Properties
- ● Always provide
defaultValueso the control looks good out of the box - ● Use
categoryto group related properties (Appearance, Behavior, Data) - ● Use the
colortype for any color property — it shows a color picker - ● Use
selecttype withoptionsfor enum-like choices
Step 3: Define Events
Events connect your control to user code. When an event fires, WebVB Studio looks for a matching event handler in the user's VB or Python code.
"events": [
{
"name": "Change",
"description": "Fired when the toggle state changes",
"params": ["NewValue"]
},
{
"name": "Click",
"description": "Fired when the toggle is clicked (even if disabled)"
}
]
The user writes event handlers like Sub ToggleSwitch1_Change(NewValue)
where NewValue will be True or
False.
Step 4: Write the Designer Renderer
The designer renderer produces a static HTML preview for the form designer. It should reflect the current property values but doesn't need interactivity.
"designerRenderHtml": "var isOn = props.Value === true || props.Value === 'true'; var onColor = props.OnColor || '#22c55e'; var offColor = props.OffColor || '#d1d5db'; var label = isOn ? (props.OnLabel || 'ON') : (props.OffLabel || 'OFF'); var trackColor = isOn ? onColor : offColor; var thumbLeft = isOn ? 'calc(100% - 26px)' : '2px'; return '<div style=\"display:flex;align-items:center;height:100%;padding:4px;\">' + '<div style=\"position:relative;width:50px;height:26px;background:' + trackColor + ';border-radius:13px;\">' + '<div style=\"position:absolute;top:2px;left:' + thumbLeft + ';width:22px;height:22px;background:white;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,0.3);\">' + '</div></div>' + '<span style=\"margin-left:8px;font-size:12px;font-weight:600;color:' + trackColor + ';\">' + label + '</span></div>';" Designer Renderer Rules
- ● The code is a function body that receives
propsand mustreturnan HTML string - ● Always use inline styles (no external CSS classes)
- ● Always provide fallback values for every property:
props.Color || '#default' - ● Handle boolean props as both true/false and 'true'/'false' (they may come as strings from JSON)
Step 5: Write the Runtime Renderer
The runtime renderer is similar to the designer renderer but produces the live, interactive HTML. It can include data attributes for the init code to hook into.
"runtimeRenderHtml": "var isOn = props.Value === true || props.Value === 'true'; var onColor = props.OnColor || '#22c55e'; var offColor = props.OffColor || '#d1d5db'; var label = isOn ? (props.OnLabel || 'ON') : (props.OffLabel || 'OFF'); var disabled = props.Disabled === true || props.Disabled === 'true'; var trackColor = isOn ? onColor : offColor; var thumbLeft = isOn ? 'calc(100% - 26px)' : '2px'; var cursor = disabled ? 'not-allowed' : 'pointer'; var opacity = disabled ? '0.5' : '1'; return '<div data-toggle=\"track\" style=\"display:flex;...cursor:' + cursor + ';opacity:' + opacity + ';...\">...</div>';"
Notice the data-toggle="track" attribute — we'll use it
in the init code to attach the click listener.
Step 6: Write the Runtime Init Code
The init code runs once after the control mounts. This is where you attach event listeners and set up interactivity. You have access to these special variables:
element props fireEvent(name, data) setProperty(name, value) "runtimeInitCode": "element.addEventListener('click', function(e) {
fireEvent('Click', {});
var disabled = props.Disabled === true || props.Disabled === 'true';
if (disabled) return;
var isOn = props.Value === true || props.Value === 'true';
var newVal = !isOn;
setProperty('Value', newVal);
fireEvent('Change', { NewValue: newVal });
});" How Events Flow
- 1. User clicks the toggle
- 2.
fireEvent('Click', ...)triggers the user'sToggleSwitch1_Click()handler - 3.
setProperty('Value', newVal)updates the property and re-renders the control - 4.
fireEvent('Change', ...)triggersToggleSwitch1_Change(NewValue)
Step 7: Add CSS Styles (Optional)
The styles field injects global CSS into the document.
Use this for animations, keyframes, or shared styles.
"styles": "[data-toggle='track']:hover { filter: brightness(1.1); } [data-toggle='track']:active { transform: scale(0.97); }" Style Tips
- ● Use
data-*attribute selectors to scope styles to your control - ● Prefer inline styles in renderers; use this field for animations and pseudo-elements
- ● CSS is injected when plugin activates and removed when disabled/uninstalled
Step 8: Write Help Documentation
The helpContent field accepts HTML that documents
your plugin for users. Include property descriptions, event examples, and code samples.
"helpContent": "<h3>Toggle Switch Control</h3>
<p>A modern ON/OFF toggle switch.</p>
<h4>Properties</h4>
<ul>
<li><b>Value</b> - Boolean: True (ON) or False (OFF)</li>
<li><b>OnColor/OffColor</b> - Track colors</li>
<li><b>OnLabel/OffLabel</b> - Text labels</li>
<li><b>Disabled</b> - Prevent user interaction</li>
</ul>
<h4>Events</h4>
<ul>
<li><b>Change(NewValue)</b> - Fires when toggled</li>
<li><b>Click</b> - Fires on any click</li>
</ul>" Step 9: The Complete Plugin
Here's the entire Toggle Switch plugin assembled into a single JSON file:
{
"pluginApiVersion": "1.0",
"metadata": {
"id": "com.myname.toggleswitch",
"name": "Toggle Switch",
"version": "1.0.0",
"description": "A modern toggle switch control with smooth animation",
"author": { "name": "Your Name" },
"icon": "🔘",
"category": "Input",
"tags": ["toggle", "switch", "boolean", "on", "off"],
"license": "MIT"
},
"controls": [
{
"typeName": "ToggleSwitch",
"displayName": "Toggle Switch",
"icon": "🔘",
"description": "ON/OFF toggle switch",
"defaultWidth": 120,
"defaultHeight": 36,
"properties": [
{ "id": "Value", "label": "Value", "type": "boolean",
"defaultValue": false, "description": "Toggle state",
"category": "Data" },
{ "id": "OnColor", "label": "On Color", "type": "color",
"defaultValue": "#22c55e", "category": "Appearance" },
{ "id": "OffColor", "label": "Off Color", "type": "color",
"defaultValue": "#d1d5db", "category": "Appearance" },
{ "id": "OnLabel", "label": "On Label", "type": "text",
"defaultValue": "ON", "category": "Appearance" },
{ "id": "OffLabel", "label": "Off Label", "type": "text",
"defaultValue": "OFF", "category": "Appearance" },
{ "id": "Disabled", "label": "Disabled", "type": "boolean",
"defaultValue": false, "category": "Behavior" }
],
"events": [
{ "name": "Change", "description": "Toggle state changed",
"params": ["NewValue"] },
{ "name": "Click", "description": "Toggle was clicked" }
],
"designerRenderHtml": "var isOn = props.Value === true ...; return '<div>...</div>';",
"runtimeRenderHtml": "var isOn = ...; return '<div data-toggle=...>...</div>';",
"runtimeInitCode": "element.addEventListener('click', function(e) { ... });"
}
],
"styles": "[data-toggle='track']:hover { filter: brightness(1.1); }",
"helpContent": "<h3>Toggle Switch</h3>..."
} Step 10: Using Your Control
After installing, your Toggle Switch appears in the Toolbox under "Plugins". Drag it onto a form and write event handlers:
Private Sub ToggleSwitch1_Change(NewValue)
If NewValue Then
lblStatus.Caption = "Dark mode ON"
Form1.BackColor = "#1a1a2e"
Else
lblStatus.Caption = "Dark mode OFF"
Form1.BackColor = "#ffffff"
End If
End Sub
' Read the current value anytime:
Private Sub cmdCheck_Click()
MsgBox "Toggle is: " & ToggleSwitch1.Value
End Sub
' Set it programmatically:
Private Sub cmdEnable_Click()
ToggleSwitch1.Value = True
End Sub def ToggleSwitch1_Change(NewValue):
if NewValue:
lblStatus.Caption = "Dark mode ON"
Form1.BackColor = "#1a1a2e"
else:
lblStatus.Caption = "Dark mode OFF"
Form1.BackColor = "#ffffff"
# Read the current value anytime:
def cmdCheck_Click():
MsgBox(f"Toggle is: {ToggleSwitch1.Value}")
# Set it programmatically:
def cmdEnable_Click():
ToggleSwitch1.Value = True Part 3: Advanced Topics
Combining Controls and Functions
A single plugin can provide both controls and functions. For example, a "Charts" plugin might
include a Chart control and helper functions like FormatAxis()
or ParseCSV().
{
"pluginApiVersion": "1.0",
"metadata": { ... },
"controls": [
{ "typeName": "PieChart", ... },
{ "typeName": "BarChart", ... }
],
"functions": [
{ "name": "ParseCSV", ... },
{ "name": "FormatAxis", ... }
],
"styles": "@keyframes chart-fadein { ... }",
"helpContent": "..."
} Cleanup and Resource Management
If your control sets up intervals, timeouts, or external event listeners, use runtimeCleanupCode to clean up when the control is unmounted.
// runtimeInitCode:
"var intervalId = setInterval(function() {
/* update clock */
}, 1000);
element.__myInterval = intervalId;"
// runtimeCleanupCode:
"if (element.__myInterval) clearInterval(element.__myInterval);" Memory Leak Prevention
- ● Always clear
setIntervalandsetTimeout - ● Remove any listeners attached to
windowordocument - ● Store references on
element(e.g.element.__myRef) so cleanup code can access them
Handling Property Changes
When properties are changed programmatically (e.g. MyControl.Value = 50 in VB code),
the control re-renders. If you need custom logic beyond re-rendering, use runtimePropertyChangeCode:
// runtimePropertyChangeCode receives:
// (element, propName, newValue, oldValue, props)
"if (propName === 'Value' && parseInt(newValue) >= parseInt(props.Max)) {
fireEvent('Complete', {});
}" Async Functions
Plugin functions can be asynchronous by setting isAsync: true.
The jsCode can then use await:
{
"name": "FetchJSON",
"description": "Fetch JSON from a URL",
"params": [
{ "name": "url", "type": "String", "description": "URL to fetch" }
],
"returnType": "Object",
"jsCode": "var response = await fetch(url); return await response.json();",
"isAsync": true,
"category": "Network"
} Naming Conventions
Plugin IDs
Use reverse-domain notation to ensure uniqueness:
Control Type Names
Use PascalCase. The name is prefixed with the plugin ID automatically:
Function Names
Use PascalCase. Functions are registered globally (case-insensitive):
Troubleshooting
"Plugin render error" in the designer
Your designerRenderHtml has a JavaScript error. Common causes:
- • Unescaped quotes inside the HTML string
- • Missing
returnstatement - • Syntax error in string concatenation
"Invalid plugin JSON" on import
The JSON is malformed. Common issues:
- • Trailing commas (not allowed in JSON)
- • Single quotes instead of double quotes
- • Unescaped special characters in code strings
- • Missing
pluginApiVersion: "1.0"
Events not firing
Check that:
- • The event name in
fireEvent('Change', ...)matches the event name in theeventsarray exactly (case-sensitive) - • The user's event handler follows the naming pattern:
ControlName_EventName - • Your
runtimeInitCodeis correctly attaching the event listener
Function returns undefined
Make sure your jsCode includes a return statement.
The code is a function body, so without return, it implicitly returns undefined.
Control doesn't re-render after setProperty
The setProperty call should trigger a re-render automatically. If it doesn't:
- • Ensure you're calling
setProperty(not directly modifyingprops) - • Check the browser console for errors
- • Verify the property name matches exactly (case-sensitive)
Best Practices Checklist
props.X || 'default' true and "true" runtimeCleanupCode styles field for animations helpContent with property descriptions and code examples Distributing Your Plugin
Plugins are simple JSON files, making distribution straightforward. Here are several ways to share your plugins:
Export & Send
Use the Export button in the Plugin Manager to download a .vbplugin file.
Send it via email, chat, or file sharing. The recipient imports it via the Import tab.
GitHub / Hosting
Host the .vbplugin file on GitHub, your website, or a CDN.
Users can download it and import into WebVB Studio.
Paste JSON
Share the raw JSON in documentation, forum posts, or tutorials. Users paste it into the Import tab's text area or the Create tab's editor.
You're a Plugin Developer!
You now know everything needed to create, test, and distribute custom plugins. Check the built-in examples for more inspiration, and happy building!