Bringing AI to SugarCRM Dashlets: A Practical Prototype for Natural Language Integration

Author: Yuri Gee

Date: 22 Jun 2025

10 minute read time

As artificial intelligence continues to revolutionize enterprise software, the opportunities to embed AI into CRM applications are growing rapidly. AI can be integrated natively into the CRM platform and as part of custom solutions that use your preferred AI models—be it a cloud service or a self-hosted open-source model.

This article presents a prototype dashlet that converts a natural language prompt into a SQL-like structure, enabling it to retrieve and display related records (e.g., tasks and opportunities) tied to an Account. The conversion leverages an AI model to generate structured metadata without exposing sensitive customer data.

While this example uses Google's Gemini 2.0 Flash model (available through its free tier), the same approach is broadly applicable across AI models trained on API or SQL patterns.

Introduction

At its core, an AI model can be thought of as a high-dimensional mathematical function that transforms encoded text, pixels, or other inputs—into semantically rich vector representations. This semantic understanding unlocks use cases like contextual search, pattern recognition, insight generation, and action recommendations based on linked CRM data.

While semantic understanding holds incredible potential within CRM systems, it also brings important challenges—such as maintaining alignment with user permissions, ensuring the accuracy and clarity of generated outputs, managing the operational costs of API usage and prompt tokens, and safeguarding data privacy within defined boundaries.

A promising approach to address some of these complexities can be the translation of natural language prompts into structured queries (like REST API filters or SQL-style conditions). Many general-purpose language models have been extensively trained on these patterns, allowing them to understand and generate CRM-friendly queries with minimal extra tuning.

Scenario: A Personalized Insight Dashlet

Imagine a user wants a quick, interactive report within the Account record view. Instead of building complex filters or navigating report builders, the user simply types:

"Show 3 latest task names and descriptions with status in progress"  

"List top 5 opportunities by amount with assigned seller names"

The system interprets the request using an AI model and displays the appropriate related records using standard SugarCRM APIs—respecting the user’s permissions and limiting exposure to only what’s required.

This enables instant insight generation and offers flexibility to incorporate custom formulas, enhance the user interface with contextual elements, personalize interactions, and assist in triggering meaningful actions.

Dashlet Code

The dashlet implementation includes both a PHP definition and a JavaScript file. Please make sure to insert your AI model’s api_key into the JavaScript file.

The files should first be deployed in a test environment to validate their functionality. Performing a Quick Repair and Rebuild is necessary for the new dashlet to appear in the list of available dashlets in the Accounts module’s record view.

custom/modules/Accounts/clients/base/views/superdashlet/superdashlet.js

({
    plugins: ['Dashlet'],
    //custom/modules/Accounts/clients/base/views/superdashlet/superdashlet.js

    rel_records: [],
    module_name: '',
    relationshipName: 'tasks',
    fieldNames: ['name', 'status', 'date_due'],  
    sortField: 'date_due',
    sort_up: true,
    limitx: 100,
    where_fieldname: '',
    where_value: '',    

    initialize: function(options) {
        this._super('initialize', [options]);
    },

        //Note: refresh not implemented
    _renderHtml: function() {
        this._super('_renderHtml');

		this.$el.empty();
		this.$el.append(`
		  <div class="dashlet-container">
		    <input type="text" class="ai-input" placeholder="Show 3 latest tasks with status in progress" style="width:70%; margin-right:8px;" />
		    <button class="ai-send btn btn-sm btn-primary">Ask AI</button>
		    <div class="ai-loading" style="margin-top:10px; display:none;">Loading...</div>
		    <div class="ai-reply" style="margin-top:10px;"></div>
		  </div>
		`);

		this.$('.ai-send').on('click', this.onAIPrompt.bind(this));	
    },

	onAIPrompt: function() {
	    const input = this.$('.ai-input').val().trim();
	    if (!input) return;
	
	    //Note: A more rigorously defined schema should be applied based on the specifications of the AI model.
	    const prompt = `Convert this request into JSON: { "select": "field1,field2,...", "from": "relationship", "where": "field=value", "order_by": "field asc|desc", "limit": "number" } Allowed: - from: tasks, opportunities - tasks fields: id, name, description, date_due, assigned_user_name, date_entered - opportunities fields: id, name, amount, sales_stage, assigned_user_name, date_entered If any value is invalid, respond with: "not found" Only return JSON — no extra text or formatting. Request: ${input}`;
	
	    this.$('.ai-loading').show();
	    this.$('.ai-reply').empty();
	
	    //Note: Content-Security-Policy for your AI model url: connect-src https://generativelanguage.googleapis.com
	    fetch("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=Your_Key", {
	      method: "POST",
	      headers: { "Content-Type": "application/json" },
	      body: JSON.stringify({
	        contents: [{ parts: [{ text: prompt }] }]
	      })
	    })
	    .then(res => res.json())
	    .then(data => {
	      const text = data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
	      const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/);
	      if (!jsonMatch) throw new Error("Invalid format");
	
	      const parsed = JSON.parse(jsonMatch[1]);
	
	      this.fieldNames = parsed.select?.split(',').map(f => f.trim()) || [];
	      this.relationshipName = parsed.from || '';
	      this.sortField = parsed.order_by?.split(' ')[0] || '';
	      this.sort_up = parsed.order_by?.toLowerCase().includes('asc') || false;
	      this.limitx = parseInt(parsed.limit, 10) || 5;
	
	      this.where_fieldname = '';
	      this.where_value = '';
	
	      if (parsed.where) {
	          const wm = parsed.where.match(/^([^=]+)=['"]?(.+?)['"]?$/);
	          if (wm) {
	            this.where_fieldname = wm[1].trim();
	            this.where_value = wm[2].trim();
	          }
	      }
	      this.validateAndRender();
	    })
	    .catch(() => {
	      this.showMessage("Response could not be parsed.");
	    })
	    .finally(() => {
	      this.$('.ai-loading').hide();
	    });
  },

   validateAndRender: function() {

		//Note: Schema validation should be more rigorously enforced here using Sugar model relationships and field type definitions.
          console.log(
		`fieldNames: ${this.fieldNames.join(', ')}\n` +
		`relationshipName: ${this.relationshipName}\n` +
	        `sortField: ${this.sortField}\n` +
	        `sort_up: ${this.sort_up}\n` +
	        `limitx: ${this.limitx}\n` +
	        `where_fieldname: ${this.where_fieldname}\n` +
	        `where_value: ${this.where_value}`);

	    const self = this;
		if (!self.sortField || !self.sortField.trim()) {
		  self.sortField = 'date_entered';
		}
        var model = this.model || App.controller.context.get('model');

        if (!model || !model.cid) {
            this.showMessage('<div>No related records found.</div>');
			return;
        }

        model.getRelatedCollection(this.relationshipName).fetch({
            relate: true,
            limit: 100,    // Note: resolve next_offset
            success: function(collection) {
              
                self.rel_records = collection.sortBy(function(record) {
					  const raw = record.get(self.sortField);
					  if (typeof raw === 'string') {
					    const num = parseFloat(raw);
					    if (!isNaN(num) && isFinite(num)) return Number.isInteger(num) ? parseInt(raw, 10) : num;
					    return raw.toLowerCase();
					  }
					  if (typeof raw === 'number') return Number.isInteger(raw) ? raw : parseFloat(raw);
					  return raw || '';
				});
				
                self.module_name=collection.module;
          
				if (!self.rel_records || self.rel_records.length === 0) {
		            self.showMessage('<div>No related records found.</div>');
		            return;
		        }
		
		        let html = '<table class="table"><thead><tr>';
		   
		        self.fieldNames.forEach(field => {
		            const fieldMeta = App.metadata.getModule(self.module_name).fields[field] || {};

		            const label = App.lang.get(fieldMeta.vname || field, self.module_name); 
		            html += `<th>${label}</th>`;
		        });
		        html += '</tr></thead><tbody>';
		
		        if (!self.sort_up) {
		           self.rel_records.reverse();
		        }

				if (self.where_fieldname) {
			        self.rel_records = self.rel_records.filter(record => {
						  const value = record.get(self.where_fieldname);
						  const target = self.where_value;
						
						  if (typeof value === 'string' && typeof target === 'string') {
						    return value.trim().toLowerCase() === target.trim().toLowerCase();
						  }
						
						  return value === target;
						});
				}
		
		        self.rel_records = self.rel_records.slice(0, self.limitx);
		
		        self.rel_records.forEach(record => {
		            html += '<tr>';
		            self.fieldNames.forEach(field => {
		                html += `<td>${record.get(field) || ''}</td>`;
		            });
		            html += '</tr>';
		        });
		
		        html += '</tbody></table>';
		        self.showMessage(html);

            },
		error: function () {
                self.showMessage('<p>Error loading related records.</p>');
            }
        }); 

  },

  showMessage: function(message) {
    this.$('.ai-reply').html(`<div class="alert alert-warning">${message}</div>`);
  }
});

custom/modules/Accounts/clients/base/views/superdashlet/superdashlet.php

<?php
//custom/modules/Accounts/clients/base/views/superdashlet/superdashlet.php

$viewdefs['Accounts']['base']['view']['superdashlet'] = [
    'dashlets' => [
        [
            'label' => 'SuperDashlet',
            'description' => 'Displays related records based on a relationship.',
            'config' => [

            ],
            'preview' => [

            ],
            'filter' => [
                'module' => [
                    'Accounts',
                ],
                'view' => [
                    'record',
                    'records',
                ],
            ],
        ],
    ],
];

Implementation Highlights and Design Constraints

Data Transfer: Only schema metadata (e.g., field names, modules) is transmitted to the model—no sensitive data leaves the system.

Model Flexibility: Any LLM (Large Language Model) that supports structured prompt-to-JSON or SQL conversion can be used. You must supply your own API key to connect to the AI model. Ensure your Content Security Policy (CSP) includes your AI model url in connect-src generativelanguage.googleapis.com;

Validation: Input must be validated against your system's schema and relationships to ensure safe and correct execution. SugarCRM's APIs natively enforce access controls.

Dashlet UI: A minimal but expandable UI accepts user prompts, fetches AI responses, and renders results accordingly. Only tasks and opportunities modules are supported for now, with a limited field set for each. Default record limit is capped at 100 results per request. Prompt structure is simple but extensible via schemas.

Logic processing: The default sortBy function in a Sidecar collection may not handle all data types accurately or consistently. AI response caching is currently not implemented. A REST API proxy endpoint is needed to allow users to share the same API key for the AI model.

Final Thoughts

This prototype demonstrates how AI can be integrated into Sugar CRM system for smarter, more natural user interactions, where natural language becomes a powerful new interface layer. Whether you're exploring semantic search, contextual insights, or AI-assisted field population, SugarCRM’s flexible architecture combined with structured AI prompting gives you a strong foundation.

I'd love to hear how you're exploring AI within SugarCRM! What use cases have you already tackled, or are excited to try next?

  • Nice post, I look forward to taking some time to delve deeper into your posted solution.

    Our Wolfram Solutions team is working with AI tools, our own products like Wolfram Enterprise Private Cloud, Wolfram|Alpha and Wolfram Language, together with data from our SugarCRM, our ERP, and other internal customer data systems, to facilitate outreach to non-client organizations by creating Dossiers of our current customer base, their interests, outreach strategies employed by Sales, known use of our products etc., and identifying "similar companies" from trustworthy sources. 

    The idea is to generate a handful of top-Leads for business expansion each week for the sales team to contact, and to include a Dossier of that potential customer accessible from the Sugar Lead record itself for Sales to have an informed conversation with the potential customer.

    The Dossiers are tailored to the specific area of the types of businesses we generally work with: Academic, Government, Commercial and sub-categories from there, like Research Labs etc.

    Our Solutions team is very experienced in doing this kind of tailored work for our customers, and we are finally in a place with our Business Systems team where we can turn this expertise inward and leverage the power of Wolfram Solutions for ourselves.

    FrancescaS

  • Hi Francesca, thanks for sharing your feedback and the AI use cases your team is exploring.

    Machine learning approaches can vary significantly depending on the specific scenario and objectives. An existing AI model might require fine-tuning—through additional training—or the use of both specialized and general-purpose models in combination, to optimize for required metrics e.g. lead conversion rates or opportunity values over time. The model's input may consist of a combination of structured data—such as numerical metrics that I discussed in this post sourced from conventional databases—and unstructured data, including details of communications, visual materials, news content, and similar sources. Adjusting these inputs can determine what influences the optimisation metrics the most.

    Once leads with similar semantic characteristics are identified, it becomes possible to explore broader questions, such as searching for and structuring the common traits shared across those leads and business profiles.

  • Thank you Yuri, building custom models that combine structured and unstructured data to meet the specific needs of the customer is what our Solutions team does best.

    Unfortunately I have to admit to a very superficial understanding of the team's work and approach and cannot speak to specifics but, if you are interested in the team's approach, I can certainly put you in touch with the right people to have a more in depth conversation.

    FrancescaS

  • That’s great! I think these are solutions our partner team would be glad to delve into.