Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Release Notes for Element API

## Unreleased

### Changed
- Element API now requires Craft 6 and PHP 8.5, and no longer uses the Yii 2 adapter.
- The plugin was ported to Craft 6’s Laravel-based architecture, and its classes moved to the `CraftCms\ElementApi` namespace. See the [upgrade guide](UPGRADE.md) for the full list.
- The config file moved from `config/element-api.php` to `config/craft/element-api.php`.
- Endpoint URL patterns now use Laravel route syntax (`news/{entryId}.json`) instead of Yii URL rules (`news/<entryId:\d+>.json`).
- Endpoint functions can set response headers with `ResponseHeaders::add()`, and can type-hint services and `Illuminate\Http\Request`.
- Responses no longer set cookies.

### Removed
- Removed the `beforeSendData` event and `craft\elementapi\DataEvent`. Use the new `CraftCms\ElementApi\Events\SendingData` Laravel event instead.
- Removed the `generateTransformsBeforePageLoad` override.

## 4.3.0 - 2026-08-05

- Updated Fractal to 0.21. ([#202](https://github.com/craftcms/element-api/issues/202))
Expand Down
120 changes: 83 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ It’s powered by Phil Sturgeon’s excellent [Fractal](http://fractal.thephplea

## Requirements

This plugin requires Craft CMS 4.3.0+ or 5.0.0+.
This plugin requires Craft CMS 6.0.0-alpha.1 or later, and PHP 8.5 or later. It doesn’t need the Yii 2 adapter.

Using Craft 4 or 5? Stay on the 4.x line of this plugin. Upgrading a Craft 5 project that already uses Element API? See the [upgrade guide](UPGRADE.md).

## Installation

Expand All @@ -28,20 +30,20 @@ cd /path/to/my-project.test
composer require craftcms/element-api

# tell Craft to install the plugin
./craft plugin/install element-api
php artisan craft:plugin:install element-api
```

## Setup

To define your API endpoints, create a new `element-api.php` file within your `config/` folder. This file should return an array with an `endpoints` key, which defines your site’s API endpoints.
To define your API endpoints, create a new `element-api.php` file within your `config/craft/` folder. This file should return an array with an `endpoints` key, which defines your site’s API endpoints.

Within the `endpoints` array, keys are URL patterns, and values are functions that define the endpoint configurations.
Within the `endpoints` array, keys are URL patterns ([Laravel route syntax](https://laravel.com/docs/routing#route-parameters), such as `news/{entryId}.json`), and values are functions that define the endpoint configurations.

```php
<?php

use craft\elements\Entry;
use craft\helpers\UrlHelper;
use CraftCms\Cms\Entry\Elements\Entry;
use CraftCms\Cms\Support\Url;

return [
'endpoints' => [
Expand All @@ -54,13 +56,13 @@ return [
'id' => $entry->id,
'title' => $entry->title,
'url' => $entry->url,
'jsonUrl' => UrlHelper::url("news/$entry->id.json"),
'jsonUrl' => Url::url("news/$entry->id.json"),
'summary' => $entry->summary,
];
},
];
},
'news/<entryId:\d+>.json' => function($entryId) {
'news/{entryId}.json' => function(int $entryId) {
return [
'elementType' => Entry::class,
'criteria' => ['id' => $entryId],
Expand All @@ -86,22 +88,22 @@ Endpoint configuration arrays can contain the following settings:

#### `class`

The class name of the Fractal resource that should be used to serve the request. If this isn’t set, it will default to `craft\elementapi\resources\ElementResource`. (All of the following configuration settings are specific to that default class.)
The class name of the Fractal resource that should be used to serve the request. If this isn’t set, it will default to `CraftCms\ElementApi\Resources\ElementResource`. (All of the following configuration settings are specific to that default class.)

#### `elementType` _(Required)_

The class name of the element type that the API should be associated with. Craft’s built-in element type classes are:
The class name of the element type that the API should be associated with. Craft’s built-in element type classes include:

- `CraftCms\Cms\Asset\Elements\Asset`
- `CraftCms\Cms\Entry\Elements\Entry`
- `CraftCms\Cms\User\Elements\User`
- `CraftCms\Cms\Address\Elements\Address`

- `craft\elements\Asset`
- `craft\elements\Category`
- `craft\elements\Entry`
- `craft\elements\GlobalSet`
- `craft\elements\MatrixBlock`
- `craft\elements\Tag`
- `craft\elements\User`
> **Note**
> As of Craft 6.0.0-alpha.20, categories, tags and global sets only exist as legacy classes in the Yii 2 adapter (`craft\elements\Category`, `craft\elements\Tag`, `craft\elements\GlobalSet`), so they can’t be queried without it yet.

```php
'elementType' => craft\elements\Entry::class,
'elementType' => CraftCms\Cms\Entry\Elements\Entry::class,
````

#### `criteria`
Expand Down Expand Up @@ -135,7 +137,7 @@ The [transformer](http://fractal.thephpleague.com/transformers/) that should be

```php
// Can be set to a function
'transformer' => function(craft\elements\Entry $entry) {
'transformer' => function(CraftCms\Cms\Entry\Elements\Entry $entry) {
return [
'title' => $entry->title,
'id' => $entry->id,
Expand All @@ -155,7 +157,7 @@ Your custom transformer class would look something like this:
```php
<?php

use craft\elements\Entry;
use CraftCms\Cms\Entry\Elements\Entry;
use League\Fractal\TransformerAbstract;

class MyTransformerClassName extends TransformerAbstract
Expand Down Expand Up @@ -201,8 +203,6 @@ The query string param name that should be used to identify which page is being
'pageParam' => 'pg',
```

Note that it cannot be set to `'p'` because that’s the parameter Craft uses to check the requested path.

#### `resourceKey`

The key that the elements should be nested under in the response data. By default this will be `'data'`.
Expand Down Expand Up @@ -238,7 +238,12 @@ Possible values are:
The [include names](http://fractal.thephpleague.com/transformers/#including-data) that should be included for the current request, if any.

```php
'includes' => (array)Craft::$app->request->getQueryParam('include'),
'news.json' => function(\Illuminate\Http\Request $request) {
return [
'includes' => (array) $request->query('include'),
// ...
];
},
```

Note that this setting requires a custom transformer class that’s prepped to handle includes:
Expand Down Expand Up @@ -317,7 +322,7 @@ Possible values are:
- an integer – results are cached for the given number of seconds.
- a [interval spec](http://www.php.net/manual/en/dateinterval.construct.php) string – results are cached for the duration specified.

Note that the `onBeforeSendData` event does not get triggered when the cache is warm.
Note that the [`SendingData` event](#events) does not get triggered when the cache is warm.

```php
'cache' => 'PT1M', // one minute
Expand All @@ -329,26 +334,67 @@ The key that responses should be cached with, if something custom is needed.

### Dynamic URL Patterns

URL patterns can contain dynamic subpatterns in the format of `<subpatternName:regex>`, where `subpatternName` is the name of the subpattern, and `regex` is a valid regular expression. For example, the URL pattern “`news/<entryId:\d+>.json`” will match URLs like `news/100.json`. You can also use the tokens `{handle}` and `{slug}` within your regular expression, which will be replaced with the appropriate regex patterns for matching handles and element slugs.
URL patterns use [Laravel route syntax](https://laravel.com/docs/routing#route-parameters). Wrap a parameter in curly braces to make it dynamic: the pattern “`news/{entryId}.json`” will match URLs like `news/100.json`.

Any parameters in the URL pattern are passed to the endpoint config function by name. For example, if a URL pattern contains `{entryId}`, then you can add an `$entryId` argument to your endpoint config function, and whatever matches the URL will be passed to `$entryId`. The function can also type-hint services and the request, such as `Illuminate\Http\Request $request`.

Any subpattern matches in the URL pattern will be mapped to the endpoint config function’s arguments. For example, if a URL pattern contains an `entryId` subpattern, then you can add an `$entryId` argument to your endpoint config function, and whatever matches the URL subpattern will be passed to `$entryId`.
If you type-hint a parameter as `int`, the route only matches digits for it, so `news/abc.json` is a 404.

```php
'news/<entryId:\d+>.json' => function($entryId) {
'news/{entryId}.json' => function(int $entryId) {
return [
'elementType' => craft\elements\Entry::class,
'elementType' => CraftCms\Cms\Entry\Elements\Entry::class,
'criteria' => ['id' => $entryId],
'one' => true,
];
},
```

Endpoints answer `GET`, `HEAD` and `OPTIONS` requests.

### Response Headers

To add response headers (for example CORS headers), call `ResponseHeaders::add()` from your endpoint function. Craft applies them to the response, whether it comes from the cache or not.

```php
use CraftCms\Cms\Support\Facades\ResponseHeaders;

'news.json' => function() {
ResponseHeaders::add('Access-Control-Allow-Origin', '*');

return [
// ...
];
},
```

Endpoints send `X-Robots-Tag: none` unless you set that header yourself. Responses don’t set any cookies.

### Events

Element API dispatches a `CraftCms\ElementApi\Events\SendingData` event before an endpoint’s data is serialized and sent. Its `payload` property is the Fractal `Scope` for the response.

```php
use CraftCms\ElementApi\Events\SendingData;
use Illuminate\Support\Facades\Event;

Event::listen(function(SendingData $event): void {
$scope = $event->payload;
});
```

### Caching and Deployment Notes

- Cached responses are tagged with `element-api`. Use **Utilities** → **Clear Caches** → **Element API responses** to clear them. Saving or deleting a relevant element clears them automatically.
- Your endpoint functions are closures, which Laravel can’t serialize, so don’t run `php artisan config:cache` in a project that uses them.
- If you cache routes with `php artisan route:cache`, run it again (or `route:clear`) whenever you change your endpoints.

### Setting Default Configuration Settings

You can specify default values for your endpoint configuration settings by adding a `defaults` key alongside your `endpoints` key (**not** within it).

```php
use craft\elements\Entry;
use CraftCms\Cms\Entry\Elements\Entry;

return [
'defaults' => [
Expand All @@ -370,7 +416,7 @@ return [
'criteria' => ['section' => 'news'],
]
},
'news/<entryId:\d+>.json' => function($entryId) {
'news/{entryId}.json' => function(int $entryId) {
return [
'criteria' => ['id' => $entryId],
'one' => true,
Expand All @@ -391,11 +437,11 @@ Here are a few endpoint examples, and what their response would look like.
return [
'criteria' => ['section' => 'ingredients'],
'elementsPerPage' => 10,
'transformer' => function(craft\elements\Entry $entry) {
'transformer' => function(CraftCms\Cms\Entry\Elements\Entry $entry) {
return [
'title' => $entry->title,
'url' => $entry->url,
'jsonUrl' => UrlHelper::url("ingredients/$entry->slug.json"),
'jsonUrl' => Url::url("ingredients/$entry->slug.json"),
];
},
'pretty' => true,
Expand Down Expand Up @@ -426,7 +472,7 @@ Here are a few endpoint examples, and what their response would look like.
"current_page": 1,
"total_pages": 7,
"links": {
"next": "/ingredients.json?p=2"
"next": "/ingredients.json?page=2"
}
}
}
Expand All @@ -436,14 +482,14 @@ Here are a few endpoint examples, and what their response would look like.
### Single Entry Endpoint

```php
'ingredients/<slug:{slug}>.json' => function($slug) {
'ingredients/{slug}.json' => function($slug) {
return [
'criteria' => [
'section' => 'ingredients',
'slug' => $slug
],
'one' => true,
'transformer' => function(craft\elements\Entry $entry) {
'transformer' => function(CraftCms\Cms\Entry\Elements\Entry $entry) {
// Create an array of all the photo URLs
$photos = [];
foreach ($entry->photos->all() as $photo) {
Expand Down Expand Up @@ -483,9 +529,9 @@ Note that `photos`, `body`, `summary`, and `tags` are imaginary custom fields.
'feed.json' => function() {
return [
'serializer' => 'jsonFeed',
'elementType' => craft\elements\Entry::class,
'elementType' => CraftCms\Cms\Entry\Elements\Entry::class,
'criteria' => ['section' => 'news'],
'transformer' => function(craft\elements\Entry $entry) {
'transformer' => function(CraftCms\Cms\Entry\Elements\Entry $entry) {
$image = $entry->photos->one();

return [
Expand Down
Loading
Loading