Skip to main content

CSV Import & Empty Value Enrichment

A common workflow is importing a CSV with partial data and enriching only the missing values. This pattern saves API calls and avoids overwriting existing data.
1

Import CSV

Your CSV has partial data—some fields filled, some empty
2

Create View

Filter to show only rows where the target field is empty
3

Set Up Enrichment

Either convert the column to code or create a separate enrichment column
4

Run Empty Rows Only

Run only the filtered rows to enrich missing values

Example Scenario

You import a CSV with contact data. Some emails are already filled in, others are missing:
First NameLast NameCompanyEmail
JohnDoeAcme Corp[email protected]
JaneSmithTechStart
BobWilsonBigCo[email protected]
AliceBrownStartupXYZ
You want to enrich only Jane and Alice’s emails without overwriting John and Bob’s existing data.

Two Approaches

There are two ways to handle this:
Best for: When you want to fill in the same column that has missing values.
  1. Create a view that filters for empty Email values
  2. Convert the Email column to an enrichment column (or it already is one)
  3. Navigate to the view and run only those rows
Be careful not to run rows that already have values—the view helps you target only the empty ones.
// Email column code - runs on rows you manually select
const firstName = await ctx.thisRow.get("First Name");
const lastName = await ctx.thisRow.get("Last Name");
const company = await ctx.thisRow.get("Company");

// Find and enrich contact info
const contactInfo = await services.person.contact.get({
  firstName,
  lastName,
  company,
});

return contactInfo?.work_emails?.[0] || contactInfo?.personal_emails?.[0];
You cannot reference your own column. ctx.thisRow.get("Email") inside the Email column will not work. If you need to check for existing values, you must use a separate enrichment column that reads from the original column.

Creating a View for Empty Values

Create a view that shows only rows where the Email field is empty. This lets you target exactly which rows need enrichment.
  1. In the right-hand panel, click Filters
  2. Click Add Filter
  3. Set the filter:
    • Column: Email
    • Operator: is empty
  4. Click Save filters as view
  5. Name your view (e.g., “Missing Emails”)

Running the Enrichment

Once your view is set up:
  1. Click into the view — Only rows with empty Email values appear
  2. Select the rows — Choose which rows to enrich (or select all in the view)
  3. Click “Run” — Execute the enrichment on selected rows only
  4. Watch progress — As emails are enriched, rows disappear from the view (they no longer match the “is empty” filter)
This gives you real-time progress feedback and ensures you never accidentally overwrite existing data.

Complete Example: Approach B

Here’s a full example using the separate enrichment column approach:
// ============================================================================
// COLUMN: "Email (Enriched)"
// ============================================================================
// This column checks the original "Email" column first, then enriches if empty.

// Step 1: Check if email already exists in the original column
const existingEmail = await ctx.thisRow.get("Email");

if (existingEmail && existingEmail.trim() !== "") {
  // Email exists - return it without making any API calls
  return existingEmail;
}

// Step 2: No existing email - gather data for enrichment
const firstName = await ctx.thisRow.get("First Name");
const lastName = await ctx.thisRow.get("Last Name");
const company = await ctx.thisRow.get("Company");

// Step 3: Find contact information
const contactInfo = await services.person.contact.get({
  firstName,
  lastName,
  company,
});

// Step 4: Return the best email found
return contactInfo?.work_emails?.[0] || contactInfo?.personal_emails?.[0];
Result:
First NameLast NameCompanyEmailEmail (Enriched)
JohnDoeAcme Corp[email protected][email protected]
JaneSmithTechStart[email protected]
BobWilsonBigCo[email protected][email protected]
AliceBrownStartupXYZ[email protected]
The enrichment column returns existing values for John and Bob (no API calls), and enriches only Jane and Alice.

Workflow Summary

StepWhat HappensWhy It Matters
1. ImportUser imports CSV with some empty valuesStarting point
2. Create ViewFilter WHERE Email IS EMPTYShows only rows needing work
3. Set Up CodeEither convert column or create separate onePrepares the enrichment
4. Run in ViewRun only the filtered/selected rowsTargeted enrichment
5. ProgressEnriched rows leave view as they’re filled inVisual feedback

Best Practices

A column cannot use ctx.thisRow.get() to read its own value. If you need to check for existing data before enriching, create a separate enrichment column that reads from the original column.
// ❌ WRONG: Inside "Email" column
const existingEmail = await ctx.thisRow.get("Email"); // Won't work!

// ✅ CORRECT: Inside "Email (Enriched)" column
const existingEmail = await ctx.thisRow.get("Email"); // Reads from different column
Create views for “needs enrichment” states. Navigate to the view before running to ensure you only process rows that actually need enrichment.
Select manageable batches (50-100 rows) to run at a time. Monitor for errors before processing all data.
When enriching contact info, prioritize work emails over personal for B2B outreach:
const contactInfo = await services.person.contact.get({ firstName, lastName, company });

// Prioritize work email
return contactInfo?.work_emails?.[0] || contactInfo?.personal_emails?.[0];
Always validate that you have the required input data before making API calls:
const firstName = await ctx.thisRow.get("First Name");
const lastName = await ctx.thisRow.get("Last Name");

if (!firstName || !lastName) {
  return; // Can't enrich without name
}

// Proceed with enrichment...