Custom Actions API

List view
Understanding Nobi
Getting Started
Implement Nobi On Your Site
Answer Customer Questions With Your Knowledge Base
Control How Nobi Shows Products With Merchandising
Analyze Your Data
Add Your Own Custom Actions To Nobi
Control How Nobi Responds With Override Rules
Beta Products
Developers Guide
References

Custom Actions API


Custom Actions let you register JavaScript functions that Nobi can execute directly in the browser. Define tools using window.NobiTools, and Nobi's AI will automatically invoke them when appropriate, collect any missing parameters through generated forms, and provide natural language confirmations to users.

Quick Start

Here's a minimal example of registering a custom action:
window.NobiTools = { submitContactForm: { name: "submitContactForm", description: "Submit a contact form with the user's information", parameters: { type: "object", properties: { name: { type: "string", description: "User's full name" }, email: { type: "string", description: "User's email address" }, message: { type: "string", description: "User's message" } }, required: ["name", "email", "message"] }, handler: async function(args) { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(args) }); if (!response.ok) throw new Error('Failed to submit form'); return { success: true, message: "Contact form submitted successfully" }; } } };
When a user says "I'd like to contact you about a partnership", Nobi will:
  1. Recognize this matches the submitContactForm tool
  1. Check which parameters are missing
  1. Render a form to collect name, email, and message
  1. Execute the handler with the collected data
  1. Respond with a natural confirmation based on the return value

Tool Registration

Adding Tools

Add tools by defining them on the window.NobiTools object before Nobi initializes:
window.NobiTools = { toolName: { name: "toolName", description: "What this tool does", parameters: { /* JSON Schema */ }, handler: function(args) { /* Implementation */ } }, anotherTool: { name: "anotherTool", description: "What this other tool does", parameters: { /* JSON Schema */ }, handler: function(args) { /* Implementation */ } } };

Where To Add Tools

Recommended approaches:
Option 1: Inline script in head
<head> <script> window.NobiTools = { // Your tools here }; </script> <!-- Nobi widget script --> </head>
Option 2: External script load
<head> <script src="/js/nobi-tools.js"></script> <!-- Nobi widget script --> </head>
Option 3: Dynamic registration
// Ensure this runs before Nobi initializes document.addEventListener('DOMContentLoaded', function() { window.NobiTools = { // Your tools here }; });

Tool Definition Schema

Each tool must include four required properties:

name (string, required)

Unique identifier for the tool. Must match the property key in window.NobiTools.
window.NobiTools = { calculateShipping: { // Property key name: "calculateShipping", // Must match key // ... } };
Rules:
  • No spaces or special characters
  • Must be unique across all tools
  • Should be representative of the action
Good names:
  • submitContactForm
  • calculateShipping
  • bookAppointment
  • checkInventory
Bad names:
  • tool1
  • do_thing
  • submit form (spaces not allowed)
  • calculate-shipping (use camelCase)

description (string, required)

Clear explanation of what the tool does. This is used by the AI to determine when to invoke the tool.
{ name: "calculateShipping", description: "Calculate shipping cost and delivery time based on destination address and cart contents", // ... }
Writing effective descriptions:
Good descriptions:
  • "Submit a contact form with the user's name, email, and message"
  • "Calculate shipping cost and estimated delivery date for a given address"
  • "Book an appointment for a specific service, date, and time"
  • "Check product availability at a specific store location"
Poor descriptions:
  • "Does stuff" (too vague)
  • "Form" (not descriptive enough)
  • "Shipping" (unclear what action is performed)
Best practices:
  • Start with an action verb (submit, calculate, book, check)
  • Be specific about what data is needed
  • Mention the outcome or result
  • Keep it under 100 characters when possible

parameters (object, required)

JSON Schema object defining the tool's input parameters. Uses standard JSON Schema specification.
Basic structure:
{ parameters: { type: "object", properties: { paramName: { type: "string|number|boolean|array|object", description: "What this parameter represents" } }, required: ["paramName"] // Array of required parameter names } }
Supported parameter types:
String:
{ email: { type: "string", description: "User's email address" } }
Number:
{ quantity: { type: "number", description: "Number of items to order" } }
Boolean:
{ subscribe: { type: "boolean", description: "Whether to subscribe to newsletter" } }
Array:
{ productIds: { type: "array", items: { type: "string" }, description: "List of product IDs to add to cart" } }
Object:
{ address: { type: "object", properties: { street: { type: "string" }, city: { type: "string" }, zipCode: { type: "string" } }, required: ["street", "city", "zipCode"], description: "Shipping address" } }
Enum (restricted values):
{ shippingMethod: { type: "string", enum: ["standard", "express", "overnight"], description: "Shipping speed option" } }

required (array)

Array of parameter names that must be provided. Parameters not in this array are optional.
{ parameters: { type: "object", properties: { name: { type: "string", description: "Full name" }, email: { type: "string", description: "Email address" }, phone: { type: "string", description: "Phone number" }, notes: { type: "string", description: "Additional notes" } }, required: ["name", "email"] // phone and notes are optional } }
When parameters are missing:
  • If the LLM cannot determine required parameters from the conversation, a form is automatically rendered
  • The form collects all required parameters
  • Optional parameters are included in the form but marked as optional
  • Users must fill all required fields before submission

handler (function, required)

JavaScript function that executes when the tool is invoked. Receives validated parameters as an object.
Basic handler:
{ handler: function(args) { // args contains validated parameters console.log('Received:', args); return { success: true }; } }
Handler signature:
function handler(args: object): any | Promise<any>
The handler receives:
  • args - Object containing all parameters (required and optional)
  • Parameters are already validated against the schema
  • Missing required parameters trigger form collection before handler runs
The handler should return:
  • Any value (string, number, object, etc.)
  • A Promise that resolves to any value (for async operations)
  • Error object or throw an exception on failure

Handler Implementation

Synchronous Handlers

For immediate operations that don't require waiting:
{ name: "addToCart", description: "Add a product to the shopping cart", parameters: { type: "object", properties: { productId: { type: "string", description: "Product ID" }, quantity: { type: "number", description: "Quantity to add" } }, required: ["productId", "quantity"] }, handler: function(args) { // Direct DOM manipulation or synchronous API const cart = window.myShop.cart; cart.addItem(args.productId, args.quantity); return { success: true, itemCount: cart.getTotalItems(), message: `Added ${args.quantity} item(s) to cart` }; } }

Asynchronous Handlers

For operations that require API calls, database queries, or other async work:
{ name: "bookAppointment", description: "Book an appointment for a specific date and time", parameters: { type: "object", properties: { service: { type: "string", description: "Service type" }, date: { type: "string", description: "Appointment date (YYYY-MM-DD)" }, time: { type: "string", description: "Appointment time (HH:MM)" } }, required: ["service", "date", "time"] }, handler: async function(args) { // Make API call to booking system const response = await fetch('/api/appointments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ service: args.service, date: args.date, time: args.time }) }); if (!response.ok) { throw new Error('Failed to book appointment'); } const data = await response.json(); return { success: true, confirmationNumber: data.confirmationNumber, message: `Appointment booked for ${args.date} at ${args.time}` }; } }
Important: Always use async/await or return a Promise for asynchronous operations. Nobi waits for the Promise to resolve before continuing.

Return Values

Return values are sent back to the LLM, which uses them to generate a natural language response.
Simple success:
handler: function(args) { // Do something return { success: true }; } // LLM might say: "Done! I've completed that action for you."
Detailed result:
handler: function(args) { return { success: true, orderId: "ORD-12345", total: 49.99, estimatedDelivery: "2024-03-15" }; } // LLM might say: "Your order #ORD-12345 has been placed! Total: $49.99. Estimated delivery: March 15, 2024."
Error result:
handler: function(args) { return { success: false, error: "Product out of stock", alternativeProducts: ["SKU-123", "SKU-456"] }; } // LLM might say: "I'm sorry, that product is currently out of stock. Here are some similar alternatives..."
Best practices for return values:
  • Include success: true/false for clarity
  • Return structured data that helps the LLM provide helpful responses
  • Include confirmation numbers, dates, or other relevant details
  • For failures, include error messages and alternative actions

Error Handling

Proper error handling ensures users receive helpful feedback when operations fail.
Using try/catch:
{ handler: async function(args) { try { const response = await fetch('/api/submit', { method: 'POST', body: JSON.stringify(args) }); if (!response.ok) { throw new Error(`API error: ${response.status}`); } const data = await response.json(); return { success: true, data: data }; } catch (error) { return { success: false, error: error.message, userMessage: "We couldn't complete that action. Please try again later." }; } } }
Throwing errors:
{ handler: async function(args) { const response = await fetch('/api/check-availability', { method: 'POST', body: JSON.stringify({ productId: args.productId }) }); if (!response.ok) { throw new Error('Unable to check product availability'); } const data = await response.json(); if (!data.available) { throw new Error('Product is currently out of stock'); } return { success: true, available: true }; } }
When errors are thrown or returned:
  • Nobi catches the error
  • The error message is sent to the LLM
  • The LLM explains the failure to the user in natural language
  • Users can try again or take alternative actions
Error handling best practices:
  • Always wrap async operations in try/catch
  • Provide clear, user-friendly error messages
  • Include details that help the LLM suggest alternatives
  • Avoid exposing sensitive error details to users
  • Log errors to console for debugging

Timeouts

Handlers have a 30-second timeout. If execution takes longer, an error is returned.
{ handler: async function(args) { // This long-running operation will timeout after 30 seconds const result = await someLongRunningTask(args); return result; } }
If a timeout occurs:
  • Execution is halted
  • An error is sent to the LLM: "Tool execution timed out"
  • The LLM explains the timeout to the user
  • Users can try again
Handling long operations:
  • Break into smaller steps if possible
  • Use webhooks or callbacks for very long operations
  • Consider returning immediately and notifying users later
  • Show progress updates during execution (if your system supports it)

Loading States

While handlers execute, Nobi displays a loading indicator to users.

Parameter Collection and Forms

When the LLM determines a tool should be invoked but lacks some required parameters, Nobi automatically generates a form to collect the missing information.

Automatic Form Generation

Forms are generated based on your parameter schema:
Tool definition:
{ name: "scheduleCallback", description: "Schedule a callback from customer service", parameters: { type: "object", properties: { phone: { type: "string", description: "Phone number to call" }, preferredTime: { type: "string", enum: ["morning", "afternoon", "evening"], description: "Preferred time of day" }, reason: { type: "string", description: "Reason for callback" } }, required: ["phone", "preferredTime"] }, handler: async function(args) { // Handler implementation } }
If user says: "I need to talk to someone"
Nobi responds: "I can schedule a callback for you!" and shows a form with:
  • Phone number field (required)
  • Preferred time dropdown (required, with options: morning, afternoon, evening)
  • Reason field (optional)

Form Field Types

Forms render different input types based on parameter types:
String → Text input:
{ type: "string" }
Renders: <input type="text">
Number → Number input:
{ type: "number" }
Renders: <input type="number">
Boolean → Checkbox:
{ type: "boolean" }
Renders: <input type="checkbox">
Date → Date Picker:
{ type: "string", format: "date" }
Also auto-detected if param name contains "date", "day", or "when"

Partial Parameters from Conversation

If the LLM extracts some parameters from the conversation, only missing parameters appear in the form.
Example:
User: "I'd like to book an appointment for haircut on March 15th"
The LLM extracts:
  • service: "haircut"
  • date: "2024-03-15"
Form only asks for:
  • time (since it's required but missing)
User sees:
"I can help you book that haircut appointment for March 15th! What time works best for you?"
[Form with time field only]
This creates a natural, conversational experience where users don't repeat information.

Form Submission

When users submit the form:
  1. All parameters (from conversation + form) are validated
  1. Handler is invoked with complete parameter set
  1. Result is processed and returned to LLM
  1. LLM generates natural language confirmation

Validation

Automatic Validation

Nobi validates tool definitions on registration.
Valid tool:
window.NobiTools = [ { name: "validTool", description: "A properly defined tool", parameters: { type: "object", properties: { param1: { type: "string" } }, required: ["param1"] }, handler: function(args) { return { success: true }; } } ]; Invalid tool (missing required fields): window.NobiTools = [ { name: "123invalid", // Invalid: starts with number // Missing description parameters: { type: "string", // Invalid: must be "object" properties: {} } // Missing handler } ];
Validation Errors
Invalid tools log errors to the console:
Nobi Tool Validation Error for "toolName": [ "Missing or invalid \"description\" field (must be a string)", "Missing or invalid \"handler\" field (must be a function)" ]
Validated fields:
  • name - Required string, must be valid identifier (letters, numbers, underscores; cannot
    start with number)
  • description - Required string
  • parameters - Required object with type: "object" and properties object
  • parameters.properties[*].type - Must be: string, number, boolean, object, array, or
    integer
  • parameters.required - Must be array if provided
  • handler - Required function

Parameter Validation

Parameters are minimally validated before handler execution:
Validation checks:
  • Required parameters are present and non-empty
Type handling:
  • Number/integer fields are parsed from strings
  • Invalid numbers become null (no error shown)
  • All other types are passed as-is (strings)
If a required field is empty:
  • Form shows "This field is required" error
  • Handler is not invoked until field is filled
Note: Type validation, enum validation, and nested schema validation are not currently implemented. Your handler should validate inputs if strict type checking is needed.

Complete Examples

Example 1: Contact Form Submission

window.NobiTools = [ { name: "submitContactForm", description: "Submit a contact form with the user's name, email, phone, and message", parameters: { type: "object", properties: { name: { type: "string", description: "Full name of the person contacting us" }, email: { type: "string", description: "Email address for follow-up" }, phone: { type: "string", description: "Phone number (optional)" }, message: { type: "string", description: "Message or inquiry from the user" } }, required: ["name", "email", "message"] }, handler: async function(args) { try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: args.name, email: args.email, phone: args.phone || null, message: args.message, timestamp: new Date().toISOString() }) }); if (!response.ok) { throw new Error('Failed to submit contact form'); } const data = await response.json(); return { success: true, confirmationId: data.id, message: `Thank you ${args.name}! We've received your message and will respond to ${args.email} within 24 hours.` }; } catch (error) { return { success: false, error: error.message }; } } } ];
User Interaction
  • User: "I need to contact your team about a partnership"
  • Nobi: "I can help you send a message! Let me get your information..."
  • [Form appears for name, email, phone, message]
  • User fills form and submits
  • Nobi: "Thank you John! We've received your message and will respond to [email protected]
    within 24 hours."

Example 2: Shipping Calculator

window.NobiTools = [ { name: "calculateShipping", description: "Calculate shipping cost and delivery time. Requires ZIP code, country, and shipping method (standard, express, or overnight).", parameters: { type: "object", properties: { zipCode: { type: "string", description: "Destination ZIP or postal code" }, country: { type: "string", description: "Destination country" }, shippingMethod: { type: "string", description: "Shipping speed: standard, express, or overnight" } }, required: ["zipCode", "country", "shippingMethod"] }, handler: async function(args) { try { const cart = window.myStore?.cart?.getItems() || []; const response = await fetch('/api/shipping/calculate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ zipCode: args.zipCode, country: args.country, method: args.shippingMethod, items: cart }) }); if (!response.ok) { throw new Error('Unable to calculate shipping'); } const data = await response.json(); return { success: true, cost: data.cost, currency: 'USD', estimatedDays: data.estimatedDays, message: `${args.shippingMethod} shipping to ${args.zipCode}: $${data.cost} (${data.estimatedDays} business days)` }; } catch (error) { return { success: false, error: error.message }; } } } ];
User Interaction
  • User: "How much would express shipping to 60601 cost?"
  • Nobi: "Let me calculate that..."
  • [Tool executes with extracted params, or form appears for missing fields]
  • Nobi: "Express shipping to 60601: $12.99 (2 business days)"

Best Practices

Tool Design

Keep tools focused
  • Each tool should do one thing well
  • Don't create mega-tools that handle multiple unrelated actions
  • ✅ Good: bookAppointment, cancelAppointment, rescheduleAppointment
  • ❌ Bad: manageAppointments that does everything
Write clear descriptions
  • Descriptions help the AI understand when to use each tool
  • Be specific about what data is needed and what the tool does
  • Include context about when the tool is appropriate
Use descriptive parameter names
  • ✅ Good: destinationAddress, appointmentDate, customerEmail
  • ❌ Bad: addr, date, email
Provide helpful parameter descriptions
  • Explain what format is expected (dates, times, etc.)
  • Mention any constraints or requirements
  • Include examples in descriptions when helpful

Error Handling

Always use try/catch for async operations
handler: async function(args) { try { // async operation } catch (error) { return { success: false, error: error.message }; } }
Return user-friendly error messages
  • Don't expose technical error details to users
  • Provide actionable guidance (retry, call support, etc.)
  • Include alternative actions when possible
Log errors for debugging
catch (error) { console.error('Tool error:', error); return { success: false, userMessage: "..." }; }

Performance

Keep handlers fast
  • Handlers have a 30-second timeout
  • Optimize API calls and database queries
  • Consider pagination for large data sets
Return early when possible
  • For long-running operations, return immediately with a status message
  • Handle completion via webhooks, email, or notifications
  • Don't make users wait unnecessarily
Use loading states effectively
  • Brief operations need minimal feedback
  • Long operations should show clear progress indicators
  • Very long operations should return immediately and notify later

Security

Don't expose sensitive data
  • Don't return API keys, passwords, or tokens
  • Sanitize error messages before showing to users
  • Be careful with PII (personally identifiable information)

Testing

Test with missing parameters
  • Verify forms render correctly
  • Check that partial parameters work
  • Ensure required vs optional is clear
Test error conditions
  • Network failures
  • Invalid data
  • Server errors
  • Timeouts
Test the full flow
  • Conversation → parameter extraction → form → execution → confirmation
  • Verify natural language responses make sense
  • Check that citations and sources work correctly
Test on different devices
  • Mobile vs desktop
  • Different browsers
  • Touch vs mouse interactions