facebook/flow · error · Error

sourceType option must be "script", "module", or "unambiguou

Error message

sourceType option must be "script", "module", or "unambiguous" if set

What it means

The second options check in the Flow parser: when sourceType is provided it must be 'script', 'module', or 'unambiguous' ('unambiguous' is normalized away and auto-detected from file contents). Any other string throws.

Source

Thrown at packages/flow-parser/oxidized-src/index.js:52

  // results — earlier versions wrote normalized fields back onto `opts`,
  // poisoning subsequent calls.
  const options: ParserOptions = {...DEFAULTS, ...(opts ?? {})};

  // Default to detecting whether to parse Flow syntax by the presence
  // of an @flow pragma.
  if (options.flow !== 'all' && options.flow !== 'detect') {
    throw new Error('flow option must be "all" or "detect"');
  }

  if (options.sourceType === 'unambiguous') {
    // Clear source type so that it will be detected from the contents of the file
    delete options.sourceType;
  } else if (
    options.sourceType != null &&
    options.sourceType !== 'script' &&
    options.sourceType !== 'module'
  ) {
    throw new Error(
      'sourceType option must be "script", "module", or "unambiguous" if set',
    );
  }

  if (options.enableExperimentalComponentSyntax == null) {
    options.enableExperimentalComponentSyntax = true; // Enable by default
  }

  if (options.enableExperimentalFlowMatchSyntax == null) {
    options.enableExperimentalFlowMatchSyntax = true; // Enable by default
  }

  // Record syntax: upstream defaults `enableExperimentalFlowRecordSyntax` to
  // `true`. The fork's WASM cwrap (FlowParser.js) reads `options.enableRecords`
  // — a separate, OCaml-aligned key intentionally mirroring
  // `default_parse_options`. To preserve the upstream-compatible API surface
  // on the outside while keeping the OCaml-aligned key on the WASM boundary,
  // bridge: default the upstream key, then mirror it onto the cwrap key. The

View on GitHub (pinned to d1341dac89)

Solutions

  1. Use 'module' for ESM, 'script' for CommonJS/global scripts, 'unambiguous' to auto-detect, or omit the option
  2. Build parser options explicitly instead of reusing config objects from tsconfig/Babel

Example fix

// before
parse(source, { sourceType: 'commonjs' });

// after
parse(source, { sourceType: 'script' }); // CommonJS files are scripts
Defensive patterns

Strategy: validation

Validate before calling

const SOURCE_TYPES = new Set(['script', 'module', 'unambiguous']);

function validSourceType(opts: {sourceType?: unknown}): boolean {
  return opts?.sourceType == null || SOURCE_TYPES.has(opts.sourceType as string);
}

Type guard

function isSourceType(
  v: unknown,
): v is 'script' | 'module' | 'unambiguous' | undefined {
  return v == null || v === 'script' || v === 'module' || v === 'unambiguous';
}

Try / catch

try {
  parse(source, opts);
} catch (err) {
  if (err.message.includes('sourceType option must be')) {
    const {sourceType, ...rest} = opts; // drop invalid value, use default
    parse(source, rest);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling parse(code, {sourceType: 'commonjs'}) or 'esm'/'es6' — none are accepted; feeding values in from other tools' configs.

Common situations: Porting options from Babel or other parsers; passing tsconfig's `module: "commonjs"` value straight into parser options; typos like 'modules'.

Related errors


AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17). Data as JSON: /api/errors/d582e00971798fc1. Report an issue: GitHub.