appscript.dev
Automation Intermediate Docs Drive

Generate a printable address book from contacts

Export Northwind's Google Contacts to a formatted Doc you can actually print.

Published Apr 21, 2026

Google Contacts is fine on a screen and useless on paper. There’s no “print the whole address book” button, and copying contacts into a document by hand is the kind of job nobody ever gets round to. So when someone wants a physical list — for a reception desk, an event, or just a drawer — it doesn’t exist.

This script builds one for Northwind. It reads every contact through the People API, sorts them alphabetically, and writes a formatted Google Doc grouped under A–Z headings, with each person’s name, company, email, and phone. The result is a clean, printable address book that you can regenerate in seconds whenever the contact list changes.

What you’ll need

  • Google Contacts with the people you want in the book. Empty contacts (no name) are skipped.
  • The People API enabled in Advanced Google Services: in the editor open Services (the + icon), find People API, and add it.
  • Drive access — the script creates a new Doc in your Drive each run.

The script

// Page size for the People API. 500 is fine; for thousands of
// contacts the script pages through automatically.
const CONTACTS_PAGE_SIZE = 500;

// The fields to request for each contact.
const PERSON_FIELDS = 'names,emailAddresses,phoneNumbers,organizations';

/**
 * Builds a printable, alphabetically grouped address book Doc from
 * every contact in Google Contacts.
 */
function buildAddressBook() {
  // 1. Pull every contact, then sort them by name.
  const all = listContacts();

  if (all.length === 0) {
    Logger.log('No contacts found — nothing to build.');
    return;
  }

  const sorted = all.sort((a, b) =>
    (a.name || '').localeCompare(b.name || ''));

  // 2. Create the Doc and give it a dated title.
  const stamp = new Date().toISOString().slice(0, 10);
  const doc = DocumentApp.create(`Northwind Address Book — ${stamp}`);
  const body = doc.getBody();
  body.appendParagraph('Northwind Studios — Address Book')
    .setHeading(DocumentApp.ParagraphHeading.TITLE);

  // 3. Walk the sorted list, inserting an A–Z heading whenever the
  //    first letter changes.
  let lastLetter = '';
  for (const c of sorted) {
    const letter = (c.name || '?')[0].toUpperCase();
    if (letter !== lastLetter) {
      body.appendParagraph(letter)
        .setHeading(DocumentApp.ParagraphHeading.HEADING1);
      lastLetter = letter;
    }

    // 4. Write one block per contact: bold name, optional company,
    //    then email and phone on their own lines.
    const para = body.appendParagraph('');
    para.appendText(c.name || '').setBold(true);
    if (c.org) para.appendText(`  ·  ${c.org}`);
    para.appendText('\n');
    if (c.email) para.appendText(c.email + '\n');
    if (c.phone) para.appendText(c.phone + '\n');
  }

  // 5. Save and close so the Doc is ready to open or print.
  doc.saveAndClose();
  Logger.log('Address book created: ' + doc.getUrl());
}

/**
 * Returns every Google contact as a flat list of plain objects,
 * paging through the People API until there are no more results.
 */
function listContacts() {
  const out = [];
  let pageToken;
  do {
    const res = People.People.Connections.list('people/me', {
      personFields: PERSON_FIELDS,
      pageSize: CONTACTS_PAGE_SIZE,
      pageToken,
    });

    // Flatten each person down to the four fields we print.
    for (const p of res.connections || []) {
      out.push({
        name: p.names?.[0]?.displayName,
        email: p.emailAddresses?.[0]?.value,
        phone: p.phoneNumbers?.[0]?.value,
        org: p.organizations?.[0]?.name,
      });
    }
    pageToken = res.nextPageToken;
  } while (pageToken);
  return out;
}

How it works

  1. buildAddressBook calls listContacts to fetch everyone, then stops early if there are no contacts at all — no point creating an empty Doc.
  2. The list is sorted with localeCompare so names order naturally, and a missing name sorts as an empty string rather than throwing.
  3. A new Doc is created with a dated title like Northwind Address Book — 2026-04-21, so each run is its own snapshot you can keep or discard.
  4. The loop tracks lastLetter. Whenever a contact’s first letter differs from the previous one, it inserts an A–Z heading — that’s what gives the printed book its tabbed structure.
  5. Each contact becomes one paragraph: the name in bold, the company appended after a separator if there is one, then email and phone on their own lines. Every field is guarded, so a contact with only a name still prints cleanly.
  6. listContacts uses the People API’s Connections.list and follows nextPageToken in a do...while loop, so it works whether you have 50 contacts or 5,000. The ?. chains mean a contact missing a phone number or company simply yields undefined instead of an error.

Example run

Given three Google Contacts, the generated Doc looks like this:

ContactNameCompanyEmailPhone
1Amara OkaforRiverside Co[email protected]020 7946 0100
2Ben Carter[email protected]
3Priya ShahAcme Ltd[email protected]07700 900123

In the Doc that becomes:

Northwind Studios — Address Book        (title)

A                                       (heading 1)
Amara Okafor  ·  Riverside Co            (bold name + company)
[email protected]
020 7946 0100

B                                       (heading 1)
Ben Carter                              (bold name, no company)
[email protected]

P                                       (heading 1)
Priya Shah  ·  Acme Ltd
[email protected]
07700 900123

Open it, hit print, and you have a tidy A–Z directory.

Run it

This is an on-demand job — you regenerate the book when contacts have changed enough to warrant a fresh print:

  1. In the Apps Script editor, select buildAddressBook and click Run.
  2. Approve the authorisation prompt the first time — it covers Contacts and Drive access.
  3. Open the new Doc from the logged URL, check it, and print.

If you’d rather it refresh on a schedule, add a Time-driven trigger on a Month timer so a fresh book lands in Drive each quarter.

Watch out for

  • Each run creates a new Doc — old address books pile up in Drive. Delete the previous one, or have the script open a fixed Doc and clear its body instead of calling DocumentApp.create.
  • The script reads the first email, phone, and organisation for each contact. People with several numbers will only show one; if you need them all, loop over the arrays instead of taking index 0.
  • Contacts with no name are skipped from the headings logic by defaulting to '?', but they’ll still appear under a ? heading. Clean up nameless contacts in Google Contacts if you don’t want that section.
  • The People API counts against a per-user quota. One full export is trivial; don’t call buildAddressBook in a tight loop.
  • “Other contacts” — addresses Google auto-saved but you never added — are not returned by Connections.list. Only contacts you’ve actually saved appear in the book.

Related