Working with Collections

Make.com collections are arrays of data that flow between modules in your scenarios. Understanding how to work with collections effectively is crucial for processing multiple items, iterating through datasets, and building scalable automation workflows.

Understanding Make.com Collections

What are Collections?

Collections in Make.com are arrays of bundles (data objects) that can be processed individually or as a group. When a module outputs multiple items, Make.com treats this as a collection.

// Example collection structure
const collection = [
  { id: 1, name: "Item 1", price: 29.99 },
  { id: 2, name: "Item 2", price: 39.99 },
  { id: 3, name: "Item 3", price: 19.99 }
];

Receiving Collections as Input

1. Processing Individual Collection Items

When your JavaScript module receives a collection, Make.com typically processes each item individually:

// Input will be a single item from the collection
const item = input; // { id: 1, name: "Item 1", price: 29.99 }

// Process the individual item
const processedItem = {
  id: item.id,
  name: item.name.toUpperCase(),
  price: item.price,
  discountedPrice: item.price * 0.9,
  category: item.price > 30 ? 'premium' : 'standard',
  processedAt: new Date().toISOString()
};

return processedItem;

2. Working with the Entire Collection

To process the entire collection at once, use an aggregator module before your JavaScript module:

// After using an aggregator, input contains the full array
const items = input.array || [];

// Process the entire collection
const summary = {
  totalItems: items.length,
  totalValue: items.reduce((sum, item) => sum + (item.price || 0), 0),
  averagePrice: 0,
  categories: {},
  priceRanges: {
    budget: items.filter(item => item.price < 25).length,
    standard: items.filter(item => item.price >= 25 && item.price < 50).length,
    premium: items.filter(item => item.price >= 50).length
  }
};

// Calculate average price
summary.averagePrice = summary.totalItems > 0 ? 
  summary.totalValue / summary.totalItems : 0;

// Group by categories
items.forEach(item => {
  const category = item.category || 'uncategorized';
  summary.categories[category] = (summary.categories[category] || 0) + 1;
});

return summary;

Creating Collections as Output

1. Returning Arrays to Create Collections

// Process input and return an array to create a collection
const baseData = input.data || {};
const variations = input.variations || ['small', 'medium', 'large'];

// Create multiple items from single input
const collectionItems = variations.map(variation => ({
  id: `${baseData.id}-${variation}`,
  name: `${baseData.name} (${variation})`,
  size: variation,
  price: calculatePrice(baseData.basePrice, variation),
  sku: generateSKU(baseData.id, variation),
  createdAt: new Date().toISOString()
}));

function calculatePrice(basePrice, size) {
  const multipliers = { small: 0.8, medium: 1.0, large: 1.2 };
  return basePrice * (multipliers[size] || 1.0);
}

function generateSKU(baseId, size) {
  return `${baseId}-${size.toUpperCase()}-${Date.now()}`;
}

// Return array to create collection
return collectionItems;

2. Conditional Collection Creation

// Create collections based on conditions
const orders = input.orders || [];
const processedOrders = [];

orders.forEach(order => {
  // Process each order
  const processedOrder = {
    orderId: order.id,
    customerName: order.customer.name,
    orderDate: order.date,
    status: order.status
  };
  
  // Add to collection if meets criteria
  if (order.status === 'pending' && order.total > 100) {
    processedOrders.push({
      ...processedOrder,
      priority: 'high',
      requiresApproval: true
    });
  } else if (order.status === 'pending') {
    processedOrders.push({
      ...processedOrder,
      priority: 'normal',
      requiresApproval: false
    });
  }
  
  // Skip cancelled or completed orders
});

return processedOrders;

Collection Transformation Patterns

1. Filtering Collections

// Filter collection based on criteria
const allItems = input.items || [];

const filteredItems = allItems.filter(item => {
  // Multiple filter conditions
  const isActive = item.status === 'active';
  const hasStock = item.inventory > 0;
  const isInPriceRange = item.price >= 10 && item.price <= 1000;
  const hasValidCategory = item.category && item.category.trim().length > 0;
  
  return isActive && hasStock && isInPriceRange && hasValidCategory;
});

// Add filter metadata
const filterResults = filteredItems.map(item => ({
  ...item,
  filtered: true,
  filterAppliedAt: new Date().toISOString(),
  originalIndex: allItems.indexOf(item)
}));

return {
  filteredItems: filterResults,
  originalCount: allItems.length,
  filteredCount: filterResults.length,
  filterEfficiency: (filterResults.length / allItems.length * 100).toFixed(2) + '%'
};

2. Sorting Collections

// Sort collection by multiple criteria
const items = input.items || [];

// Sort by priority, then by date, then by name
const sortedItems = items.sort((a, b) => {
  // Primary sort: priority (high > medium > low)
  const priorityOrder = { high: 3, medium: 2, low: 1 };
  const priorityDiff = (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
  if (priorityDiff !== 0) return priorityDiff;
  
  // Secondary sort: date (newest first)
  const dateA = new Date(a.createdAt || 0);
  const dateB = new Date(b.createdAt || 0);
  const dateDiff = dateB - dateA;
  if (dateDiff !== 0) return dateDiff;
  
  // Tertiary sort: name (alphabetical)
  return (a.name || '').localeCompare(b.name || '');
});

// Add sort metadata
const sortedWithMetadata = sortedItems.map((item, index) => ({
  ...item,
  sortOrder: index + 1,
  sortedAt: new Date().toISOString()
}));

return sortedWithMetadata;

3. Grouping Collections

// Group collection items by category
const items = input.items || [];

const groupedItems = items.reduce((groups, item) => {
  const category = item.category || 'uncategorized';
  
  if (!groups[category]) {
    groups[category] = {
      categoryName: category,
      items: [],
      count: 0,
      totalValue: 0,
      averagePrice: 0
    };
  }
  
  groups[category].items.push(item);
  groups[category].count++;
  groups[category].totalValue += item.price || 0;
  groups[category].averagePrice = groups[category].totalValue / groups[category].count;
  
  return groups;
}, {});

// Convert to array format for Make.com collection
const groupedArray = Object.values(groupedItems).map(group => ({
  ...group,
  groupId: group.categoryName.toLowerCase().replace(/\s+/g, '-'),
  createdAt: new Date().toISOString()
}));

return groupedArray;

Advanced Collection Processing

1. Batch Processing

// Process large collections in batches
const allItems = input.items || [];
const batchSize = 50;

function processBatch(items, batchIndex) {
  return items.map((item, itemIndex) => ({
    ...item,
    batchId: batchIndex,
    itemIndex: itemIndex,
    globalIndex: (batchIndex * batchSize) + itemIndex,
    processedAt: new Date().toISOString(),
    batchProcessed: true
  }));
}

// Create batches
const batches = [];
for (let i = 0; i < allItems.length; i += batchSize) {
  const batch = allItems.slice(i, i + batchSize);
  const processedBatch = processBatch(batch, Math.floor(i / batchSize));
  batches.push({
    batchIndex: Math.floor(i / batchSize),
    itemCount: batch.length,
    items: processedBatch,
    batchCreatedAt: new Date().toISOString()
  });
}

return {
  batches: batches,
  totalBatches: batches.length,
  totalItems: allItems.length,
  batchSize: batchSize
};

2. Collection Merging

// Merge multiple collections
const collection1 = input.collection1 || [];
const collection2 = input.collection2 || [];
const collection3 = input.collection3 || [];

// Merge with source tracking
const mergedCollection = [
  ...collection1.map(item => ({ ...item, source: 'collection1' })),
  ...collection2.map(item => ({ ...item, source: 'collection2' })),
  ...collection3.map(item => ({ ...item, source: 'collection3' }))
];

// Remove duplicates based on ID
const uniqueItems = mergedCollection.reduce((unique, item) => {
  const existingItem = unique.find(u => u.id === item.id);
  
  if (!existingItem) {
    unique.push({
      ...item,
      mergedAt: new Date().toISOString(),
      isDuplicate: false
    });
  } else {
    // Handle duplicate - keep the one from the preferred source
    const sourcePreference = ['collection1', 'collection2', 'collection3'];
    const currentPreference = sourcePreference.indexOf(existingItem.source);
    const newPreference = sourcePreference.indexOf(item.source);
    
    if (newPreference < currentPreference) {
      // Replace with higher priority source
      const index = unique.indexOf(existingItem);
      unique[index] = {
        ...item,
        mergedAt: new Date().toISOString(),
        isDuplicate: true,
        replacedSource: existingItem.source
      };
    }
  }
  
  return unique;
}, []);

return {
  mergedItems: uniqueItems,
  originalCounts: {
    collection1: collection1.length,
    collection2: collection2.length,
    collection3: collection3.length
  },
  finalCount: uniqueItems.length,
  duplicatesRemoved: mergedCollection.length - uniqueItems.length
};

Collection Performance Optimization

1. Efficient Processing

// Optimize for large collections
const items = input.items || [];

// Use Map for O(1) lookups instead of array.find()
const categoryMap = new Map();
const processedItems = [];

// Single pass processing
for (const item of items) {
  // Process item
  const processedItem = {
    id: item.id,
    name: item.name,
    processedAt: new Date().toISOString()
  };
  
  // Update category statistics efficiently
  const category = item.category || 'uncategorized';
  if (!categoryMap.has(category)) {
    categoryMap.set(category, { count: 0, totalValue: 0 });
  }
  
  const categoryStats = categoryMap.get(category);
  categoryStats.count++;
  categoryStats.totalValue += item.price || 0;
  
  processedItems.push(processedItem);
}

// Convert Map to object for output
const categoryStats = Object.fromEntries(
  Array.from(categoryMap.entries()).map(([category, stats]) => [
    category,
    {
      ...stats,
      averagePrice: stats.totalValue / stats.count
    }
  ])
);

return {
  processedItems,
  categoryStats,
  totalProcessed: processedItems.length
};

2. Memory Management

// Process large collections without storing everything in memory
const items = input.items || [];
let processedCount = 0;
const summary = {
  totalValue: 0,
  maxPrice: 0,
  minPrice: Infinity,
  categories: new Set()
};

// Process items one by one without storing results
for (const item of items) {
  if (item.price && typeof item.price === 'number') {
    summary.totalValue += item.price;
    summary.maxPrice = Math.max(summary.maxPrice, item.price);
    summary.minPrice = Math.min(summary.minPrice, item.price);
  }
  
  if (item.category) {
    summary.categories.add(item.category);
  }
  
  processedCount++;
}

// Return summary instead of full processed collection
return {
  summary: {
    totalItems: processedCount,
    totalValue: summary.totalValue,
    averagePrice: processedCount > 0 ? summary.totalValue / processedCount : 0,
    maxPrice: summary.maxPrice === 0 ? 0 : summary.maxPrice,
    minPrice: summary.minPrice === Infinity ? 0 : summary.minPrice,
    uniqueCategories: Array.from(summary.categories),
    categoryCount: summary.categories.size
  },
  processedAt: new Date().toISOString()
};

Best Practices for Collections

  1. Use aggregators when you need to process entire collections
  2. Consider memory usage with large collections
  3. Implement proper error handling for collection processing
  4. Add metadata to track processing information
  5. Use efficient algorithms for sorting and filtering
  6. Validate collection structure before processing
  7. Handle empty collections gracefully
  8. Use consistent data structures in collection items
  9. Consider pagination for very large datasets
  10. Test with various collection sizes during development

Working effectively with Make.com collections enables you to build powerful automation workflows that can handle complex data processing scenarios at scale.