Mini apps

Embed SDK

Build mini apps that receive Bigmind context and work with CRM data.

The @bigmind/embed-sdk package is the client-side SDK for mini apps. Use it to receive context from Bigmind, read the current CRM object, and create focused embedded experiences for Sidekick, CRM records, and standalone mini app pages.

#Install and import

Mini apps created in Bigmind already include the SDK in the runtime. Import the pieces you need from @bigmind/embed-sdk:

import { bigmind, SidepanelProps } from '@bigmind/embed-sdk';

#Working with props

Mini apps receive context through props. In Sidekick, those props usually describe what the user is looking at, such as an account, deal, contact, lead, or website.

import { FC } from 'react';
import { SidepanelProps } from '@bigmind/embed-sdk';

const MiniApp: FC<SidepanelProps> = (props) => {
  const { objectType, objectId } = props;

  return (
    <div className="p-3">
      <div className="rounded-lg bg-white p-3 shadow">
        <p className="text-lg font-semibold text-black">Current context</p>
        <p className="mt-2 text-sm text-black">
          {objectType}: {objectId}
        </p>
      </div>
    </div>
  );
};

export default MiniApp;

Use the mini app editor test panel to pass sample props as JSON and validate how your app behaves for different records and surfaces.

#CRM integration

Mini apps can interact with the connected CRM through the SDK. The bigmind.crm namespace provides CRUD operations for common CRM objects without requiring each mini app to call HubSpot or Salesforce directly.

Available objects

  • Contacts: bigmind.crm.contacts
  • Accounts: bigmind.crm.accounts
  • Deals: bigmind.crm.deals
  • Leads: bigmind.crm.leads

Supported operations

  • .create(data) - Create a new record
  • .get(id) - Get a record by ID
  • .update(id, data) - Update an existing record
  • .delete(id) - Delete a record
  • .search(options) - Search records with filters and pagination

#Create CRM records

Create a contact

import { bigmind } from '@bigmind/embed-sdk';

const result = await bigmind.crm.contacts.create({
  firstName: 'John',
  lastName: 'Doe',
  email: 'john.doe@example.com',
  phone: '+1-555-0123',
  title: 'VP of Sales',
  accountId: 'company_123',
});

if (result.success) {
  console.log('Contact created:', result.data);
} else {
  console.error('Error:', result.error);
}

Create a deal with associations

const result = await bigmind.crm.deals.create({
  name: 'Enterprise License Deal',
  amount: 50000,
  stage: 'Qualification',
  closeDate: '2025-06-30',
  accountId: 'company_123',
  contactIds: ['contact_456', 'contact_789'],
});

if (result.success) {
  console.log('Deal created with ID:', result.data.id);
}

#Read, update, and delete records

const contact = await bigmind.crm.contacts.get('contact_123');
if (contact.success) {
  console.log('Contact name:', contact.data.firstName, contact.data.lastName);
}

await bigmind.crm.deals.update('deal_123', {
  stage: 'Negotiation',
  amount: 75000,
});

const deleted = await bigmind.crm.leads.delete('lead_456');
if (deleted.success) {
  console.log('Lead deleted successfully');
}

#Field reference

The SDK uses generic field names and maps them to the connected CRM.

Contacts

FieldDescription
firstNameContact first name
lastNameContact last name
emailEmail address
phonePhone number
titleJob title
accountIdAssociated account or company ID
ownerIdCRM user who owns the record

Accounts

FieldDescription
nameCompany name
domainCompany website domain
ownerIdCRM user who owns the record

Deals

FieldDescription
nameDeal name
amountDeal value
stagePipeline stage
closeDateExpected close date, formatted as YYYY-MM-DD
accountIdAssociated account or company ID
contactIdsArray of associated contact IDs
ownerIdCRM user who owns the record

Leads

FieldDescription
firstNameLead first name
lastNameLead last name
emailEmail address
phonePhone number
titleJob title
companyCompany name
ownerIdCRM user who owns the record

#CRM-specific behavior

The SDK handles CRM differences automatically. Salesforce uses native foreign key fields, such as AccountId on contacts. HubSpot uses associations to link records. You use the same generic fields in your mini app and the SDK maps them for the connected CRM.

#Error handling

CRM methods return a result object with a success boolean. Check it before reading data.

const result = await bigmind.crm.contacts.create({ ... });

if (result.success) {
  console.log(result.data);
} else {
  console.error(result.error);
}

#Search CRM records

Each CRM object supports .search(options) for querying records with filters.

Search options

  • filters - A filter predicate defining your search criteria
  • limit - Maximum number of results to return
  • offset - Number of results to skip for pagination

Filter predicate structure

interface FilterPredicate {
  operator: 'AND' | 'OR';
  conditions: (Filter | FilterGroup)[];
}

interface Filter {
  name: string;
  type: FilterType;
  operator: FilterOperator;
  value?: string;
}

interface FilterGroup {
  operator: 'AND' | 'OR';
  filters: Filter[];
}

Filter operators

OperatorDescription
equalsExact match
notEqualsDoes not equal
containsContains substring
notContainsDoes not contain substring
startsWithStarts with value
endsWithEnds with value
greaterThanGreater than
lessThanLess than
greaterThanOrEqualGreater than or equal
lessThanOrEqualLess than or equal
is-emptyField is empty or null
is-not-emptyField is not empty

Search accounts by domain

const result = await bigmind.crm.accounts.search({
  filters: {
    operator: 'AND',
    conditions: [
      {
        name: 'domain',
        type: 'crm-account',
        operator: 'contains',
        value: 'acme.com',
      },
    ],
  },
  limit: 50,
});

if (result.success) {
  console.log(`Found ${result.data.total} accounts`);
  result.data.items.forEach((account) => {
    console.log(`${account.name} - ${account.domain}`);
  });
}

Search contacts with multiple conditions

const result = await bigmind.crm.contacts.search({
  filters: {
    operator: 'AND',
    conditions: [
      {
        name: 'email',
        type: 'crm-contact',
        operator: 'endsWith',
        value: '@enterprise.com',
      },
      {
        name: 'title',
        type: 'crm-contact',
        operator: 'contains',
        value: 'Director',
      },
    ],
  },
});

Use OR logic

const result = await bigmind.crm.deals.search({
  filters: {
    operator: 'OR',
    conditions: [
      {
        name: 'stage',
        type: 'crm-opportunity',
        operator: 'equals',
        value: 'Negotiation',
      },
      {
        name: 'amount',
        type: 'crm-opportunity',
        operator: 'greaterThan',
        value: '100000',
      },
    ],
  },
});

Paginate results

async function searchAllLeads(query: string) {
  const allLeads = [];
  let offset = 0;
  const limit = 100;
  let hasMore = true;

  while (hasMore) {
    const result = await bigmind.crm.leads.search({
      filters: {
        operator: 'AND',
        conditions: [
          {
            name: 'company',
            type: 'crm-lead',
            operator: 'contains',
            value: query,
          },
        ],
      },
      limit,
      offset,
    });

    if (!result.success) break;

    allLeads.push(...result.data.items);
    hasMore = result.data.hasMore;
    offset += limit;
  }

  return allLeads;
}

Search result shape

interface CrmSearchResult<T> {
  items: T[];
  total: number;
  limit: number;
  offset: number;
  hasMore: boolean;
}

#Example: lead capture mini app

import { FC, useState } from 'react';
import { bigmind } from '@bigmind/embed-sdk';

const LeadCaptureForm: FC = () => {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setStatus('loading');

    const formData = new FormData(e.currentTarget);
    const result = await bigmind.crm.leads.create({
      firstName: formData.get('firstName') as string,
      lastName: formData.get('lastName') as string,
      email: formData.get('email') as string,
      company: formData.get('company') as string,
      title: formData.get('title') as string,
    });

    setStatus(result.success ? 'success' : 'error');
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input name="firstName" placeholder="First Name" required />
      <input name="lastName" placeholder="Last Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <input name="company" placeholder="Company" />
      <input name="title" placeholder="Job Title" />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Creating...' : 'Create Lead'}
      </button>
      {status === 'success' && <p>Lead created successfully</p>}
      {status === 'error' && <p>Failed to create lead</p>}
    </form>
  );
};

export default LeadCaptureForm;

#Example: account lookup mini app

import { FC, useState } from 'react';
import { bigmind, CrmAccount } from '@bigmind/embed-sdk';

const AccountLookup: FC = () => {
  const [domain, setDomain] = useState('');
  const [accounts, setAccounts] = useState<CrmAccount[]>([]);
  const [loading, setLoading] = useState(false);

  const searchAccounts = async () => {
    if (!domain.trim()) return;

    setLoading(true);
    const result = await bigmind.crm.accounts.search({
      filters: {
        operator: 'AND',
        conditions: [
          {
            name: 'domain',
            type: 'crm-account',
            operator: 'contains',
            value: domain.trim(),
          },
        ],
      },
      limit: 20,
    });

    if (result.success) {
      setAccounts(result.data.items);
    }
    setLoading(false);
  };

  return (
    <div className="space-y-4 p-4">
      <input
        value={domain}
        onChange={(e) => setDomain(e.target.value)}
        placeholder="Enter domain"
      />
      <button onClick={searchAccounts} disabled={loading}>
        {loading ? 'Searching...' : 'Search'}
      </button>
      {accounts.map((account) => (
        <div key={account.id}>{account.name} - {account.domain}</div>
      ))}
    </div>
  );
};

export default AccountLookup;

#Security model

  • Sandboxed rendering: Mini apps run in an embedded environment isolated from the parent application.
  • User permissions: SDK calls run in the context of the current user and connected workspace.
  • CRM abstraction: Mini apps use Bigmind SDK methods instead of storing HubSpot or Salesforce credentials.
  • Secret handling: Keep secrets out of client-side mini app code. Use server-side tools for private tokens and backend systems.

#Best practices

  • Keep each mini app focused on one workflow, panel, or decision.
  • Design for the surface where it appears, especially Sidekick's compact panel.
  • Handle empty, loading, and error states explicitly.
  • Use TypeScript types from the SDK to catch mistakes early.
  • Test with realistic props in the editor before publishing.
Updated 9/29/2025