How do I modify an article's body field before the end user sees it for one particular article?

Background: Currently the content editors are updating this data manually twice per week. We want to automatically update it every 8 hours with a cron job instead. We will get data from an external source, it comes to us in JSON format. We process the data into a render array.

When I'm ready to see some output on the page, how do I put it there?

  • 1
    I don't get it. You mean like with MYMODULE_preproccess_field__body(&$variables)? – leymannx Mar 5 '19 at 13:19
  • Though if I follow your thoughts a little bit further, you may actually be looking for some API approach to really update existing data, instead of just overriding/preprocessing the existing data, no? – leymannx Mar 5 '19 at 16:09
  • Oh. Ha! Yep. That's probably just what I was looking for. Thanks! I'm still new to Drupal. Would a custom field formatter be better? – rjacobsen0 Mar 5 '19 at 16:20
  • 1
    It really depends on what you want to achieve. Sure you could build a custom field formatter to fetch data from an external API field by field and node by node and render that according your needs. But what about caching then? Maybe have a look at hook_cron and Batch operations. With that you can build something to fetch your data and then update nodes accordingly. Node cache will automatically get rebuild on node updates. – leymannx Mar 5 '19 at 16:54
  • 1
    I ended up taking a different approach. I modified the node and saved it (this will eventually happen on cron). – rjacobsen0 Mar 5 '19 at 19:17

I ended up modifying the node when cron runs.

// 60 * 60 * 8 - measured in seconds.
define("JMT_CLASSES_EIGHT_HOURS", 28800);

 * Implements hook_cron().
function jmt_classes_cron() {
  // Check current config for time, if greater than 8hrs check for updates.
  $timeOfLastUpdate = \Drupal::configFactory()
  $currentTime = \Drupal::time()
  $intervalSinceLastUpdate = $currentTime - $timeOfLastUpdate;

  if ($intervalSinceLastUpdate >= JMT_CLASSES_EIGHT_HOURS || $timeOfLastUpdate == NULL) {
    // Check for updates.
    $raw = jmt_classes_get_classes('https://example.com/Client.WebApi/api/Route?url=query/get?url=jmt');
    if ($raw) {
      $newBodyValueHtml = jmt_classes_render_html($raw);
      $classesEdNode = Node::load(JMT_CLASSES_CLASSES_ED_NODE_ID);
      jmt_classes_update_class_node($classesEdNode, $newBodyValueHtml);

        ->set('last_run', $currentTime)

 * Update node with new html for the body field and log that it was updated.
 * @param string $node
 *   The node (output page) that should be updated with new data.
 * @param string $newBodyValueHtml
 *   The HTML to set the body to.
function jmt_classes_update_class_node($node, $newBodyValueHtml) {
  $node->body->value = $newBodyValueHtml;
  \Drupal::logger('jmt_classes')->notice("Successfully updated body field of articles/upcoming-education-classes-field-days");

 * Get raw json output for JMT classes. Log error or success.
 * @param string $uri
 *   JMT api uri: should be something like:
 *   'https://example.com/Client.WebApi/api/Route?url=query/get?url=jmt'.
 * @return string
 *   Raw json data or empty if nothing returned from request.
 * @throws \GuzzleHttp\Exception\GuzzleException
function jmt_classes_get_classes($uri) {

  try {
    $response = \Drupal::httpClient()->request('GET', $uri, ['timeout' => 120]);
  catch (Exception $e) {
    \Drupal::logger('jmt_classes')->error("Attempted to get data 
from $uri but failed. $php_errormsg");
    return '';

  $data = (string) $response->getBody();
  $data_array = json_decode($data);

  // If this isn't json data return nothing.
  if (empty($data_array)) {
    return '';
  // Otherwise, return raw json data.
  else {
    \Drupal::logger('jmt_classes')->notice("Successfully got data from $uri");
    return $data;

This has the advantage that I don't do any preprocessing. The change happens when the data changes when cron runs, not on every page load. This is important because fetching the data is SLOW!

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.