Guide

Export Contacts to CSV from the CLI

Sales asks for the contact list 'as a spreadsheet,' and you reach for a Python script. You don't need one. The CLI returns contacts as JSON; jq's @csv operator turns them into properly quoted rows that Excel and Google Sheets open without complaint. This guide builds a correct CSV export — a header row, RFC 4180 quoting, and null-safe fallbacks for the ragged fields contacts always have, like a missing surname, a blank company, or a person with three email addresses.

Written by Pouya Sanooei Software Engineer

Reviewed by Qasim Muhammad

VerifiedCLI 3.1.17 · Gmail, Outlook · last tested June 9, 2026

Command references used in this guide: nylas contacts list, nylas contacts search, and nylas contacts show.

How do I export contacts to CSV?

You export contacts to CSV by piping the CLI's JSON through jq's @csv operator, which formats an array of values as one correctly quoted CSV row. The nylas contacts list --json command returns every contact as a JSON object; jq maps each one to a row of the fields you want. The -r flag prints raw output, so the file holds literal CSV rather than JSON-escaped strings.

Use @csv instead of joining values with commas yourself because CSV quoting is harder than it looks. A company named “Acme, Inc.” contains a comma; a job title with a quote in it breaks a naive join. The @csv operator implements the quoting rules from RFC 4180, the 2005 spec that Excel and Google Sheets both follow, so those cases just work. One operator replaces a pile of escaping code.

# Build a CSV with a header row, one contact per line
{
  echo 'given_name,surname,email,company'
  nylas contacts list --json --limit 500 \
    | jq -r '.[] | [
        (.given_name // ""),
        (.surname // ""),
        (.emails[0].email // ""),
        (.company_name // "")
      ] | @csv'
} > contacts.csv

Why does my CSV need null-safe fallbacks?

Contact data is ragged: one record has a surname and no company, the next has a company and no email. Without a fallback, jq emits null for a missing field, and a row with a missing trailing value can come out one column short. The // empty and // "" operators in jq supply a default, so every one of your 500 rows carries the same column count.

The distinction matters: // "" substitutes an empty string for a null field, keeping the cell present but blank, while // empty drops the value entirely from a stream. For a fixed-width CSV row you almost always want // "" on each cell. Reserve // empty for skipping whole contacts, such as records with no email at all. The jq manual documents both as alternative operators.

# Skip contacts with no email, blank-fill the rest
nylas contacts list --json --limit 500 \
  | jq -r '.[]
      | select(.emails[0].email // empty)
      | [
          (.given_name // ""),
          (.surname // ""),
          (.emails[0].email // ""),
          (.company_name // ""),
          (.job_title // "")
        ] | @csv' > contacts.csv

How do I handle contacts with multiple emails?

A single contact often carries two or three addresses — a work email, a personal one, an old alias. The Nylas v3 contact object stores them as an emails array of objects, each with an email and a type. You have two choices: collapse the array into one cell, or explode one contact into several rows. Pick the shape your downstream tool expects.

To collapse, map the array to its email values and join them with a semicolon — not a comma, which would break the CSV column. To explode, iterate the array so each address becomes its own row, repeating the name fields. A mailing-list importer usually wants exploded rows; a CRM dedupe usually wants the collapsed form. The first example below joins; the second emits one row per email for a contact, so a person with 3 addresses produces 3 rows.

# Collapse: all emails in one semicolon-joined cell
nylas contacts list --json --limit 500 \
  | jq -r '.[] | [
      (.given_name // ""),
      (.surname // ""),
      ((.emails // []) | map(.email) | join(";")),
      (.company_name // "")
    ] | @csv' > contacts-joined.csv

# Explode: one row per email address
nylas contacts list --json --limit 500 \
  | jq -r '.[]
      | . as $c
      | (.emails // [])[]
      | [ ($c.given_name // ""), ($c.surname // ""), .email, .type ]
      | @csv' > contacts-exploded.csv

How do I export only a subset of contacts?

The nylas contacts search command filters server-side before the JSON ever reaches jq, which is faster than pulling every contact and discarding most. It accepts --company, --email, --phone, and --source, plus --has-email to drop records with no address. Company name is a partial match, so --company Acme catches “Acme Inc” too.

Source filtering is the most useful for a clean export. The --source flag takes address_book, inbox, or domain. Pulling only address_book excludes the auto-collected addresses Gmail and Outlook harvest from your inbox, which are usually noise in a contact export. Provider IDs are native: Google polls roughly every 5 minutes, while Microsoft syncs in real time through Graph.

# Only saved address-book contacts at one company, with an email
{
  echo 'given_name,surname,email,company'
  nylas contacts search --company "Acme" --source address_book --has-email --json \
    | jq -r '.[] | [
        (.given_name // ""),
        (.surname // ""),
        (.emails[0].email // ""),
        (.company_name // "")
      ] | @csv'
} > acme-contacts.csv

How do I make the contacts CSV open cleanly in Excel?

Two details decide whether the contacts CSV opens cleanly in Excel: character encoding and how the app reads the first row. Write the file as UTF-8 so accented names survive; if Excel still shows garbled characters, prepend a UTF-8 byte-order mark so it detects the encoding. Excel treats the first line as a header automatically, which is why echoing a header row before the jq output matters.

Add the BOM with a 3-byte prefix before the header, or set it in jq. Google Sheets needs no BOM — it reads UTF-8 by default — so keep one canonical export and only add the prefix for Excel users. Quote the whole pipeline once, save it as a shell script, and anyone on the team can regenerate the export with a single command in under 2 seconds for a few hundred contacts.

# UTF-8 BOM so Excel reads accented names correctly
{
  printf '\xEF\xBB\xBF'
  echo 'given_name,surname,email,company'
  nylas contacts list --json --limit 500 \
    | jq -r '.[] | [
        (.given_name // ""),
        (.surname // ""),
        (.emails[0].email // ""),
        (.company_name // "")
      ] | @csv'
} > contacts-excel.csv

Next steps