Many integrations need customer-specific behavior: custom field mappings, record filters, or feature toggles. Customers running custom schemas in their CRMs or ERPs need integrations that adapt to their setup without you forking code.
Nango solves this with connection metadata — arbitrary JSON stored on each connection that your functions can read at runtime.
How it works
Each connection can carry a metadata object. You set it from your backend (when a customer configures their integration), and read it inside any sync or action function to customize behavior per customer.
Customer A: { "fieldMapping": { "internal_id": "custom_field_123" } }
Customer B: { "fieldMapping": { "internal_id": "cf_456" }, "filterStatus": "active" }
Set metadata from your backend after a customer configures their integration.
import { Nango } from '@nangohq/node' ;
const nango = new Nango ({ secretKey: process . env . NANGO_SECRET_KEY ! });
// Replace all metadata
await nango . setMetadata ( '<INTEGRATION-ID>' , '<CONNECTION-ID>' , {
fieldMapping: { internal_id: 'custom_field_123' },
filterStatus: 'active' ,
});
// Merge/update specific keys
await nango . updateMetadata ( '<INTEGRATION-ID>' , '<CONNECTION-ID>' , {
filterStatus: 'closed' ,
});
# Set (replaces all metadata)
curl --request POST \
--url https://api.nango.dev/connection/ < CONNECTION-I D > /metadata \
--header 'Authorization: Bearer <NANGO_SECRET_KEY>' \
--header 'Content-Type: application/json' \
--header 'Provider-Config-Key: <INTEGRATION-ID>' \
--data '{ "fieldMapping": { "internal_id": "custom_field_123" } }'
# Update (merges with existing metadata)
curl --request PATCH \
--url https://api.nango.dev/connection/ < CONNECTION-I D > /metadata \
--header 'Authorization: Bearer <NANGO_SECRET_KEY>' \
--header 'Content-Type: application/json' \
--header 'Provider-Config-Key: <INTEGRATION-ID>' \
--data '{ "filterStatus": "active" }'
When reading metadata inside a sync with nango.getMetadata(), values are cached for up to 60 seconds. Changes made mid-execution will take effect on the next sync run.
Inside any sync or action, use nango.getMetadata() to access the connection’s metadata.
import { createSync } from 'nango' ;
interface Metadata {
fieldMapping ?: Record < string , string >;
filterStatus ?: string ;
}
export default createSync ({
exec : async ( nango ) => {
const metadata = await nango . getMetadata < Metadata >();
const customField = metadata . fieldMapping ?. internal_id ?? 'default_field' ;
const statusFilter = metadata . filterStatus ?? 'all' ;
const res = await nango . get ({
endpoint: '/crm/v3/objects/contacts' ,
params: {
properties: `firstname,lastname,email, ${ customField } ` ,
... ( statusFilter !== 'all' ? { filterGroups: JSON . stringify ([{ filters: [{ propertyName: 'hs_lead_status' , operator: 'EQ' , value: statusFilter }] }]) } : {}),
}
});
// Map using customer-specific field names
const contacts = res . data . results . map (( c : any ) => ({
id: c . id ,
internalId: c . properties [ customField ] ?? null ,
email: c . properties . email ,
}));
await nango . batchSave ( contacts , 'Contact' );
}
}) ;
Common use cases
Customers using CRMs or ERPs often store data in custom fields with provider-specific names. Let customers map their custom fields to your internal fields through your UI, then persist the mapping in metadata.
Fetch available custom fields
Use an action to retrieve the customer’s custom fields from the external API. const { fields } = await nango . triggerAction ( 'hubspot' , connectionId , 'list-custom-fields' , {});
// Returns: [{ name: 'custom_field_123', label: 'Customer ID' }, ...]
Show mapping UI to the customer
Display the external fields in your product UI and let the customer map them to your internal fields.
Save the mapping to metadata
await nango . setMetadata ( 'hubspot' , connectionId , {
fieldMapping: {
internal_customer_id: selectedField . name ,
}
});
Use the mapping in your sync
Read fieldMapping from metadata inside your sync function and use the mapped field names in API requests.
Feature flags per customer
Enable or disable integration features per customer without deploying new code. // Store in metadata
await nango . setMetadata ( 'salesforce' , connectionId , {
features: {
syncOpportunities: true ,
syncLeads: false ,
bidirectionalSync: true ,
}
});
// Read in sync
const { features } = await nango . getMetadata <{ features : Record < string , boolean > }>();
if ( features . syncOpportunities ) {
// run opportunity sync logic
}
Let customers define which records to sync — by status, date range, team, or any other criteria. // Customer configures: only sync deals created in the last 90 days, status = 'open'
await nango . setMetadata ( 'pipedrive' , connectionId , {
filter: { status: 'open' , daysBack: 90 }
});
// Inside sync
const { filter } = await nango . getMetadata <{ filter : { status : string ; daysBack : number } }>();
const since = new Date ( Date . now () - filter . daysBack * 86400000 ). toISOString ();
Best practices
Start with defaults — only add per-customer config when you see the same need recurring across customers.
Use a typed schema — define a TypeScript interface for your metadata to catch errors early.
Build a config UI — capture customer config in your product, then persist it with setMetadata. Never ask customers to set metadata manually.
Version your schema — when you add or rename metadata keys, handle both old and new formats in your functions during the transition.
Fold universals back into defaults — if a per-customer config becomes universal, move it into the function’s default behavior to reduce complexity.
You can view and edit connection metadata in the Nango dashboard: Connections tab → select a connection → Authorization tab.