microsoft/TypeScript · error · Error

${option.name} is a string value; tsconfig JSON must be pars

Error message

${option.name} is a string value; tsconfig JSON must be parsed with parseJsonSourceFileConfigFileContent or getParsedCommandLineOfConfigFile before passing to createProgram

What it means

Thrown by `ts.createProgram` when a compiler option of custom type (e.g. `target`, `module`, `moduleResolution`, `jsx`, `newLine`, `target`-family enums) is still a raw string. These options must be parsed into their numeric enum form first; a leftover string means the tsconfig JSON was not run through `parseJsonSourceFileConfigFileContent` / `getParsedCommandLineOfConfigFile` / `parseJsonConfigFileContent` before being passed in.

Source

Thrown at src/compiler/program.ts:1526

 * @param options - The compiler options which should be used.
 * @param host - The host interacts with the underlying file system.
 * @param oldProgram - Reuses an old program structure.
 * @param configFileParsingDiagnostics - error during config file parsing
 * @returns A 'Program' object.
 */
export function createProgram(rootNames: readonly string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program, configFileParsingDiagnostics?: readonly Diagnostic[]): Program;
export function createProgram(_rootNamesOrOptions: readonly string[] | CreateProgramOptions, _options?: CompilerOptions, _host?: CompilerHost, _oldProgram?: Program, _configFileParsingDiagnostics?: readonly Diagnostic[]): Program {
    let _createProgramOptions = isArray(_rootNamesOrOptions) ? createCreateProgramOptions(_rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : _rootNamesOrOptions; // TODO: GH#18217
    const { rootNames, options, configFileParsingDiagnostics, projectReferences, typeScriptVersion, host: createProgramOptionsHost } = _createProgramOptions;
    let { oldProgram } = _createProgramOptions;
    // Stop referencing these objects to ensure GC collects them.
    _createProgramOptions = undefined!;
    _rootNamesOrOptions = undefined!;

    for (const option of commandLineOptionOfCustomType) {
        if (hasProperty(options, option.name)) {
            if (typeof options[option.name] === "string") {
                throw new Error(`${option.name} is a string value; tsconfig JSON must be parsed with parseJsonSourceFileConfigFileContent or getParsedCommandLineOfConfigFile before passing to createProgram`);
            }
        }
    }

    const reportInvalidIgnoreDeprecations = memoize(() => createOptionValueDiagnostic("ignoreDeprecations", Diagnostics.Invalid_value_for_ignoreDeprecations));

    let processingDefaultLibFiles: SourceFile[] | undefined;
    let processingOtherFiles: SourceFile[] | undefined;
    let files: SourceFile[];
    let symlinks: SymlinkCache | undefined;
    let typeChecker: TypeChecker;
    let classifiableNames: Set<__String>;
    let filesWithReferencesProcessed: Set<Path> | undefined;
    let cachedBindAndCheckDiagnosticsForFile: Map<Path, readonly Diagnostic[]> | undefined;
    let cachedDeclarationDiagnosticsForFile: Map<Path, readonly DiagnosticWithLocation[]> | undefined;
    const programDiagnostics = createProgramDiagnostics(getCompilerOptionsObjectLiteralSyntax);

    let automaticTypeDirectiveNames: string[] | undefined;

View on GitHub (pinned to b465fdbfe1)

Solutions

  1. Parse the config with `ts.parseJsonConfigFileContent` (or `parseJsonSourceFileConfigFileContent`/`getParsedCommandLineOfConfigFile`) and pass `parsed.options` to `createProgram`.
  2. If building options by hand, use the enum: `{ target: ts.ScriptTarget.ES2020 }` instead of a string.
  3. Map known string values through `ts.optionDeclarations`/the relevant parser instead of forwarding them raw.

Example fix

// before
const opts = JSON.parse(fs.readFileSync("tsconfig.json", "utf8")).compilerOptions;
const prog = ts.createProgram(["a.ts"], opts); // target/module are strings -> throws
// after
const parsed = ts.parseJsonConfigFileContent(
  ts.readConfigFile("tsconfig.json", ts.sys.readFile).config,
  ts.sys,
  ".",
);
const prog = ts.createProgram(parsed.fileNames, parsed.options);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure no custom-type option is a string before calling createProgram:
for (const opt of ts.commandLineOptionOfCustomType) {
  if (opts[opt.name] != null && typeof opts[opt.name] === "string") {
    throw new Error(`${opt.name} must be parsed (run parseJsonConfigFileContent)`);
  }
}

Type guard

function isParsedOptions(opts: ts.CompilerOptions): boolean {
  return ts.commandLineOptionOfCustomType.every(o => typeof opts[o.name] !== "string");
}

Prevention

When it happens

Trigger: Passing hand-built `CompilerOptions` where a custom-type field is a string literal (e.g. `{ target: "es2020" }`), or forwarding raw JSON-derived options straight into `createProgram` without a parsed-config step.

Common situations: Custom tooling/build scripts that read tsconfig via `JSON.parse` and pass `options` directly; IDE plugins that construct a program from scratch; migrating from an older TS version that was lenient about this.

Related errors


AI-assisted analysis of microsoft/TypeScript@b465fdbfe1 (2026-08-12). Data as JSON: /api/errors/02780fb224cf737f. Report an issue: GitHub.