Last updated July 26, 2016. Created on September 26, 2013.
Edited by jacov, Wim Leers, mpp, kay_v. Log in to edit this page.

Follow these steps to expose resources to POST requests:

  1. Configuration
  2. Test with a POST request

Configuration

This builds upon the GET example of the previous page.

See Getting started: REST configuration & REST request fundamentals — Configuration

Sample requests below assume this configuration:

resources:
  entity:node:
    GET:
      supported_formats:
        - hal_json
      supported_auth:
        - basic_auth
        - cookie
    POST:
      supported_formats:
        - hal_json
      supported_auth:
        - basic_auth
        - cookie

Test with a POST request

In this example, we will use HAL+JSON. HAL+JSON has the concept of relations. Most of the content in Drupal has relations. Make sure the relations are properly added to the payload (they live under the _links key).

For instance if you want to POST a new comment you need a _links entry to the user and to the entity the comment is for. Best way to get this is to first GET an example and study its _links.

Never POST a UUID (or node ID/comment ID/…) as you create a new entity.

In all of the examples below, you should get a 200 response, which includes the serialized entity in the body (since Drupal 8.1.0, i.e. since #2546216: Return entity object in REST response body after successful POST — before then, you'll get a 201 response, with an empty body)

In all examples below, we POST to the URL /entity/node. Since Drupal 8.2.0, you are able to POST to /node instead, but /entity/node will continue to work until Drupal 9.

cURL (command line)

curl --include \
  --request POST \
  --user klausi:secret \
  --header 'Content-type: application/hal+json' \
  --header 'X-CSRF-Token: <obtained from http://example.com/rest/session/token>' \
  http://example.com/entity/node?_format=hal_json \
  --data-binary '{"_links":{"type":{"href":"http://example.com/rest/type/node/article"}},"title":[{"value":"Example node title"}],"type":[{"target_id":"article"}]}'

Guzzle

<?php
$serialized_entity = json_encode([
  'title' => [['value' => 'Example node title']],
  'type' => [['target_id' => 'article']],
  '_links' => ['type' => [
      'href' => 'http://example.com/rest/type/node/article'
  ]],
]);

$response = \Drupal::httpClient()
  ->post('http://example.com/entity/node?_format=hal_json', [
    'auth' => ['klausi', 'secret'],
    'body' => $serialized_entity,
    'headers' => [
      'Content-Type' => 'application/hal+json',
      'X-CSRF-Token' => <obtained from /rest/session/token>
    ],
  ]);
?>

jQuery

function getCsrfToken(callback) {
  jQuery
    .get(Drupal.url('rest/session/token'))
    .done(function (data) {
      var csrfToken = data;
      callback(csrfToken);
    });
}

function postNode(csrfToken, node) {
  jQuery.ajax({
    url: 'http://example.com/entity/node?_format=hal_json',
    method: 'POST',
    headers: {
      'Content-Type': 'application/hal+json',
      'X-CSRF-Token': csrfToken
    },
    data: JSON.stringify(node),
    success: function (node) {
      console.log(node);
    }
  });
}

var newNode = {
  _links: {
    type: {
      href: 'http://example.com/rest/type/node/article'
    }
  },
  type: {
    target_id: 'article'
  },
  title: {
    value: 'Example node title'
  }
}; 

getCsrfToken(function (csrfToken) {
  postNode(csrfToken, newNode);
});

Dev HTTP client

POST with Taxonomy Term entity reference using HAL+JSON: cURL (command line)

The following is an example of a POST request using HAL+JSON to create an article Node with a taxonomy term entity reference for a "tagging" vocabulary.

This example applies only to HAL+JSON, since the concept of _embedded is specific to HAL+JSON, it does not exist in JSON or XML.

Before you can actually POST the article node with a tag when using the HAL+JSON format, you first have to GET the tag to retrieve its UUID (because HAL+JSON requires references by UUID). If it's a new term, you must first POST it to create it. See also example 'POST term'.

curl --request POST -k -i -s --user user:password --header 'Content-type: application/hal+json' -H 'Cache-Control: no-cache' --header 'X-CSRF-Token: <obtained from http://example.com/rest/session/token>' 'http://example.com/entity/node?_format=hal_json' --data-binary '
{
  "_links": {
    "type": {
      "href": "http://example.com/rest/type/node/article"
    },
    "http://example.com/rest/relation/node/article/field_tags": {
       "href": "http://example.com/taxonomy/term/1?_format=hal_json"
    }
  },
  "type": {
      "target_id": "article"
    },
  "title": {
      "value": "My Article"
    },
  "body": {
      "value": "some body content aaa bbb ccc"
  },
    "_embedded": {
      "http://example.com/rest/relation/node/article/field_tags": [
        {
          "_links": {
            "self": {
              "href": "http://example.com/taxonomy/term/1?_format=hal_json"
            },
            "type": {
              "href": "http://example.com/rest/type/taxonomy_term/tags"
            }
          },
          "uuid": [
            {
              "value": "ff61ea71-2540-47fe-a4bb-384b12d4de47"
            }
          ],
          "lang": "en"
        }
      ]
    }
}'

POST New Taxonomy Term using HAL+JSON: cURL (command line)

Note: actual endpoint is '/entity/taxonomy_term/'

export JSON_DATA='
{
  "_links": {
    "type": {
      "href": "https://example.com/rest/type/taxonomy_term/tags"
    }
  },
  "vid": [
    {
      "target_id": "tags"
    }
  ],
  "name": [
    {
      "value": "RESTtag",
      "lang": "en"
    }
  ]
}'


curl --request POST \
  -k \
  -i \
  -s \
  --user "username:password" \
  --header 'Content-type: application/hal+json' \
  -H 'Cache-Control: no-cache' \
  --header "X-CSRF-Token: ####YourTokenHash####" \
  'https://example.com/entity/taxonomy_term/?_format=hal_json'  \
  --data-binary "$JSON_DATA"

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

bigjim’s picture

I spent a few hours on this today so thought I would share it somewhere. I will cut child page to this page but wanted to get this jotted down first.

If you are creating a custom POST method. It is not sufficient to simply have a post() method in you rest resource plugin.

So for example your annotations before your class declaration should be something like this:

/**
 * Provides a test resource.
 *
 * @RestResource(
 *   id = "api-test",
 *   label = @Translation("API - Test"),
 *   uri_paths = {
 *     "canonical" = "/api/alpha/test",
 *     "https://www.drupal.org/link-relations/create" = "/api/alpha/test/{string}"
 *   }
 * )
 */
class APITest extends ResourceBase {
 /**
   * Responds to POST requests.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function post($string) {
  }
}

Otherwise your API will be be expecting the request to use the /entity/{entity_type} endpoint, which will conflict with the endpoint provided by core.

Wim Leers’s picture

That's impossible.

From \Drupal\rest\Plugin\ResourceBase::routes():

    $create_path = isset($definition['uri_paths']['https://www.drupal.org/link-relations/create']) ? $definition['uri_paths']['https://www.drupal.org/link-relations/create'] : '/' . strtr($this->pluginId, ':', '/');

There's no mention whatsoever of /entity/{entity_type}.

ahoms’s picture

Hi,

For customer requirements, I need to set up my server to receive POST REST calls from a third party application. That received data will be then stored inside a node entity, but the json structure is already defined by this third party and there is no option to change it. I've created my own resource and I haven't found yet any way to avoid having to pass something like 'type' : {'target_id' : 'xxxx'} inside the json POST data to make it work. That's precisely my problem, because my customer refuses to include any Drupal-specific notation in the calls. I need to fit to the already defined json structure given by them.

I'm aware that the new REST service philosophy is oriented to serialization, objects... and it seems a right decision to me. But in this particular case I would need a little more flexibility, so I could implement (the rest of) my REST service within the Drupal standard.

Is there any way to solve this?

I'd really appreciate some advice. Thank you :D