# Google Contacts API JavaScript example

Source: https://developer.nylas.com/docs/cookbook/contacts/google-contacts-api-javascript/

You can display a user's Google Contacts in a JavaScript app without calling the Google People API directly. The safe pattern has three parts: the browser calls your own server, your server calls Nylas with a stored Google grant ID, and the browser renders only the contact fields it needs.

This keeps your Nylas API key out of client-side code and gives you one Contacts API shape that also works for Outlook and Exchange later.

## Before you begin

You'll need:

- A [Nylas application](/docs/v3/getting-started/) with a valid API key
- A Google grant created through [hosted OAuth](/docs/cookbook/use-cases/build/connect-user-accounts-oauth/) or another supported auth flow
- Google contact scopes on the connector, usually `https://www.googleapis.com/auth/contacts.readonly`
- A server route where your frontend can request contacts

For Google-specific source behavior, scopes, and quotas, see [How to list Google contacts](/docs/cookbook/contacts/list-contacts-google/).

## Create a server route for Google Contacts

Never call Nylas directly from browser JavaScript with your API key. Create a server route that reads the user's stored grant ID, calls `/v3/grants/{grant_id}/contacts`, and returns a trimmed response to the browser.

The Express route below returns up to 50 saved Google contacts. It maps the full Nylas contact object into a small view model with `id`, `name`, `email`, `company`, and `jobTitle`.

```js [googleContactsServer-Node.js]


const app = express();
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY });

app.get("/api/google-contacts", async (req, res) => {
  const grantId = await getGrantIdForCurrentUser(req);

  const response = await nylas.contacts.list({
    identifier: grantId,
    queryParams: {
      limit: 50,
      source: "address_book",
    },
  });

  const contacts = response.data.map((contact) => ({
    id: contact.id,
    name: [contact.givenName, contact.surname].filter(Boolean).join(" "),
    email: contact.emails?.[0]?.email ?? "",
    company: contact.companyName ?? "",
    jobTitle: contact.jobTitle ?? "",
  }));

  res.json({ contacts });
});
```

If the user has more than 50 contacts, follow `response.nextCursor` and fetch another page with `pageToken`. For large address books, cache the result on your server and refresh it with contact webhooks instead of listing contacts on every page load.

## Render contacts in browser JavaScript

The browser calls your `/api/google-contacts` route, not Nylas. The example below renders a contact list with names and email addresses.

```html
<ul id="contacts"></ul>

<script type="module">
  const list = document.querySelector("#contacts");
  const response = await fetch("/api/google-contacts");
  const { contacts } = await response.json();

  list.replaceChildren(
    ...contacts.map((contact) => {
      const item = document.createElement("li");
      item.textContent = `${contact.name || contact.email} <${contact.email}>`;
      return item;
    }),
  );
</script>
```

For React, use the same route from an effect or data loader. Keep the grant lookup and API key on the server side.

```jsx
export function GoogleContactsList() {
  const [contacts, setContacts] = useState([]);

  useEffect(() => {
    fetch("/api/google-contacts")
      .then((response) => response.json())
      .then((body) => setContacts(body.contacts));
  }, []);

  return (
    <ul>
      {contacts.map((contact) => (
        <li key={contact.id}>
          <strong>{contact.name || contact.email}</strong>
          {contact.email && <span> {contact.email}</span>}
        </li>
      ))}
    </ul>
  );
}
```

## Add search or autocomplete

The Contacts API filters by `email`, `phone_number`, `group`, and `source`. It doesn't have a fuzzy name-search parameter, so a JavaScript contact picker usually loads contacts once and filters them locally.

```js
const searchContacts = (contacts, query) => {
  const term = query.trim().toLowerCase();

  return contacts
    .filter((contact) => {
      const name = contact.name.toLowerCase();
      const email = contact.email.toLowerCase();
      return name.includes(term) || email.includes(term);
    })
    .slice(0, 8);
};
```

For a production recipient picker, flatten each contact into one entry per email address, remove duplicate records by lowercase email, and rank frequent correspondents before alphabetical matches. The [recipient autocomplete recipe](/docs/cookbook/contacts/recipient-autocomplete/) covers that pattern.

## Things to know about Google Contacts in JavaScript

**Keep API credentials off the client.** Browser code should never hold your Nylas API key. Use a server route, server action, or edge function that authenticates the user and looks up the grant ID.

**Use `source=address_book` for saved contacts.** Google also has automatically created "other contacts" from Gmail. Those appear through the `inbox` source and need the `contacts.other.readonly` scope.

**Expect sync delay.** Google doesn't offer modern push notifications for contact changes. Nylas polls Google contact changes about every 5 minutes, then emits contact webhooks.

**Load profile pictures on demand.** Contact lists can include many photos. Render names and email addresses first, then fetch profile images only when the row is visible.

## What's next

- [How to list Google contacts](/docs/cookbook/contacts/list-contacts-google/) for Google-specific scopes, sources, and quotas
- [Build recipient autocomplete](/docs/cookbook/contacts/recipient-autocomplete/) for a production To-field picker
- [Search contacts](/docs/cookbook/contacts/search-contacts/) for exact filters and local name search
- [Contacts API reference](/docs/reference/api/contacts/) for every parameter and response field