lovell/sharp · error · Error

Expected a valid string to create an image with text.

Error message

Expected a valid string to create an image with text.

What it means

Thrown when inputOptions.text is provided but text.text is not a string (or text itself is not an object with a string text field). The text renderer needs the actual string content to rasterize, so missing/non-string content cannot proceed. This is the else branch after `is.object(text) && is.string(text.text)`.

Source

Thrown at lib/input.mjs:470

          }
        }
        if (is.defined(inputOptions.text.spacing)) {
          if (is.integer(inputOptions.text.spacing) && is.inRange(inputOptions.text.spacing, -1000000, 1000000)) {
            inputDescriptor.textSpacing = inputOptions.text.spacing;
          } else {
            throw is.invalidParameterError('text.spacing', 'integer between -1000000 and 1000000', inputOptions.text.spacing);
          }
        }
        if (is.defined(inputOptions.text.wrap)) {
          if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'word-char', 'none'])) {
            inputDescriptor.textWrap = inputOptions.text.wrap;
          } else {
            throw is.invalidParameterError('text.wrap', 'one of: word, char, word-char, none', inputOptions.text.wrap);
          }
        }
        delete inputDescriptor.buffer;
      } else {
        throw new Error('Expected a valid string to create an image with text.');
      }
    }
    // Join images together
    if (is.defined(inputOptions.join)) {
      if (is.defined(this.options.join)) {
        if (is.defined(inputOptions.join.animated)) {
          if (is.bool(inputOptions.join.animated)) {
            inputDescriptor.joinAnimated = inputOptions.join.animated;
          } else {
            throw is.invalidParameterError('join.animated', 'boolean', inputOptions.join.animated);
          }
        }
        if (is.defined(inputOptions.join.across)) {
          if (is.integer(inputOptions.join.across) && is.inRange(inputOptions.join.across, 1, 1000000)) {
            inputDescriptor.joinAcross = inputOptions.join.across;
          } else {
            throw is.invalidParameterError('join.across', 'integer between 1 and 100000', inputOptions.join.across);
          }

View on GitHub (pinned to 56676c6918)

Solutions

  1. Wrap the string: sharp({ text: { text: 'hello' } }).
  2. Coerce non-strings: text: { text: String(value) }.
  3. Guard that the content is a non-empty string before calling sharp.

Example fix

// before
sharp({ text: 'hello' })

// after
sharp({ text: { text: 'hello' } })
Defensive patterns

Strategy: type-guard

Validate before calling

function textCreate(content, opts = {}) {
  if (typeof content !== 'string') {
    throw new Error('text.text must be a non-empty string');
  }
  return sharp({ text: { text: content, ...opts } });
}

Type guard

function isTextSpec(text) {
  return text !== null && typeof text === 'object' && typeof text.text === 'string';
}

Prevention

When it happens

Trigger: sharp({ text: { text: 123 } }), sharp({ text: { text: null } }), sharp({ text: {} }) with no text field, or sharp({ text: 'hello' }) passing the string directly instead of wrapping it.

Common situations: Passing the string at the wrong level (sharp({ text: 'hello' }) instead of sharp({ text: { text: 'hello' } })). Forgetting to set the content when building the object programmatically. Receiving text from a non-string source (number id, object) without coercion.

Related errors


AI-assisted analysis of lovell/sharp@56676c6918 (2026-08-13). Data as JSON: /api/errors/80efa9651656c929. Report an issue: GitHub.