PaddlePaddle/PaddleOCR · error

OCR model selection must define both detection and recogniti

Error message

OCR model selection must define both detection and recognition models.

What it means

After resolving detection and recognition model assets from base defaults, pipelineConfig, and explicit options, the pipeline verifies that every role resolved to a non-null asset. If either the detection or recognition asset is missing, OCR cannot run (both stages are mandatory), so resolveSelectedAssets throws this blanket error.

Source

Thrown at paddleocr-js/packages/core/src/pipelines/ocr/shared.ts:469

): Record<string, ModelAsset> {
  const assets = Object.fromEntries(
    OCR_MODEL_ROLES.map((role) => [
      role.assetKey,
      resolveSelectedAsset(
        role.assetKey,
        role.modelRole,
        role.selectionKey,
        baseSelection,
        configSelection,
        explicitSelection,
        configAssets,
        explicitAssets
      )
    ])
  );

  if (Object.values(assets).some((asset) => !asset)) {
    throw new Error("OCR model selection must define both detection and recognition models.");
  }

  return assets as Record<string, ModelAsset>;
}

function getExplicitModelSelection(options: Record<string, unknown>): {
  modelSelection: Record<string, string | null>;
  assets: Record<string, ModelAsset>;
} | null {
  const modelSelection: Record<string, string | null> = {};
  const assets: Record<string, ModelAsset> = {};
  let hasAnyOption = false;

  for (const role of OCR_MODEL_ROLES) {
    const modelName = readAliasedOption(options, role.nameAliases, role.nameLabel) as
      | string
      | undefined;
    const asset = readAliasedOption(options, role.assetAliases, role.assetLabel) as

View on GitHub (pinned to 2661c7c0ef)

Solutions

  1. Provide both detection and recognition models explicitly: textDetectionModelName + textRecognitionModelName (and matching dirs if self-hosted).
  2. If you nullified a selection to disable a stage, remove that — both stages are required.
  3. Simplest fix: pass just lang (or nothing) so DEFAULT_MODEL_SELECTION supplies both models.

Example fix

// before
const ocr = await PaddleOCR.create({
  textDetectionModelName: 'PP-OCRv5_mobile_det'
  // recognition model missing
});

// after
const ocr = await PaddleOCR.create({
  textDetectionModelName: 'PP-OCRv5_mobile_det',
  textRecognitionModelName: 'PP-OCRv5_mobile_rec'
});
Defensive patterns

Strategy: validation

Validate before calling

// Both roles must resolve before create()
function resolvesBothModels(o: Record<string, unknown>): boolean {
  const usesDefaults = o.textDetectionModelName === undefined && o.textRecognitionModelName === undefined;
  if (usesDefaults) return true; // lang/defaults supply both
  const det = o.textDetectionModelName ?? o.text_detection_model_name;
  const rec = o.textRecognitionModelName ?? o.text_recognition_model_name;
  return det !== undefined && rec !== undefined;
}

Type guard

type ModelPairConfig = { textDetectionModelName: string; textRecognitionModelName: string };
function isCompleteModelPair(o: Record<string, unknown>): o is ModelPairConfig {
  return typeof o.textDetectionModelName === 'string' && typeof o.textRecognitionModelName === 'string';
}

Try / catch

try {
  const ocr = await PaddleOCR.create(opts);
} catch (e) {
  if (e instanceof Error && e.message.includes('both detection and recognition')) {
    // add the missing role's model name (or drop explicit selection to use defaults)
  }
  throw e;
}

Prevention

When it happens

Trigger: An explicit selection that defines only one role, e.g. passing text_detection_model_name without any recognition model while also suppressing defaults (e.g. lang: null), or a pipelineConfig whose model selection nulls out one role.

Common situations: Trying to use a detection-only or recognition-only pipeline (not supported); setting lang: null / model selection entries to null expecting to 'skip' a stage; partial config merges that drop one role.

Related errors


AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14). Data as JSON: /api/errors/691c5d7105c3807a. Report an issue: GitHub.