Skip to content

mediaEmbed.previewsInData ignored server-side when Config Options come from a JSON file #636

Open

Description

@antcooper

Description

Hey folks 👋 We hit this on a site that moved from Redactor to CKEditor, and it took a while to track down, so here's what we found.

Since 5.3.0, a field's Config Options can come from a JSON file in config/ckeditor/ (Advanced → Config Options → File). That works great in the editor itself. But _normalizeFigures() reads mediaEmbed.previewsInData from $this->options, and that only gets filled from the file inside configJs():

private function configJs(): string
{
    if (isset($this->jsFile) && strtolower(pathinfo($this->jsFile, PATHINFO_EXTENSION)) === 'json') {
        $this->options = self::jsonFileContents($this->jsFile);
    }
    // ...

configJs() only runs when the field's input is rendered. So when an element is saved without the editor (craft resave/*, resave queue jobs, revision reverts, imports, programmatic saves), previewsInData comes back as false. Redactor-style <figure><iframe> then gets rewritten into a bare <oembed>, even though the JSON file sets previewsInData: true.

Why it matters

In #605, the advice for Redactor-era videos was to set mediaEmbed.previewsInData: true, because Parse Embeds can't handle the youtube.com/embed/<id> URLs that Redactor stored. That's what we did. Then the 5.3.0 migration moved our config into config/ckeditor/Standard.json for us, and the setting stopped applying to anything saved outside the editor.

The result is videos that look fine in the CP but render as an empty <oembed> on the front end. Restoring an old revision brings it back too, because the revision still has the Redactor markup and gets re-normalised on save. On our (statically built) site it went unnoticed for months.

Steps to reproduce

  1. Create config/ckeditor/Test.json:
    { "mediaEmbed": { "previewsInData": true } }
  2. Create a CKEditor field with Media Embed in the toolbar. Under Advanced → Config Options, choose File → Test.json. Add the field to an entry type.
  3. Set Redactor-style markup on an entry without the editor:
    php craft resave/entries --element-id=<id> --set=<fieldHandle> --to='=<figure><iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"></iframe></figure>'
  4. Check the stored value.

Actual:

<figure class="media"><oembed url="https://www.youtube.com/embed/dQw4w9WgXcQ"></oembed></figure>

Expected:

<figure class="media"><div data-oembed-url="https://www.youtube.com/embed/dQw4w9WgXcQ"><iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ"></iframe></div></figure>

If you switch the field to JSON with the exact same options and run step 3 again, you get the expected output. So it's only the file-based config that's affected.

Suggested fix

Resolve the options in one place, so they're available whether or not the input has been rendered:

private function resolvedOptions(): ?array
{
    if (isset($this->jsFile) && strtolower(pathinfo($this->jsFile, PATHINFO_EXTENSION)) === 'json') {
        return self::jsonFileContents($this->jsFile);
    }

    return $this->options;
}

Then in _normalizeFigures():

$previewsInData = $this->resolvedOptions()['mediaEmbed']['previewsInData'] ?? false;

Thanks! 🙏

Additional info

  • Craft version: 5.10.14
  • PHP version: 8.4
  • Database driver & version: MySQL 8.0
  • Plugins & versions: CKEditor 5.7.0 (the code is unchanged in 5.9.0), HTML Field 3.6.0

Activity

  1. linear-code commented on Oct 12, 2026

    @linear-code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions