mrkeyoor.com_
Thu 06 Aug 02:46 UTC
npmCLI & Toolingupdated 06 Aug 2026

enquirer

enquirer asks questions in a terminal. You hand it a question object with a type, a name, and a message, and it returns a promise resolving to an answers object keyed by name. Around twenty prompt types ship in the box: input, password, confirm, select, multiselect, autocomplete, form, list, numeral, scale, survey, snippet, sort, toggle, quiz, and more, each with its own options for validation, formatting, and default values. Two ways to call it exist side by side. The prompt() function takes one question or an array of them and runs them in sequence, which is what most scripts use. Or you import a prompt class such as Select directly and call run() on it, which gives you the instance, its events, and its styling hooks. Everything is CommonJS, there are two small dependencies, and it has no plugin system to learn.

Verdict

A wide, quick, dependency-light prompt library that still works fine, but it has been frozen since 2023 with a large open issue backlog and TypeScript support that stops at the front door. Use it in a CommonJS script that needs its unusual prompt types; start new TypeScript CLIs on @inquirer/prompts or @clack/prompts.

API stability5/5The question-object shape and the prompt classes have not changed since 2.0, and 2.4.1 has been the published version since July 2023, so code written years ago still runs unmodified. Some of that stability is simply inactivity.
Docs4/5One very long README documents every prompt with an animated demo and an option table, plus key bindings and a custom-prompt guide. There is no docs site or search, several options are only shown in the examples folder, and the type definitions document far less than the prose does.
Maintenance2/5No release since July 2023 and no commits since June 2024, with around 169 open issues and additional PRs unmerged. It is stable rather than abandoned-and-broken, but do not expect fixes for anything you report.
Ecosystem4/5Around 33.4M weekly downloads, though much of that is transitive: @eslint/create-config depends on it. The built-in prompt set is unusually wide, but there is no plugin ecosystem and few third-party prompt packages built on it.

Use it if

  • You want a lot of prompt types from one small dependency: form, scale, survey, snippet, and sort are built in, where other libraries make you install or write them
  • Your CLI asks a branching series of questions and you want skip and initial as functions of the answers collected so far, so later questions adapt to earlier ones
  • You are staying on CommonJS. enquirer is plain require()-able with no ESM migration pending, which matters for older Node scripts and generators
  • You want to subclass a prompt and override its render or its styles to build something custom without adopting a whole framework
Skip it if

Setup reality

npm install enquirer and require it; there is no config, no theme file, and only two dependencies (ansi-colors and strip-ansi). The awkward parts are elsewhere. TypeScript users hit export = plus untyped prompt classes, so importing Select cleanly usually means a local declaration file or a require cast. Windows terminals need a reasonably modern console for the unicode pointers to render, and the older cmd.exe falls back to garbled glyphs. Every prompt needs an interactive stdin, so wrap prompt calls in a TTY check before they hang a CI job. And because the package predates the current Node.js releases, its engines field still says Node 8.6 or newer, which tells you how long the API has sat still.

Patterns

Ask one questionsingle-question

const { prompt } = require('enquirer');

const { username } = await prompt({
  type: 'input',
  name: 'username',
  message: 'What is your username?'
});

prompt() always resolves to an object keyed by name, never to the bare value. Destructuring at the call site is the usual way to avoid an extra variable.

Run several questions in orderquestion-sequence

const { prompt } = require('enquirer');

const answers = await prompt([
  { type: 'input', name: 'name', message: 'Project name?' },
  { type: 'select', name: 'lang', message: 'Language?', choices: ['ts', 'js'] },
  { type: 'confirm', name: 'git', message: 'Initialise a git repo?' }
]);

Questions run one at a time and answers accumulate into a single object. If a name uses dot notation like 'author.email', the result is nested accordingly.

Pick one option from a listselect-one

const { Select } = require('enquirer');

const answer = await new Select({
  name: 'env',
  message: 'Deploy to which environment?',
  choices: [
    { name: 'dev', message: 'Development' },
    { name: 'stg', message: 'Staging' },
    { name: 'prd', message: 'Production' }
  ]
}).run();
// answer === 'dev'

The class form resolves to the value itself instead of an answers object. Selecting resolves to choice.name, not choice.message, so keep names stable and put the pretty text in message.

Select several and get their valuesmultiselect-values

const { MultiSelect } = require('enquirer');

const selected = await new MultiSelect({
  name: 'features',
  message: 'Enable which features?',
  limit: 7,
  choices: [
    { name: 'auth', value: '@app/auth' },
    { name: 'billing', value: '@app/billing' },
    { name: 'admin', value: '@app/admin' }
  ],
  result(names) {
    return this.map(names);
  }
}).run();
// { auth: '@app/auth', admin: '@app/admin' }

Without the result function you get an array of names and the value fields are simply lost. this.map converts the selected names into a name-to-value object, which is the documented workaround.

Yes/no and masked inputconfirm-and-password

const { prompt } = require('enquirer');

const { proceed } = await prompt({
  type: 'confirm',
  name: 'proceed',
  message: 'Overwrite existing files?',
  initial: false
});

if (proceed) {
  const { token } = await prompt({
    type: 'password',
    name: 'token',
    message: 'Paste your API token'
  });
}

confirm resolves to a boolean. For a secret that should leave no trace at all on screen, the invisible type hides the input entirely instead of masking it with asterisks.

Reject bad input before it returnsvalidate-input

const { prompt } = require('enquirer');

const { port } = await prompt({
  type: 'numeral',
  name: 'port',
  message: 'Port to listen on',
  initial: 3000,
  validate(value) {
    if (value < 1024 || value > 65535) return 'Pick a port between 1024 and 65535';
    return true;
  }
});

Return true to accept, or a string to show as the error and keep the prompt open. Returning false shows a generic message, which is rarely what you want.

Skip a question based on earlier answersconditional-skip

const { prompt } = require('enquirer');

const answers = await prompt([
  { type: 'confirm', name: 'custom', message: 'Use a custom registry?' },
  {
    type: 'input',
    name: 'registry',
    message: 'Registry URL',
    initial: 'https://registry.npmjs.org',
    skip() {
      return this.state.answers.custom !== true;
    }
  }
]);

A skipped prompt still resolves, using its initial value, so downstream code always sees the key. Read prior answers from this.state.answers inside skip, initial, or message.

Type-ahead over a long listautocomplete-filter

const { AutoComplete } = require('enquirer');

const pkg = await new AutoComplete({
  name: 'pkg',
  message: 'Which package?',
  limit: 10,
  choices: allPackageNames,
  suggest(input, choices) {
    return choices.filter(c => c.message.toLowerCase().includes(input.toLowerCase()));
  }
}).run();

The default suggest does a greedy substring match on choice.message. Override it for fuzzy matching or to sort by relevance; limit controls how many rows are visible, not how many are searched.

Fill several fields on one screenform-multiple-fields

const { Form } = require('enquirer');

const author = await new Form({
  name: 'author',
  message: 'Package author:',
  choices: [
    { name: 'name', message: 'Name', initial: 'Jane Doe' },
    { name: 'email', message: 'Email' },
    { name: 'url', message: 'URL', initial: 'https://' }
  ]
}).run();
// { name: '...', email: '...', url: '...' }

Form keeps all fields visible and moves between them with the arrow keys, which reads better than three sequential input prompts for related fields.

Survive Ctrl+C without a crashhandle-cancel

const { prompt } = require('enquirer');

try {
  const answers = await prompt(questions);
  await run(answers);
} catch (error) {
  // Ctrl+C rejects with the prompt's formatted error, often an empty string
  if (!error || typeof error === 'string') {
    console.log('\nCancelled.');
    process.exit(130);
  }
  throw error;
}

The rejection value is not always an Error, so error.message can be undefined and blow up your handler. Check the type before touching properties, and exit with 130 to report an interrupt honestly.

Fail fast when there is no terminalguard-non-tty

const { prompt } = require('enquirer');

function ask(questions) {
  if (!process.stdin.isTTY) {
    throw new Error('Interactive input required. Pass --name and --env instead.');
  }
  return prompt(questions);
}

In CI or behind a pipe a prompt waits on a stdin that will never deliver a keypress, so the job hangs until its timeout. An explicit check turns that into a clear error message.

Use it from TypeScripttypescript-usage

// tsconfig: "esModuleInterop": true
import Enquirer from 'enquirer';

interface Answers { name: string; env: string }

const answers = await Enquirer.prompt<Answers>([
  { type: 'input', name: 'name', message: 'Name?' },
  { type: 'select', name: 'env', message: 'Env?', choices: ['dev', 'prd'] }
]);

// Prompt classes are untyped; require them and cast
const { Select } = require('enquirer') as any;

prompt() is generic over the answers shape, which is the only well-typed part. The bundled definitions never declare Select, MultiSelect, or the other classes, so importing them by name does not type-check.

Alternatives

PackageRegistryPick it when
@inquirer/promptsnpmYou want the actively maintained mainstream option with per-prompt packages and real TypeScript types.
@clack/promptsnpmYou care about how the CLI looks: grouped flows, spinners, and cancel handling built in.
promptsnpmYou want the smallest dependency footprint and a single simple function to run a question list.