slab/quill · critical · Error

Cannot initialize Quill without "${scrollBlotName}" blot

Error message

Cannot initialize Quill without "${scrollBlotName}" blot

What it means

Quill builds its editor tree on top of Parchment's root "scroll" blot (blotName 'scroll', defined at packages/quill/src/blots/scroll.ts:31). The constructor queries the configured registry for Parchment.ScrollBlot.blotName and throws if the lookup returns nothing or a value without a blotName property. This means the registry in options.registry does not contain Quill's core scroll blot, so the editor cannot be rooted.

Source

Thrown at packages/quill/src/core/quill.ts:218

    this.container = this.options.container;
    if (this.container == null) {
      debug.error('Invalid Quill container', container);
      return;
    }
    if (this.options.debug) {
      Quill.debug(this.options.debug);
    }
    const html = this.container.innerHTML.trim();
    this.container.classList.add('ql-container');
    this.container.innerHTML = '';
    instances.set(this.container, this);
    this.root = this.addContainer('ql-editor');
    this.root.classList.add('ql-blank');
    this.emitter = new Emitter();
    const scrollBlotName = Parchment.ScrollBlot.blotName;
    const ScrollBlot = this.options.registry.query(scrollBlotName);
    if (!ScrollBlot || !('blotName' in ScrollBlot)) {
      throw new Error(
        `Cannot initialize Quill without "${scrollBlotName}" blot`,
      );
    }
    this.scroll = new ScrollBlot(this.options.registry, this.root, {
      emitter: this.emitter,
    }) as Scroll;
    this.editor = new Editor(this.scroll);
    this.selection = new Selection(this.scroll, this.emitter);
    this.composition = new Composition(this.scroll, this.emitter);
    this.theme = new this.options.theme(this, this.options); // eslint-disable-line new-cap
    this.keyboard = this.theme.addModule('keyboard');
    this.clipboard = this.theme.addModule('clipboard');
    this.history = this.theme.addModule('history');
    this.uploader = this.theme.addModule('uploader');
    this.theme.addModule('input');
    this.theme.addModule('uiNode');
    this.theme.init();
    this.emitter.on(Emitter.events.EDITOR_CHANGE, (type) => {

View on GitHub (pinned to 539cbffd0a)

Solutions

  1. Drop the custom registry and use the formats option instead: createRegistryWithFormats (packages/quill/src/core/utils/createRegistryWithFormats.ts:4) always seeds CORE_FORMATS ['block','break','cursor','inline','scroll','text'], so scroll is guaranteed present.
  2. If you must keep a custom registry, register the scroll blot on it: const Scroll = Quill.import('blots/scroll'); myRegistry.register(Scroll); before passing it as options.registry.
  3. Ensure the full Quill build side effects ran (import Quill from 'quill' rather than cherry-picking submodules) so parchment's global registry is populated.
  4. In SSR, guard construction behind a typeof window check and only instantiate Quill in the browser where registration side effects have executed.

Example fix

// before
const registry = new Registry();
const quill = new Quill(el, { registry }); // throws: scroll blot missing

// after - use formats so core blots are auto-included
const quill = new Quill(el, { formats: ['bold', 'italic', 'list'] });

// or, register scroll on a custom registry
import { Quill } from 'quill';
import { Registry } from 'parchment';
const registry = new Registry();
registry.register(Quill.import('blots/scroll'));
const quill = new Quill(el, { registry });
Defensive patterns

Strategy: validation

Validate before calling

// Run before constructing Quill with a custom registry
import { Quill } from 'quill';

function assertScrollBlotPresent(registry) {
  const scrollBlotName = registry.constructor.name && 'scroll'; // 'scroll' per packages/quill/src/blots/scroll.ts:32
  const ScrollBlot = registry.query(scrollBlotName);
  if (!ScrollBlot || !(typeof ScrollBlot === 'function' && 'blotName' in ScrollBlot)) {
    throw new Error(
      `options.registry is missing the "${scrollBlotName}" blot; use the formats option or register Quill.import('blots/scroll').`,
    );
  }
}

// usage
const registry = options.registry ?? Quill.import('core').globalRegistry;
assertScrollBlotPresent(registry);
const quill = new Quill(el, { ...options, registry });

Type guard

// Narrows a registry to one known to contain the scroll blot
function hasScrollBlot(registry) {
  const blot = registry.query('scroll');
  return blot != null && typeof blot === 'function' && 'blotName' in blot;
}

// usage
if (hasScrollBlot(myRegistry)) {
  const quill = new Quill(el, { registry: myRegistry });
}

Try / catch

try {
  const quill = new Quill(el, options);
} catch (err) {
  if (String(err?.message).includes('Cannot initialize Quill without')) {
    // registry is missing core blots - fall back to default registry / formats option
    console.error('Quill init failed: custom registry lacks the scroll blot.', err);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a hand-built options.registry (new Registry()) that was never seeded with the scroll blot; running in an SSR/isomorphic context where the parchment global registry side-effects never executed; a custom registry whose query('scroll') was overridden/removed; tree-shaking that dropped quill's core blot registration imports.

Common situations: Switching from the formats option to a custom registry to sandbox formats but forgetting core blots; Quill v1->v2 migration where the global registry seeding moved behind an explicit import; bundler misconfiguration stripping side-effectful imports from packages/quill/src/quill.ts.

Related errors


AI-assisted analysis of slab/quill@539cbffd0a (2026-08-12). Data as JSON: /api/errors/fb3a0d98c68ad510. Report an issue: GitHub.