Skip to content

Every rich-text field rebuilds the workflow placeholder list: one query per attribute #2658

Open

Description

@alex-frolov

Bug report

Issue Description

Every rich-text field in the admin panel costs one query per attribute in the installation,
because <x-admin::tinymce> rebuilds the workflow placeholder list on each render.

Where the queries come from.
packages/Webkul/Automation/src/Helpers/Entity/AbstractEntity.php:63 — getAttributes()
walks every attribute of an entity type and reads $attribute->options, a lazy hasMany on
attribute_options, so one SELECT per attribute:

foreach ($this->attributeRepository->findByField('entity_type', $entityType) as $attribute) {
    ...
    $options = $attribute->options;      // one query, per attribute

Entity::getEmailTemplatePlaceholders() (Entity.php:89-101) runs that for all eight
entities in config('workflows.trigger_entities'), and the TinyMCE component calls it on the
first line of its template
(packages/Webkul/Admin/src/Resources/views/components/tinymce/index.blade.php:1):

@php($placeholders = app('\Webkul\Automation\Helpers\Entity')->getEmailTemplatePlaceholders())

so the whole walk repeats for every rich-text field rendered on the page — lead views,
person views, the mail composer, email templates, webhooks and workflows.

Measured (MySQL 8.0, queries per getEmailTemplatePlaceholders() call):

queries
stock installation 19
after adding 10 custom lead attributes 29

One extra query per attribute — and adding attributes is what the attribute system is for,
so the cost is lowest on a fresh install and grows with real use.

Preconditions

  1. Krayin master @ 2c12091 (Core::KRAYIN_VERSION 2.2.6), also present in v2.2.5.
  2. PHP 8.3.33, Laravel 12, MySQL 8.0.

Steps to reproduce

  1. In php artisan tinker:
DB::enableQueryLog();
app(\Webkul\Automation\Helpers\Entity::class)->getEmailTemplatePlaceholders();
count(DB::getQueryLog());     // 19 on a fresh install
  1. Add ten custom attributes under Settings → Attributes (any entity type, type select).
  2. Repeat step 1: the count is now 29 — one more query per attribute added.
  3. Open any admin screen with a rich-text field and read Laravel Debugbar's Queries tab:
    the same select * from attribute_options where attribute_id = ? series appears once per
    editor on the page.

Expected result

  • Building the placeholder list costs a constant, small number of queries, and does not
    repeat for each editor on a page.

Eager-loading the options in the one place that reads them is enough — measured 10 → 2 on
the leads entity type alone:

foreach ($this->attributeRepository->with('options')->findByField('entity_type', $entityType) as $attribute) {

Since the placeholder list is identical for every editor on a page, memoising
getEmailTemplatePlaceholders() per request would remove the repetition as well.

Actual result

Each rendered rich-text field issues one query per attribute across all eight trigger
entities, and the total rises by one for every attribute an admin configures.

This may be the mechanism behind #980 ("Create WorkFlow Query Way Too Much!"), which was
closed as not reproducible on an empty dataset — the cost scales with configured
attributes, and a fresh install has almost none.


How this was found

Found with query-guard — a PHPUnit extension
that traces the SQL a test run produces, fingerprints every query by call site and shape,
and flags a call site when the same-shaped query runs 3+ times with differing bound values
inside one test (batched IN (...) lookups are excluded, since those are the cure rather
than the disease).

The Pest suite could not surface this on its own, since these screens are covered by the
Playwright suite rather than by PHPUnit. So the numbers above come from driving admin
screens through Laravel's HTTP kernel — real route, middleware, controller and Blade — and
then measuring the helper directly with the number of configured attributes varied.

You do not need that tool to check any of this: the reproduction above uses nothing but
DB::enableQueryLog() in Tinker, and Laravel Debugbar's Queries tab shows the same series
in the browser.

Activity

  1. sagarkumar-webkul commented on Sep 9, 2026

    @sagarkumar-webkul
    Contributor

    @alex-frolov, thanks for raising this issue. We’re reviewing it and will provide an update shortly.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions