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

consola

consola is a console wrapper for Node CLIs. Instead of console.log everywhere you get typed helpers like consola.info, consola.start, consola.success, consola.warn, and consola.box, each with its own icon, color, and numeric level, so a build script reads like a build script. Output goes through pluggable reporters: a fancy colored one by default, a plain one when it detects CI or a test runner, and a browser one in the browser. It can also swallow the rest of your program's output, since wrapConsole and wrapStd redirect console.log and raw stdout writes into the same pipeline. It declares no runtime dependencies and ships both ESM and CommonJS builds.

Verdict

The most pleasant way to make a Node CLI's output look intentional, and the mocking and console-wrapping features are genuinely useful in tests. Treat the stalled release cadence as the main risk, and never reach for it as your server's logger.

API stability5/5The 3.x surface has been steady since 2023: log types, reporters, withTag, mockTypes, and wrapConsole all still work as documented, and 3.4.x releases were additive.
Docs3/5One README covers every method with short examples, including the vitest recipe and a custom reporter, which is enough to get going. There is no reference site, log types are documented by pointing at src/constants.ts, and the environment-driven level defaults are not spelled out anywhere.
Maintenance3/5The repo was pushed August 2026 with 40 open issues (91 counting PRs) under the unjs org, so it is not abandoned. But the last published release is 3.4.2 from March 2025, which means well over a year of merged work sitting unreleased.
Ecosystem4/5The logging layer for Nuxt and much of unjs, which is where most of its download volume comes from, and it composes with the rest of that toolchain. Outside that world it is one of several CLI loggers rather than the default.

Use it if

  • You are writing a CLI or build script and want levelled, icon-prefixed output that automatically degrades to plain text in CI, without wiring up color detection yourself
  • You want prompts, boxes, colors, and logging from one install: consola.prompt covers text, confirm, select, and multiselect, and consola/utils exports box, colors, and stripAnsi
  • You need to capture output in tests: consola.mockTypes(() => vi.fn()) mocks every log type at once, and wrapAll() pulls stray console.log calls from your dependencies into the same mocks
  • You are already in the unjs or Nuxt world, where consola is the logger those tools use, so it is in your tree either way
Skip it if

Setup reality

npm i consola and import { consola } from 'consola' works from both ESM and CommonJS, with types bundled and no dependencies to install, on Node ^14.18 or >=16.10. The surprises are all about which build you get and what level is active. Export conditions hand Node the fancy reporter and browsers the browser build, so the same import behaves differently by target and consola/basic, consola/core, and consola/browser exist for when you want to choose. Level defaults are environment-derived: std-env reports test and the default level drops to warn, which is why consola.info goes missing under vitest until you set consola.level yourself. CI detection also swaps the fancy reporter for the basic one, so your local screenshots will not match your pipeline logs. CONSOLA_LEVEL overrides the level in Node but is ignored by the browser and core builds.

Patterns

Use the built-in log typeslog-types

import { consola } from "consola";

consola.start("Building project...");
consola.info("Using consola");
consola.warn("A newer version is available");
consola.success("Project built!");
consola.error(new Error("Something broke"));
consola.box("I am a simple box");

Each type carries its own level: fatal and error are 0, warn is 1, log is 2, info/success/start/ready/box are 3, debug 4, trace 5. Anything above the active level is dropped without a trace.

Create an instance instead of using the global onecreate-instance

import { createConsola } from "consola";

const logger = createConsola({
  level: 4,
  fancy: true,
  formatOptions: { columns: 80, colors: true, compact: false, date: false },
});

logger.debug("now visible");

fancy: true forces the colored reporter even in CI, which is what you want when your CI renders ANSI. The global consola is shared across every module in the process, so a library should make its own instance.

Control which logs appearlog-level

import { consola, LogLevels } from "consola";

consola.level = LogLevels.debug;   // 4
consola.level = -999;              // silent

// from the shell, Node builds only:
// CONSOLA_LEVEL=5 node cli.mjs

The default level is derived from the environment: debug when a debug env var is set, warn under a test runner, info otherwise. That warn default is why consola.info produces nothing inside vitest until you raise the level.

Tag logs by subsystemtagged-logger

import { consola } from "consola";

const db = consola.withTag("db");
const http = consola.withTag("http");

db.info("connected");     // [db] connected
http.warn("slow response");

withTag returns a child instance that inherits the parent's options, including mocks, so mocking the parent in a test also mocks every tagged child. withScope is an alias for the same thing.

Emit JSON with a custom reportercustom-reporter

import { createConsola } from "consola";

const logger = createConsola({
  reporters: [
    { log: (logObj) => console.log(JSON.stringify(logObj)) },
  ],
});

logger.log("foo bar");
// {"date":"...","args":["foo bar"],"type":"log","level":2,"tag":""}

Passing reporters replaces the defaults entirely, so you lose colored output unless you add it back. Use addReporter to append one instead, for example a reporter that calls process.exit(1) when logObj.type is "fatal".

Redirect console and stdout into consolawrap-console

import { consola } from "consola";

consola.wrapAll();       // console + stdout/stderr
console.info("routed through consola");
process.stdout.write("so is this\n");

consola.restoreAll();

wrapStd alone also catches console, since console writes to stdout, but wrapConsole is what preserves the type mapping so console.info arrives as an info log. Always restore in a finally block or later code inherits the patched globals.

Assert on log output in vitest or jestmock-in-tests

beforeAll(() => consola.wrapAll());
beforeEach(() => consola.mockTypes(() => vi.fn()));

test("logs the message", async () => {
  await run();
  const messages = consola.log.mock.calls.map((c) => c[0]);
  expect(messages).toContain("your message");
});

Re-mock in beforeEach or calls leak between tests. The callback receives (typeName, type); return a falsy value to leave a type unmocked, as in mockTypes((t) => t === "fatal" && vi.fn()).

Ask the user somethingprompts

const name = await consola.prompt("Project name?", {
  type: "text",
  placeholder: "my-app",
});

const ok = await consola.prompt("Deploy to production?", {
  type: "confirm",
  initial: false,
  cancel: "reject",   // Ctrl+C throws instead of returning the default
});

Types are text, confirm, select, and multiselect only. The default cancel strategy resolves with the default value, so Ctrl+C on a confirm whose initial is true reads as a yes. Set cancel: "reject" on anything destructive.

Queue logs while something else owns the terminalpause-resume

consola.pauseLogs();
await renderProgressBar();
consola.resumeLogs();   // queued logs flush here

Pausing is global for that instance and its children, not scoped to a callback, so a thrown error between the two calls leaves logging paused for the rest of the process. Wrap it in try/finally.

Import a lighter entry pointsmaller-builds

// no fancy reporter, plain output
import { consola } from "consola/basic";

// browser reporter
import { consola } from "consola/browser";

// no reporter at all, bring your own
import { createConsola } from "consola/core";
const logger = createConsola({ reporters: [myReporter] });

consola/core exports only createConsola, with no default instance and no reporter, so nothing prints until you add one. CONSOLA_LEVEL is not read by the core or browser builds.

Use the formatting helpers on their ownconsole-utils

import { colors, box, stripAnsi, align } from "consola/utils";

console.log(colors.green("ok"));
console.log(box("Deployed", { title: "done" }));
console.log(stripAnsi(coloredString).length);

These are plain functions with no reporter behind them, so they ignore consola.level and print whatever you pass. stripAnsi is what you want before measuring string width or writing to a log file.

Log an object that looks like a log objectraw-logging

consola.log({ message: "hello" });      // prints: hello
consola.log.raw({ message: "hello" });  // prints: { message: 'hello' }

A first argument with message or args keys is interpreted as a log object, so user data with those field names gets silently unwrapped. The .raw variant, available on every type, forces it to be treated as data.

Alternatives

PackageRegistryPick it when
pinonpmThe logs go to a machine (a log aggregator, a container runtime) rather than a person, and you need JSON, levels, and speed.
@clack/promptsnpmYou only want the interactive prompts; consola wraps clack for exactly this and clack gives you the full prompt set directly.
chalknpmYou just want to color strings you print yourself and do not need levels, reporters, or console interception.