PHPackages                             dealnews/datocms-cma-client - 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. dealnews/datocms-cma-client

ActiveLibrary[API Development](/categories/api)

dealnews/datocms-cma-client
===========================

Unofficial DatoCMS API Client for the Content Management API

0.6.0(2w ago)02.5k↓67.1%BSD-3-ClausePHPPHP ^8.2CI passing

Since Dec 19Pushed 2w agoCompare

[ Source](https://github.com/dealnews/datocms-cma-client)[ Packagist](https://packagist.org/packages/dealnews/datocms-cma-client)[ RSS](/packages/dealnews-datocms-cma-client/feed)WikiDiscussions main Synced 3d ago

READMEChangelog (8)Dependencies (24)Versions (20)Used By (0)

datocms-cma-client
==================

[](#datocms-cma-client)

Unofficial PHP client for the [DatoCMS Content Management API](https://www.datocms.com/docs/content-management-api). Provides a structured, type-safe interface for managing records, models, uploads, and other DatoCMS resources.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Usage](#usage)
    - [Records](#records)
    - [Models (Item Types)](#models-item-types)
    - [Model Filters](#model-filters)
    - [Uploads](#uploads)
    - [Upload Collections](#upload-collections)
    - [Upload Tags](#upload-tags)
    - [Webhooks](#webhooks)
    - [Other Endpoints](#other-endpoints)
- [DataTypes](#datatypes)
- [Testing](#testing)
- [License](#license)

---

Features
--------

[](#features)

- Full coverage of the DatoCMS CMA: records, models, uploads, webhooks, environments, fields, plugins, and more
- Multiple independent client instances — each with its own API token (no global singleton)
- Sync and async upload helpers: `uploadFileAndWait()` / `uploadFile()`, `uploadFromUrlAndWait()` / `uploadFromUrl()`
- Typed DataType classes for structured field values (Color, Location, Asset, SEO, ExternalVideo, Scalar)
- Localization support on all DataType fields via `addLocale()`
- Automatic rate-limit retry on HTTP 429
- PSR-3 logger support

---

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

[](#requirements)

- PHP 8.2+
- Composer

---

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

[](#installation)

```
composer require dealnews/datocms-cma-client
```

---

Quick Start
-----------

[](#quick-start)

```
use DealNews\DatoCMS\CMA\Client;

$client = new Client('your-api-token', 'your-environment');

// List records
$records = $client->record->list();

// Upload a file and wait for processing to finish
$upload = $client->upload->uploadFileAndWait('/path/to/image.jpg');
echo $upload['data']['id'];
```

---

Configuration
-------------

[](#configuration)

### Constructor parameters

[](#constructor-parameters)

```
$client = new Client(
    apiToken: 'your-api-token',      // Required (or set DN_DATOCMS_API_TOKEN env var)
    environment: 'sandbox',          // Optional: DatoCMS environment name
    logger: $psrLogger,              // Optional: PSR-3 logger
    log_level: \Psr\Log\LogLevel::DEBUG, // Optional: default 'info'
    base_url: 'https://proxy.example.com' // Optional: custom base URL for proxies
);
```

### Environment variables

[](#environment-variables)

If constructor arguments are omitted, these environment variables are read on instantiation:

VariableDescription`DN_DATOCMS_API_TOKEN`API token for authentication`DN_DATOCMS_ENVIRONMENT`DatoCMS environment name`DN_DATOCMS_BASE_URL`Custom base URL (useful for proxies)`DN_DATOCMS_LOG_LEVEL`PSR-3 log level (default: `info`)### Multiple clients

[](#multiple-clients)

Each `Client` instance is fully independent, so you can use different tokens simultaneously:

```
$client_a = new Client('token-for-project-a');
$client_b = new Client('token-for-project-b');

$client_a->record->list(); // uses token-for-project-a
$client_b->record->list(); // uses token-for-project-b
```

---

Usage
-----

[](#usage)

### Records

[](#records)

```
// List all records
$records = $client->record->list();

// Filter records
use DealNews\DatoCMS\CMA\Parameters\Record as RecordParams;

$params = new RecordParams();
$params->filter->type = ['blog_post'];
$params->filter->ids = ['id-1', 'id-2'];
$params->order_by->addOrderByField('created_at', 'DESC');
$params->page->limit = 50;
$records = $client->record->list($params);

// Retrieve a single record
$record = $client->record->retrieve('record-id');

// Create a record
use DealNews\DatoCMS\CMA\Input\Record as RecordInput;
use DealNews\DatoCMS\CMA\DataTypes\Scalar;

$input = new RecordInput('item-type-id');
$input->attributes['title'] = Scalar::init()->set('Hello World');
$result = $client->record->create($input);

// Update a record
$input = new RecordInput();
$input->attributes['title'] = Scalar::init()->set('Updated Title');
$result = $client->record->update('record-id', $input);

// Delete a record
$client->record->delete('record-id');

// Publish / Unpublish
$client->record->publish('record-id');
$client->record->unpublish('record-id');

// Bulk operations
$client->record->publishBulk(['id-1', 'id-2']);
$client->record->unpublishBulk(['id-1', 'id-2']);
$client->record->deleteBulk(['id-1', 'id-2']);
```

#### Creating records with plain arrays

[](#creating-records-with-plain-arrays)

If you don't need DataType validation, you can pass a plain array:

```
$result = $client->record->create([
    'type' => 'item',
    'attributes' => [
        'title' => 'Hello World',
        'body'  => 'Content here',
    ],
    'relationships' => [
        'item_type' => ['data' => ['type' => 'item_type', 'id' => 'item-type-id']],
    ],
]);
```

---

### Models (Item Types)

[](#models-item-types)

```
// List all models
$models = $client->model->list();

// Retrieve a model
$model = $client->model->retrieve('model-id');

// Create a model
use DealNews\DatoCMS\CMA\Input\Model as ModelInput;

$input = new ModelInput();
$input->attributes['name']               = 'Blog Post';
$input->attributes['api_key']            = 'blog_post';
$input->attributes['singleton']          = false;
$input->attributes['sortable']           = true;
$input->attributes['draft_mode_active']  = true;
$result = $client->model->create($input);

// Update a model
$input = new ModelInput();
$input->attributes['name'] = 'Updated Name';
$client->model->update('model-id', $input);

// Delete / Duplicate
$client->model->delete('model-id');
$client->model->duplicate('model-id');
```

**Common model attributes:**

AttributeTypeDescription`name`stringHuman-readable name`api_key`stringMachine-friendly identifier`singleton`boolSingle-instance model`sortable`boolAllow manual sorting`modular_block`boolIs a modular block`tree`boolHierarchical records`draft_mode_active`boolEnable draft/publish workflow`all_locales_required`boolRequire all locales to be filled---

### Model Filters

[](#model-filters)

Saved searches that help editors quickly filter records within a model.

```
// List filters
$filters = $client->model_filter->list();

// Create a filter
use DealNews\DatoCMS\CMA\Input\ModelFilter;

$filter = new ModelFilter('model-id');
$filter->attributes['name'] = 'Draft posts';
$filter->attributes['filter'] = [
    'fields' => ['_status' => ['eq' => 'draft']],
];
$filter->attributes['shared'] = true;
$result = $client->model_filter->create($filter);

// Update / Delete
$client->model_filter->update('filter-id', $filter);
$client->model_filter->delete('filter-id');
```

---

### Uploads

[](#uploads)

#### Sync upload (recommended)

[](#sync-upload-recommended)

Returns the completed upload once DatoCMS finishes processing. Polls internally until done or timeout.

```
// Upload a local file (waits up to 30 seconds by default)
$upload = $client->upload->uploadFileAndWait('/path/to/image.jpg');
echo $upload['data']['id'];

// Upload with metadata and custom timeout
$upload = $client->upload->uploadFileAndWait(
    '/path/to/image.jpg',
    ['author' => 'Jane Doe', 'tags' => ['hero']],
    'collection-id',  // optional upload collection ID
    60                // timeout in seconds
);

// Upload from URL
$upload = $client->upload->uploadFromUrlAndWait('https://example.com/image.jpg');
```

#### Async upload

[](#async-upload)

Returns a job payload immediately. Poll `$client->job->retrieve($job_id)` until complete.

```
$job = $client->upload->uploadFile('/path/to/image.jpg');
$job_id = $job['data']['id'];
// poll $client->job->retrieve($job_id) ...

$job = $client->upload->uploadFromUrl('https://example.com/image.jpg', 'custom-name.jpg');
```

#### List and filter uploads

[](#list-and-filter-uploads)

```
use DealNews\DatoCMS\CMA\Parameters\Upload as UploadParams;

$params = new UploadParams();
$params->filter->type  = 'image';
$params->filter->query = 'banner';
$params->filter->tags  = ['hero'];
$params->filter->fields->addField('width', 1920, 'gte');
$params->order_by->addOrderByField('created_at', 'DESC');
$params->page->limit = 25;

$uploads = $client->upload->list($params);
```

#### Other upload operations

[](#other-upload-operations)

```
// Retrieve, update, delete
$upload = $client->upload->retrieve('upload-id');
$client->upload->update('upload-id', $input);
$client->upload->delete('upload-id');

// Bulk operations
$client->upload->deleteBulk(['upload-1', 'upload-2']);
$client->upload->updateBulk(['upload-1', 'upload-2'], ['author' => 'Updated Author']);
```

---

### Upload Collections

[](#upload-collections)

Organize uploads into folders.

```
use DealNews\DatoCMS\CMA\Input\UploadCollection;

// List / retrieve
$collections = $client->upload_collection->list();
$collection  = $client->upload_collection->retrieve('collection-id');

// Create a folder
$input = new UploadCollection();
$input->attributes['label'] = 'Product Images';
$result = $client->upload_collection->create($input);

// Create a nested folder
$sub = new UploadCollection();
$sub->attributes['label'] = 'Thumbnails';
$sub->parent_id = $result['data']['id'];
$client->upload_collection->create($sub);

// Delete
$client->upload_collection->delete('collection-id');
```

---

### Upload Tags

[](#upload-tags)

```
// List / retrieve
$tags = $client->upload_tag->list();
$tag  = $client->upload_tag->retrieve('tag-id');

// Create / delete
$tag = $client->upload_tag->create('featured');
$client->upload_tag->delete($tag['data']['id']);

// Smart tags (auto-detected, read-only)
$smart_tags = $client->upload_smart_tag->list();
```

---

### Webhooks

[](#webhooks)

```
use DealNews\DatoCMS\CMA\Input\Webhook;

// List / retrieve
$webhooks = $client->webhook->list();
$webhook  = $client->webhook->retrieve('webhook-id');

// Create
$input = new Webhook();
$input->attributes['name']       = 'My Webhook';
$input->attributes['url']        = 'https://example.com/hook';
$input->attributes['http_basic_user'] = null;
$result = $client->webhook->create($input);

// Update / delete
$client->webhook->update('webhook-id', $input);
$client->webhook->delete('webhook-id');

// Webhook call logs
$calls = $client->webhook_call->list();
$call  = $client->webhook_call->retrieve('call-id');
```

---

### Other Endpoints

[](#other-endpoints)

```
// Fields (within a model)
$fields = $client->field->list('model-id');
$client->field->retrieve('field-id');
$client->field->create('model-id', $input);
$client->field->update('field-id', $input);
$client->field->delete('field-id');

// Fieldsets
$client->fieldset->list('model-id');
$client->fieldset->create('model-id', $input);

// Environments
$client->environment->list();
$client->environment->retrieve('env-id');

// Site settings
$site = $client->site->retrieve();
$client->site->update($input);

// Jobs (poll async operation results)
$job = $client->job->retrieve('job-id');

// Maintenance mode
$client->maintenance->retrieve();
$client->maintenance->activate();
$client->maintenance->deactivate();

// Plugins
$client->plugin->list();
$client->plugin->retrieve('plugin-id');
$client->plugin->create($input);
$client->plugin->update('plugin-id', $input);
$client->plugin->delete('plugin-id');

// Record versions
$versions = $client->record_version->list('record-id');
$version  = $client->record_version->retrieve('version-id');

// Scheduled publication / unpublishing
$client->scheduled_publication->create('record-id', $input);
$client->scheduled_publication->delete('record-id');
$client->scheduled_unpublishing->create('record-id', $input);
$client->scheduled_unpublishing->delete('record-id');
```

---

DataTypes
---------

[](#datatypes)

Typed wrappers for DatoCMS field values. All DataTypes support localization via `addLocale()`.

```
use DealNews\DatoCMS\CMA\DataTypes\Scalar;
use DealNews\DatoCMS\CMA\DataTypes\Color;
use DealNews\DatoCMS\CMA\DataTypes\Location;
use DealNews\DatoCMS\CMA\DataTypes\Asset;
use DealNews\DatoCMS\CMA\DataTypes\SEO;
use DealNews\DatoCMS\CMA\DataTypes\ExternalVideo;

// Single value
$title = Scalar::init()->set('Hello World');

// Localized value
$title = Scalar::init()
    ->addLocale('en', 'Hello World')
    ->addLocale('es', 'Hola Mundo');

// Color (RGBA, 0-255 per channel)
$color = Color::init()->setColor(255, 128, 64, 255);

// Geographic coordinates
$location = Location::init()->setLocation(40.7128, -74.0060);

// File asset
$asset = Asset::init()->setAsset('upload-id', 'Image Title', 'Alt text');

// SEO fields
$seo = SEO::init()->setSEO('Page Title', 'Description', 'image-upload-id', 'summary', false);

// External video
$video = ExternalVideo::init()->setExternalVideo(
    'youtube', 'dQw4w9WgXcQ',
    'https://youtu.be/dQw4w9WgXcQ',
    1280, 720,
    'https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg',
    'Never Gonna Give You Up'
);
```

Attach DataTypes to a record input:

```
use DealNews\DatoCMS\CMA\Input\Record as RecordInput;

$record = new RecordInput('item-type-id');
$record->attributes['title']       = Scalar::init()->set('Hello');
$record->attributes['brand_color'] = Color::init()->setColor(255, 128, 64, 255);
$record->attributes['location']    = Location::init()->setLocation(40.71, -74.00);

$result = $client->record->create($record);
```

> **Note:** Structured Text fields are not yet supported by the DataTypes layer. Pass structured text as a plain PHP array matching the DatoCMS structured text format.

---

Testing
-------

[](#testing)

```
# Install dependencies
composer install

# Run all tests
composer test

# Run a single test file
composer test -- tests/API/RecordTest.php

# Run lint check
composer lint

# Fix code style
composer fix
```

---

License
-------

[](#license)

BSD-3-Clause. See [LICENSE](LICENSE) for details.

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance97

Actively maintained with recent releases

Popularity21

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 84.6% 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 ~21 days

Recently: every ~31 days

Total

8

Last Release

17d ago

PHP version history (2 changes)0.1.0PHP ^8.4

0.2.0PHP ^8.2

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/49531?v=4)[Brian Moon](/maintainers/brianlmoon)[@brianlmoon](https://github.com/brianlmoon)

![](https://www.gravatar.com/avatar/dbf067b8b1b679cc96fb0e13483a6be5b90cb35dfbb85d471cee151b9fa87417?d=identicon)[dealnews](/maintainers/dealnews)

![](https://www.gravatar.com/avatar/243758ed7035943299b60b3927056b7bdefb979d7161183261ddc2ac82a53840?d=identicon)[jkearle](/maintainers/jkearle)

---

Top Contributors

[![jearle-dealnews](https://avatars.githubusercontent.com/u/8784094?v=4)](https://github.com/jearle-dealnews "jearle-dealnews (126 commits)")[![brianlmoon](https://avatars.githubusercontent.com/u/49531?v=4)](https://github.com/brianlmoon "brianlmoon (17 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (6 commits)")

###  Code Quality

TestsPHPUnit

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/dealnews-datocms-cma-client/health.svg)

```
[![Health](https://phpackages.com/badges/dealnews-datocms-cma-client/health.svg)](https://phpackages.com/packages/dealnews-datocms-cma-client)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k5.9M738](/packages/sylius-sylius)[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[avalara/avataxclient

Client library for Avalara's AvaTax suite of business tax calculation and processing services. Uses the REST v2 API.

528.5M7](/packages/avalara-avataxclient)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

21866.0M1.7k](/packages/drupal-core)[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3741.3M46](/packages/tencentcloud-tencentcloud-sdk-php)[keboola/storage-api-client

Keboola Storage API PHP Client

10405.9k40](/packages/keboola-storage-api-client)

PHPackages © 2026

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