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
| Field | Description |
|---|---|
firstName | Contact first name |
lastName | Contact last name |
email | Email address |
phone | Phone number |
title | Job title |
accountId | Associated account or company ID |
ownerId | CRM user who owns the record |
Accounts
| Field | Description |
|---|---|
name | Company name |
domain | Company website domain |
ownerId | CRM user who owns the record |
Deals
| Field | Description |
|---|---|
name | Deal name |
amount | Deal value |
stage | Pipeline stage |
closeDate | Expected close date, formatted as YYYY-MM-DD |
accountId | Associated account or company ID |
contactIds | Array of associated contact IDs |
ownerId | CRM user who owns the record |
Leads
| Field | Description |
|---|---|
firstName | Lead first name |
lastName | Lead last name |
email | Email address |
phone | Phone number |
title | Job title |
company | Company name |
ownerId | CRM 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 criterialimit- Maximum number of results to returnoffset- 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
| Operator | Description |
|---|---|
equals | Exact match |
notEquals | Does not equal |
contains | Contains substring |
notContains | Does not contain substring |
startsWith | Starts with value |
endsWith | Ends with value |
greaterThan | Greater than |
lessThan | Less than |
greaterThanOrEqual | Greater than or equal |
lessThanOrEqual | Less than or equal |
is-empty | Field is empty or null |
is-not-empty | Field 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.
