I've created a simple filter that allows webforms to be rendered anywhere in a node with a tag like this: [webform:machine_name]

The code to render the form is as follows:

$webform = \Drupal::entityTypeManager()->getStorage('webform')->load($machineName);
$viewBuilder = \Drupal::service('entity_type.manager')->getViewBuilder('webform');
$formBuild = $viewBuilder->view($webform);
return render($formBuild);

It works insofar as the form is displayed, client-side validation is working.

BUT none of the webform-specific JS is attached (e.g. input masks don't work). I assume this is because Drupal doesn't know it needs to attach the libraries.

How do I attach those libraries? Preferably in a method that can be invoked from the code that renders the filter, as if it's in a separate place things would start to get more complicated.


Following 4k4's feedback, here's the code that I'm now using. It works as above, without the libraries being attached:

foreach ($renderArrays as $tag => $renderArray) {
  $renderContext = new RenderContext();         
  $renderedOutput = $this->renderer->executeInRenderContext($renderContext, function () use (&$renderArray) {
    return $this->renderer->render($renderArray);
  $metaData = BubbleableMetadata::createFromRenderArray($renderArray);
  if (!$renderContext->isEmpty()) {
    $metaData = $metaData->merge($renderContext->pop());
  $result = $result->merge($metaData);
  $text = str_replace($tag, $renderedOutput, $text);

For completeness, $renderArrays contains:

['[webform:machine_name]' => [
  '#type' => 'webform',
  '#webform' => 'machine_name',

Now you return bare HTML without any attached meta data for caching, libraries and so on.

Also Webforms provides a render element, so this is all you need to build a webform:

$build = [
  '#type' => 'webform',
  '#webform' => $machineName,

The important part though is that the filter you put the token in handles the attached metadata correctly. It needs to do it similar to Entity Embed:

    $result = new FilterProcessResult($text);
    // We need to render the embedded entity:
    // - without replacing placeholders, so that the placeholders are
    //   only replaced at the last possible moment. Hence we cannot use
    //   either renderPlain() or renderRoot(), so we must use render().
    // - without bubbling beyond this filter, because filters must
    //   ensure that the bubbleable metadata for the changes they make
    //   when filtering text makes it onto the FilterProcessResult
    //   object that they return ($result). To prevent that bubbling, we
    //   must wrap the call to render() in a render context.
    $entity_output = $this->renderer->executeInRenderContext(new RenderContext(), function () use (&$build) {
      return $this->renderer->render($build);
    $result = $result->merge(BubbleableMetadata::createFromRenderArray($build));


Edit: Improved bubbling of metadata:

    $render_context = new RenderContext();
    $entity_output = $this->renderer->executeInRenderContext($render_context, function () use (&$build) {
      return $this->renderer->render($build);
    $meta_data = BubbleableMetadata::createFromRenderArray($build);
    if (!$render_context->isEmpty()) {
      $meta_data = $meta_data->merge($render_context->pop());
    $result = $result->merge($meta_data);

If you are looking for an easier solution you could for example add the webform to $variables in preprocess to render it in a node template. Then the attached libraries bubble up in the right place automatically.

| improve this answer | |
  • Thanks for the information, both parts of your response are useful. I can't go with the node template solution as it needs to be more generic and reusable just based on a tag in a field. I've rewritten the code to use the anonymous function as described above, but I can't work out how to actually replace the tag. If I use a standard str_replace, despite the extra code, the form is rendered without any Javascript. Do you have any idea? – Geat Dec 12 '18 at 18:36
  • After you've replaced the tag, do you put the resulting text in $result->setProcessedText($text) and return $result? – 4k4 Dec 12 '18 at 19:25
  • I do. I've added the code I'm using as an edit to the question. – Geat Dec 12 '18 at 19:33
  • I don't know why the code example doesn't do it, but normally you would merge the bubbled up metadata from the render context. I edit the answer. – 4k4 Dec 12 '18 at 19:59
  • Thanks for your continued help. I updated the code as suggested but it didn't change anything unfortunately. – Geat Dec 12 '18 at 20:12

Even after adding the META data bubbling, which is the correct approach for processing a filter, the libraries were still not being included.

It turns out that came down to the way I was sending the output from my custom field widget to the template.

I was originally sending this to the template:

  '#markup' => check_markup($field_content, 'full_html')

When in order to correctly run the filters and attach the libraries, I should have been sending:

  '#type' => 'processed_text',
  '#text' => $field_content,
  '#format' => 'full_html',

Funnily enough it was this post from 4k4 (who also helped me above) that pointed me in the right direction:


| improve this answer | |
  • 1
    This has still to do with the filter. Although you get away with sending the field as render array to the template and leak metadata by simply render() in the filter, this is not good practice, because the filter might be used in places where it is supposed to return the metadata in FilterProcessResult as defined in the interface. – 4k4 Dec 14 '18 at 16:42
  • Can you suggest a proper solution, keeping in mind nothing else worked? – Geat Dec 14 '18 at 17:51
  • It might be worth noting that I do still use the meta bubbling code that you provided above. – Geat Dec 14 '18 at 17:52
  • 1
    Then it's OK. I had the impression this was meant to be an answer to the question and you wanted to ignore the changes to the filter. – 4k4 Dec 14 '18 at 18:59
  • Fair comment, I should edit in just in case people find this post useful. – Geat Dec 14 '18 at 19:29

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.