---
title: "Actions"
description: "Make a public method of a service an action with the PHP attribute AsAction, and the arguments of the attribute."
canonical: actions.html
---

<a id="developer-actions"></a>

<a id="actions"></a>

# Actions

To make a public method of a service an action, add the PHP attribute
`\TYPO3\CMS\Core\Attribute\AsAction`. The class must be a service with
autowiring and autoconfiguration. The default `Configuration/Services.yaml`
of an extension sets both.

When TYPO3 compiles the dependency injection container, it derives the JSON
Schema of the parameters and of the return value from the method signature.
This includes PHPDoc types, for example `list<string>` or array shapes.
TYPO3 uses this schema to validate and hydrate the arguments, and to validate
the result of the action.

> [!IMPORTANT]
> A plain `array` is a JSON object. Declare a list with
> `list<…>`, otherwise the validation of the result fails.

EXT:my\_extension/Classes/Action/PageTitleAction.php

```php
use TYPO3\CMS\Backend\Scope\ContentReadScope;
use TYPO3\CMS\Core\Action\ActionContext;
use TYPO3\CMS\Core\Action\ActionType;
use TYPO3\CMS\Core\Action\Error\NotFoundError;
use TYPO3\CMS\Core\Attribute\AsAction;

final readonly class PageTitleAction
{
    /**
     * @return list<string>
     * @throws NotFoundError if the page does not exist
     */
    #[AsAction(
        route: 'my-extension/page/{uid}/titles',
        type: ActionType::fetch,
        summary: 'Fetch the titles of a page and its translations',
        scopes: [ContentReadScope::class],
    )]
    public function titles(int $uid, ActionContext $context): array
    {
        // …
        throw new NotFoundError('Page not found', 1791975434);
    }
}
```

The endpoint of this action is `GET /typo3/api/v1/my-extension/page/42/titles`.

```diagram
flow canvas=940
  columns 1,1,1
  gap 70,24
title "The method signature is the only source of the schema"
description "When the container is compiled, the JSON Schema is derived from the method signature and its PHPDoc types. At runtime, this schema describes the action in the OpenAPI document and validates incoming arguments and the result."

column 0 "When the container is compiled"
column 2 "At runtime"

signature "Method signature"
  at 0,0
  rows 3
  body "With #[AsAction] and PHPDoc types"
schema "JSON Schema"
  at 1,0
  rows 3
  emphasis
openapi "OpenAPI document"
  at 2,0
input "Validation of arguments"
  at 2,1
output "Validation of the result"
  at 2,2

signature -> schema
schema -> openapi
schema -> input
schema -> output
```

*The documentation of an action and the validation of its calls always agree, because both use the same schema.*

The attribute accepts these arguments:

- **`route`**

  The path of the action, below `/typo3/api/` and the version. A
  placeholder, for example `{uid}`, gives its value to the parameter with the
  same name. Actions of a different type or version can use the same route.
- **`type`**

  An `\TYPO3\CMS\Core\Action\ActionType`: `fetch` (default),
  `create`, `replace`, `update`, `delete` or
  `dispatch`. It sets the HTTP method of the endpoint, see
  [Endpoints](../usage/endpoints.md#usage-endpoints).
- **`id`**

  The identifier of the action. OpenAPI uses it as `operationId`, and the
  backend route has the name `action:<id>`. Without `id`, TYPO3 derives the
  identifier from the type, the route and the version, for example
  `fetch_my_extension_page_by_uid_titles`. From version 2, a derived
  identifier gets the suffix `_v<version>`, for example `_v2`. An identifier starts with a letter and
  contains letters, digits, `_` and `-`. It has not more than 64 characters
  and must be unique. Set it to keep the identifier when the route changes,
  or if the derived identifier is too long.
- **`summary`, `description`, `tag`**

  Text that describes the action for people, in the OpenAPI document.
- **`scopes`**

  A list of scope classes. The caller must have all of them to call the
  action.
- **`version`**

  The version of the action (default `1`). It is the first part of the route
  path, for example `v1/my-extension/page/{uid}/titles`. Each action has its
  own version. An incompatible change gets a new version of the same
  `route`, and the previous version stays available. `null` gives
  the action no version prefix. Use this only for protocol endpoints.
- **`deprecated`**

  The Unix timestamp of the deprecation of the action. The HTTP response
  header `Deprecation`
  ([RFC 9745](https://datatracker.ietf.org/doc/html/rfc9745)) shows the
  date, and the OpenAPI document marks the action as deprecated.
- **`targets`**

  The channels of the action, see [Targets](targets.md#developer-targets). By default, an
  action has all targets.
- **`location`**

  For an action of type `create`: a link to the action that gets the
  created resource, see [Location of created resources](#developer-location).

A new version of an action, and the deprecation of the previous version

```php
/** @return list<string> */
#[AsAction(
    route: 'my-extension/page/{uid}/titles',
    deprecated: 1793608038,
)]
public function titles(int $uid): array
{
    // …
}

/** @return list<string> */
#[AsAction(
    route: 'my-extension/page/{uid}/titles',
    version: 2,
)]
public function titlesV2(int $uid): array
{
    // …
}
```

<a id="developer-location"></a>

<a id="location-of-created-resources"></a>

## Location of created resources

An action of type `create` can declare the action that gets the created
resource, with a `\TYPO3\CMS\Core\Action\Link`. The link refers to the
class and the method of the action. Runtime expressions, as in OpenAPI link
objects, set its parameters:

- **`$request.path.<name>`, `$request.query.<name>`, `$request.body#/<name>`**

  An argument of the creating action.
- **`$response.body#/<pointer>`**

  A value of the result of the creating action, as JSON pointer.

Such an action returns the status `201`, and the `Location` header contains the
URL of the created resource. The OpenAPI document also describes the link.

```php
use TYPO3\CMS\Core\Action\Link;

#[AsAction(
    route: 'my-extension/news/{pid}',
    type: ActionType::create,
    location: new Link([NewsActions::class, 'show'], [
        'uid' => '$response.body#/news/uid',
    ]),
)]
public function create(int $pid, string $title): array
{
    // …
    return ['news' => ['uid' => $uid]];
}
```

To find an action by its method in PHP, call
`getActionByMethod([NewsActions::class, 'show'])` on the service
`\TYPO3\CMS\Core\Action\ActionRegistry`.
