PHPackages                             tristanjahier/zoho-crm-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. tristanjahier/zoho-crm-php

Abandoned → [tristanjahier/zoho-crm](/?search=tristanjahier%2Fzoho-crm)Library[API Development](/categories/api)

tristanjahier/zoho-crm-php
==========================

A PHP client for the API of Zoho CRM.

0.5.0(2y ago)24102.6k↓67.1%14[1 issues](https://github.com/tristanjahier/zoho-crm-php/issues)MITPHPPHP ^7.3|^8.0

Since May 16Pushed 9mo ago2 watchersCompare

[ Source](https://github.com/tristanjahier/zoho-crm-php)[ Packagist](https://packagist.org/packages/tristanjahier/zoho-crm-php)[ Docs](https://github.com/tristanjahier/zoho-crm-php)[ RSS](/packages/tristanjahier-zoho-crm-php/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (5)Dependencies (4)Versions (7)Used By (0)

Zoho CRM API client (PHP)
=========================

[](#zoho-crm-api-client-php)

This is an API client library for Zoho CRM, written in PHP.

It aims to cover the whole API (every module and method), while providing a great abstraction and very easy-to-use methods.

Requirements
------------

[](#requirements)

- PHP : `8.0+`
- Implementations of [PSR-7 (HTTP messages)](https://www.php-fig.org/psr/psr-7/), [PSR-17 (HTTP factories)](https://www.php-fig.org/psr/psr-17/) and [PSR-18 (HTTP client)](https://www.php-fig.org/psr/psr-18/)

Installation
------------

[](#installation)

The recommended way to install this package is through [Composer](https://getcomposer.org).

Edit your `composer.json` file:

```
"require": {
    "tristanjahier/zoho-crm": "^0.5"
}
```

or simply run this command:

```
composer require tristanjahier/zoho-crm

```

If you do not know what "PSR-7, PSR-17 and PSR-18 implementations" are, basically it means that you need a standard-compliant HTTP client library installed. If you do not want to bother picking one, we recommend you to install the popular [Guzzle](https://docs.guzzlephp.org/en/stable/):

```
composer require guzzlehttp/guzzle

```

Getting started
---------------

[](#getting-started)

### TL;DR - A quick example

[](#tldr---a-quick-example)

Here are just a few examples of what is possible to do with this library:

```
// Create an API client
$client = new Zoho\Crm\V2\Client(
    new Zoho\Crm\V2\AccessTokenBroker('MY_API_CLIENT_ID', 'MY_API_CLIENT_SECRET', 'MY_API_REFRESH_TOKEN')
);

// Create a request and execute it
$response = $client->newRawRequest('Calls')->param('page', 2)->execute();

// Retrieve all deals modified for the last time after April 1st, 2019
$deals = $client->records->deals->all()->modifiedAfter('2019-04-01')->get();

// Retrieve records by ID
$myLead = $client->records->leads->find('1212717324723478324');
$myProduct = $client->records->products->find('8734873457834574028');

// Create a new contact
$result = $client->records->contacts->insert([
    'First_Name' => 'Jean',
    'Last_Name' => 'Dupont',
    'Email' => 'jacques@dupont.fr'
]);

$contactId = $result['details']['id'];

// Update the name of the contact
$client->records->contacts->update($contactId, ['First_Name' => 'Jacques']);

// Delete this contact
$client->records->contacts->delete($contactId);
```

### The basics

[](#the-basics)

The main component of this library is the `Client` class. This is the **starting point** for each API request.

To create a client object you need an "access token broker" first. It is an object which sole purpose is to provide your client with fresh API access tokens. It MUST implement `Zoho\Crm\Contracts\AccessTokenBrokerInterface`.

`Zoho\Crm\V2\AccessTokenBroker` is the default implementation and it should suit most use cases. To create an instance you need to provide the credentials of your registered API client. In that order, the client ID, the client secret, and the refresh token:

```
$tokenBroker = new Zoho\Crm\V2\AccessTokenBroker('MY_API_CLIENT_ID', 'MY_API_CLIENT_SECRET', 'MY_API_REFRESH_TOKEN');
```

Then you can create your client using that token broker:

```
$client = new Zoho\Crm\V2\Client($tokenBroker);
```

It is sufficient to start making requests to the API of Zoho CRM. However, in this configuration, the API access token (that is used to authenticate requests) will only live as long as the `$client` instance. It means that as soon as your client is garbage-collected or your PHP script stops executing, you will lose your access token, even though it was probably still valid for many minutes!

To prevent wasting fresh access tokens, **it is strongly recommended to use an "access token store" to enable persistency** across multiple PHP lifecycles:

1. A token store is an object solely meant to handle the storage of the access token used by the client.
2. It MUST implement `Zoho\Crm\Contracts\AccessTokenStoreInterface`.
3. It shall be passed as the 2nd argument of the client constructor.

This library provides a few implementations in `Zoho\Crm\AccessTokenStorage`. To quickly get started you may use the `FileStore`, which, as its name suggests, simply stores the access token in the local file system. Example:

```
$tokenStore = new Zoho\Crm\AccessTokenStorage\FileStore('dev/.token.json');
$client = new Zoho\Crm\V2\Client($tokenBroker, $tokenStore);
```

Finally, you need to make sure that your client has a valid access token (not expired):

```
if (! $client->accessTokenIsValid()) {
    $client->refreshAccessToken();
}
```

You are now ready to make API requests! One possibility is to follow the HTTP specifications of the API and manually craft any request you want using "raw requests":

```
// Retrieve the second page of records from the Contacts module, modified after April 1st, 2019:
$request = $client->newRawRequest()
    ->setHttpMethod('GET')
    ->setUrl('Contacts')
    ->setHeader('If-Modified-Since', '2019-04-01')
    ->setUrlParameter('page', 2);

// Retrieve a Deals record whose ID is 9032776450912388478:
$request = $client->newRawRequest('Deals/9032776450912388478');
```

Creating a request object does not make any HTTP request to the API, you need to execute it:

```
$response = $request->execute();
```

If the request is successful, it returns a `Response` instance. The API response is parsed and cleaned up for you, you simply have to use `getContent()` to get your data:

```
$data = $response->getContent();
```

All summarized:

```
$response = $client->newRawRequest()
    ->setHttpMethod('GET')
    ->setUrl('Contacts')
    ->setHeader('If-Modified-Since', '2019-04-01')
    ->setUrlParameter('page', 2)
    ->execute();

$records = $response->getContent();
```

If you do not want to bother with the formal `Response` object, you can call the `get()` method on any request. It will execute the request and return its response content:

```
$data = $request->get();
// is strictly equivalent to:
$data = $request->execute()->getContent();
```

*But... that's still a bit verbose, isn't it?* Yes. This is just the most basic way to make an API request. Read the next sections to learn how to make better use of the library.

### The client sub-APIs

[](#the-client-sub-apis)

The API support is divided into "sub-APIs", which are helpers that regroup multiple related features of the API. They are attached to the client and you can access them as public properties (e.g.: `$client->theSubApi`). The currently available sub-APIs are:

Client accessorClass`records``Zoho\Crm\V2\Records\SubApi``users``Zoho\Crm\V2\Users\SubApi`**The purpose of a sub-API is to provide to the developer a fluent, eloquent and concise interface to manipulate one or multiple related aspects of the API.**

For example, let's consider this request from a previous example, to retrieve the second page of records from the Contacts module that were modified after April 1st, 2019:

```
$records = $client->newRawRequest()
    ->setHttpMethod('GET')
    ->setUrl('Contacts')
    ->setHeader('If-Modified-Since', '2019-04-01')
    ->setUrlParameter('page', 2)
    ->get();
```

Using the Records sub-API, it can be rewritten like so:

```
$records = $client->records->contacts->all()->modifiedAfter('2019-04-01')->page(2)->get();
```

Creating a new contact is very straightforward:

```
$result = $client->records->contacts->insert([
    'First_Name' => 'Jean',
    'Last_Name' => 'Dupont',
    'Email' => 'jean.dupont@exemple.fr'
]);
```

Retrieving all users from your Zoho CRM organization is as simple as:

```
$users = $client->users->all()->get();
```

These are just a couple of examples. Sub-APIs bring many more features. Look at the dedicated documentation and explore the code to find out.

### Request pagination

[](#request-pagination)

When requesting records from Zoho, you will get a maximum of 200 records per response. Thus, if you want to get more than 200 records, you need to make multiple requests. This is done with the "page" URL parameter. Iterating on this parameter is called **pagination**.

In this library, pagination is made simple thanks to a request method called `autoPaginated()`. All you have to do is to call this method on a compatible request object (implementing `Zoho\Crm\Contracts\PaginatedRequestInterface`) and the library will fetch every page of records until there is no more data (or before if you set a limit). Example:

```
$client->records->contacts->newListRequest()->autoPaginated()->get();
```

Note

The request objects returned by the `all()` methods of Records and Users sub-APIs have auto-pagination already enabled.

By default, request pagination is synchronous. It simply means that every new page is only fetched once the previous one has been executed and returned a response. **This library also supports asynchronous request execution, and it usually makes pagination faster.** Once again, this is really simple to use. All you have to do is to call the `concurrency()` method on the request:

```
$client->records->calls->newListRequest()->autoPaginated()->concurrency(5)->get();
// or
$client->records->calls->all()->concurrency(5)->get();
```

This method takes a single argument: a positive non-zero integer (&gt; 0). It is the number of concurrent API requests. If you pass `1`, pagination will be synchronous. You can also pass `null` to disable asynchronous pagination.

Asynchronous pagination can speed up your paginated requests a lot, depending on the concurrency setting. If you need to retrieve thousands of records, it will save you a lot of execution time.

Warning

With X concurrent requests, you can waste up to X-1 API requests. Use it wisely.

### Response types

[](#response-types)

The type of a response content depends on the sub-API method you use. It can be a scalar like a string, an array, a boolean or null. But in most cases, you will get either an entity or a collection of entities.

Entities are objects containing a set of coherent data. For example, a Zoho record (contact, call, lead etc.) is an entity.

When the response contains (or should contain) multiple entities, you get an *entity collection*.

```
// Returns a single entity of type Zoho\Crm\V2\Records\Record:
$client->records->calls->find('');

// Returns a collection of entities (Zoho\Crm\V2\Users\User):
$client->users->all()->get();
```

#### Entities

[](#entities)

An entity is an instance of `Zoho\Crm\Entities\Entity` (or any subclass).

It encapsulates the attributes of common API objects like records or users for example.

It provides a few useful methods:

- `has($attribute)`: check if an attribute is defined
- `get($attribute)`: get the value of an attribute
- `set($attribute, $value)`: set the value of an attribute
- `getId()`: get the entity ID
- `toArray()`: get the raw attributes array

It implements magic methods `__get()` and `__set()` which lets you manipulate its attributes like public properties:

```
$id = $contact->id;
$familyName = $contact->Last_Name;
$contact->Phone = '+1234567890';
```

#### Entity collections

[](#entity-collections)

An entity collection is an instance of `Zoho\Crm\Entities\Collection`.

A collection is an array wrapper which provide a fluent interface to manipulate its items. In the case of an entity collection, these items are entities.

It provides a bunch of useful methods. To name a few:

- `has($key)`: determine if an item exists at a given index
- `get($key, $default = null)`: get the item at a given index
- `count()`: get the number of items in the collection
- `isEmpty()`: determine if the collection is empty
- `first(callable $callback = null, $default = null)`: get the first item in the collection
- `firstWhere($key, $operator, $value = null)`: get the first item matching the given (key, \[operator,\] value) tuple
- `last(callable $callback = null, $default = null)`: get the last item in the collection
- `lastWhere($key, $operator, $value = null)`: get the last item matching the given (key, \[operator,\] value) tuple
- `map(callable $callback)`: apply a callback over each item and return a new collection with the results
- `sum($property = null)`: compute the sum of the items
- `filter(callable $callback = null)`: filter the collection items with a callback
- `where($key, $operator, $value = null)`: filter items based on a comparison tuple: (key, \[operator,\] value)
- `pluck($value, $key = null)`: get the values of a given item property by key

Look at the code of `Zoho\Crm\Support\Collection` for more details.

It implements `ArrayAccess` and `IteratorAggregate` which lets you manipulate it like an array:

```
// If $records is an instance of Zoho\Crm\Entities\Collection...

// You can access items with square brackets:
$aRecord = $records[2];
$records[] = new Zoho\Crm\V2\Records\Record(['Phone' => '+1234567890']);

// And you can loop through it:
foreach ($records as $record) {
    ...
}
```

Sub-APIs reference
------------------

[](#sub-apis-reference)

### Records

[](#records)

The Records sub-API provides a single method, `module()`, used to create an instance of `Zoho\Crm\V2\Records\ModuleHelper`, which in turn provides a variety of features related to records.

```
$client->records->module('Contacts');
$client->records->module('Calls');
$client->records->module('Deals');
$client->records->module('My_Custom_Module');
```

It also implements the magic method `__get()`, so that you can get a module helper using the module name in camel case as a public property:

```
$client->records->contacts;
$client->records->calls;
$client->records->deals;
$client->records->priceBooks;
```

The rest of this section details the methods available on the module helper.

#### `all()`

[](#all)

Instance of `Zoho\Crm\V2\Records\ListRequest` with auto-pagination enabled.

```
$client->records->deals->all();
```

#### `deleted()`

[](#deleted)

Instance of `Zoho\Crm\V2\Records\ListDeletedRequest` with auto-pagination enabled.

```
$client->records->deals->deleted();
```

#### `search(string $criteria)`

[](#searchstring-criteria)

Instance of `Zoho\Crm\V2\Records\SearchRequest` with auto-pagination enabled.

```
$client->records->deals->search('');
```

#### `searchBy(string $field, string $value)`

[](#searchbystring-field-string-value)

Instance of `Zoho\Crm\V2\Records\SearchRequest` with auto-pagination enabled.

```
$client->records->deals->searchBy('Field', 'value');
// is shorthand for:
$client->records->deals->search('(Field:equals:value>)');
```

#### `relationsOf(string $recordId, string $relatedModule)`

[](#relationsofstring-recordid-string-relatedmodule)

List the records from another module related to a given record. Instance of `Zoho\Crm\V2\Records\ListRelatedRequest` with auto-pagination enabled.

```
$client->records->deals->relationsOf('', 'Contacts');
```

#### `relatedTo(string $relatedModule, string $recordId)`

[](#relatedtostring-relatedmodule-string-recordid)

List the records related to a given record from another module. Inverse of `relationsOf()`. Instance of `Zoho\Crm\V2\Records\ListRelatedRequest` with auto-pagination enabled.

```
$client->records->deals->relatedTo('Contacts', '');
```

#### `find(string $id)`

[](#findstring-id)

Retrieve a record by its ID.

```
$record = $client->records->calls->find('Record ID');
```

Returns an instance of `Zoho\Crm\V2\Records\Record`, or `null` if not found.

#### `insert($record, array $triggers = null)`

[](#insertrecord-array-triggers--null)

Insert a new record.

Accepts a `Record` instance, or an array of attributes.

```
$client->records->calls->insert([
    'Field_1' => 'Value 1',
    'Field_2' => 'Value 2',
    ...
]);
```

Returns an array containing information about the result of the operation.

#### `insertMany($records, array $triggers = null)`

[](#insertmanyrecords-array-triggers--null)

Insert multiple new records at the same time.

```
$records = [
    [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ], [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ],
    ...
];

$client->records->calls->insertMany($records);
```

Returns an array of arrays containing information about the results of the operation.

#### `update(string $id, $data, array $triggers = null)`

[](#updatestring-id-data-array-triggers--null)

Update an existing record.

```
$client->records->calls->update('Record ID', [
    'Field_1' => 'Value 1',
    'Field_2' => 'Value 2',
    ...
]);
```

Returns an array containing information about the result of the operation.

#### `updateMany($records, array $triggers = null)`

[](#updatemanyrecords-array-triggers--null)

Update multiple existing records at the same time.

```
$records = [
    [
        'id' => 'Record 1 ID',
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ], [
        'id' => 'Record 2 ID',
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ],
    ...
];

$client->records->calls->updateMany($records);
```

Returns an array of arrays containing information about the results of the operation.

#### `upsert($record, array $duplicateCheckFields = null, array $triggers = null)`

[](#upsertrecord-array-duplicatecheckfields--null-array-triggers--null)

Upsert (update or insert) a record.

```
$client->records->calls->upsert([
    'Field_1' => 'Value 1',
    'Field_2' => 'Value 2',
    ...
], ['Field_1']);
```

Returns an array containing information about the result of the operation.

#### `upsertMany($records, array $duplicateCheckFields = null, array $triggers = null)`

[](#upsertmanyrecords-array-duplicatecheckfields--null-array-triggers--null)

Upsert (update or insert) multiple records at the same time.

```
$records = [
    [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ], [
        'Field_1' => 'Value 1',
        'Field_2' => 'Value 2',
        ...
    ],
    ...
];

$client->records->calls->upsertMany($records, ['Field_1']);
```

Returns an array of arrays containing information about the results of the operation.

#### `delete(string $id)`

[](#deletestring-id)

Delete a record.

```
$client->records->calls->delete('Record ID');
```

Returns an array containing information about the result of the operation.

#### `deleteMany(array $ids)`

[](#deletemanyarray-ids)

Delete multiple records at the same time.

```
$client->records->calls->deleteMany(['Record 1 ID', 'Record 2 ID']);
```

Returns an array of arrays containing information about the results of the operation.

### Users

[](#users)

#### `all()`

[](#all-1)

Instance of `Zoho\Crm\V2\Users\ListRequest` with auto-pagination enabled.

```
$client->users->all();
```

Advanced topics
---------------

[](#advanced-topics)

### Use a different API endpoint

[](#use-a-different-api-endpoint)

By default the endpoint is `https://www.zohoapis.com/crm/v2/`. You may want to use another one: .

For that, you can use the `setEndpoint()` method:

```
$client->setEndpoint('https://www.zohoapis.eu/crm/v2/');
```

Similarly, **if you are using the default access token broker**, you can change the authorization endpoint:

```
$client->getAccessTokenBroker()->setAuthorizationEndpoint('https://accounts.zoho.eu/oauth/v2/');
```

### Refresh the access token automatically

[](#refresh-the-access-token-automatically)

Out of the box, you need to deal with the access token validity by yourself. Meaning that you need to check on the expiry date regularly, and make a request to ask for a new token when it has expired.

The client has an option to automatically refresh its access token when needed. You simply have to set the `'access_token_auto_refresh_limit'` preference to the number of seconds of remaining validity below which it should be refreshed as soon as possible:

```
$client->preferences()->set('access_token_auto_refresh_limit', 60);
```

In the above example, the client will request a fresh access token when it needs to make a request to the API and its current access token will expire in less than a minute.

### Before and after request execution hooks

[](#before-and-after-request-execution-hooks)

If you need to, you can register a callback that will be executed **before** or **after** each request.

In both cases, the callback is a closure or any `callable` taking 2 arguments:

1. a copy of the request object ;
2. a unique ID of the execution (random 16 chars string), in case you need to match the "before" and "after" hooks.

Use the `beforeEachRequest()` method to register a callback that will be invoked just before each request is executed, *but only after a successful request validation*.

Use the `afterEachRequest()` method to register a callback that will be invoked just after each request is executed and the API has returned a response. *If an error or an exception is thrown from the HTTP request layer, the callback will not be invoked.*

Example:

```
use Zoho\Crm\Contracts\RequestInterface;

$client->beforeEachRequest(function (RequestInterface $request, string $execId) {
    // do something...
});

$client->afterEachRequest(function (RequestInterface $request, string $execId) {
    // do something...
});
```

Note

Paginated requests will not trigger these hooks directly, but their subsequent requests (per page) will. In other words, only the requests that directly lead to an API HTTP request will trigger the hooks.

You can uniquely identify callbacks at registration:

```
$client->beforeEachRequest(function () {}, id: 'logging');
$client->afterEachRequest(function () {}, id: 'logging');
```

And then you can deregister these identified callbacks so that they will never be invoked anymore:

```
$client->cancelBeforeEachRequestCallback('logging');
$client->cancelAfterEachRequestCallback('logging');
```

### Request middleware

[](#request-middleware)

If you need to, you can register custom middleware that will be applied to each request before it is converted into an HTTP request. Unlike execution hooks, middleware can modify the request object. Actually, this is exactly the point of middleware.

Use the `registerMiddleware()` method, which only takes a `callable`. So, you can pass a closure or an object implementing `Zoho\Crm\Contracts\MiddlewareInterface`.

Example:

```
use Zoho\Crm\Contracts\RequestInterface;

$client->registerMiddleware(function (RequestInterface $request) {
    $request->setUrlParameter('toto', 'tutu');
});
```

Notice that you don't need to return the request object. In fact, the return value will simply be ignored.

Note

As with execution hooks, paginated requests will not pass through the middleware directly, but their subsequent requests (per page) will.

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance39

Infrequent updates — may be unmaintained

Popularity43

Moderate usage in the ecosystem

Community12

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~484 days

Total

5

Last Release

983d ago

PHP version history (4 changes)0.1.0PHP &gt;=5.5

0.3.0PHP ^7.1

0.4.0PHP ^7.3

0.5.0PHP ^7.3|^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/6f1863c18b9fff76ead36c42f0396890437ac87b2b1ea16cf48416022ecfc5c0?d=identicon)[tristanjahier](/maintainers/tristanjahier)

---

Top Contributors

[![tristanjahier](https://avatars.githubusercontent.com/u/1117304?v=4)](https://github.com/tristanjahier "tristanjahier (533 commits)")

---

Tags

api-clientcrmphpzohozoho-apizoho-crmzoho-crm-apiapiclientlibrarycrmZoho

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/tristanjahier-zoho-crm-php/health.svg)

```
[![Health](https://phpackages.com/badges/tristanjahier-zoho-crm-php/health.svg)](https://phpackages.com/packages/tristanjahier-zoho-crm-php)
```

###  Alternatives

[tristanjahier/zoho-crm

A PHP client for the API of Zoho CRM.

2414.3k](/packages/tristanjahier-zoho-crm)[gorkalaucirica/hipchat-v2-api-client

Hipchat v2 API client

80223.4k6](/packages/gorkalaucirica-hipchat-v2-api-client)[webleit/zohobooksapi

Zoho Books API v3 - PHP SDK

4881.0k1](/packages/webleit-zohobooksapi)[fabian-beiner/todoist-php-api-library

A PHP client library that provides a native interface to the official Todoist REST API.

4810.8k](/packages/fabian-beiner-todoist-php-api-library)[teknoo/sellsy-client

PHP library to connect your applications to your Sellsy account account using the Sellsy API and build your websites and your platforms on the Sellsy technology.

18195.6k](/packages/teknoo-sellsy-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
