console.table() - Debug Arrays Like a Human

Format arrays and objects as readable tables in your browser console.

No third-party tools. No endless console.log() scrolling. Just data you can actually read.

Live demo

Click the button to log the sample data to your browser console (open DevTools to see it).

const users = [
  { id: 1, name: "Alice", role: "Developer", active: true },
  { id: 2, name: "Bob", role: "Designer", active: true },
  { id: 3, name: "Charlie", role: "Manager", active: false }
];

console.table(users);

💡 Open your browser DevTools (F12 or Cmd+Option+I) to see the formatted table

Before vs After

Before: console.log()
> console.log(users)
(3) [{…}, {…}, {…}]
  0: {id: 1, name: 'Alice', ...}
  1: {id: 2, name: 'Bob', ...}
  2: {id: 3, name: 'Charlie', ...}
  length: 3
  [[Prototype]]: Array(0)

Collapsed by default. Have to expand each object manually. Hard to scan.

After: console.table()
(index)idnameroleactive
01"Alice""Developer"true
12"Bob""Designer"true
23"Charlie""Manager"false

Formatted as a table. All data visible at once. Easy to compare values.

Practical use cases

API responses

fetch('/api/users')
  .then(r => r.json())
  .then(users => console.table(users));

Performance timings

const timings = performance.getEntriesByType('measure');
console.table(timings, ['name', 'duration']);

Form validation errors

const errors = formik.errors;
console.table(errors);

💡 Browser support: All modern browsers. Works in Chrome, Firefox, Safari, Edge DevTools.


What is console.table()?

console.table() is a built-in method that prints arrays and objects as tables in DevTools.

Instead of:

console.log(users);
// [{…}, {…}, {…}] – collapsed by default

You get:

console.table(users);
// ┌─────────┬────┬───────────┬─────────────┬────────┐
// │ (index) │ id │   name    │    role     │ active │
// ├─────────┼────┼───────────┼─────────────┼────────┤
// │    0    │ 1  │  'Alice'  │ 'Developer' │  true  │
// │    1    │ 2  │   'Bob'   │ 'Designer'  │  true  │
// │    2    │ 3  │ 'Charlie' │  'Manager'  │ false  │
// └─────────┴────┴───────────┴─────────────┴────────┘

All rows visible. Columns aligned. Your eyes do less work.


Why this matters

Most debugging sessions start with console.log() and never really evolve.

For single values, that is fine:

console.log(user.name); // "Alice"

For arrays of objects, you get this:

console.log(users);
// (3) [{…}, {…}, {…}]
//   ▸ 0: {id: 1, name: "Alice", role: "Developer", active: true}
//   ▸ 1: {id: 2, name: "Bob", role: "Designer", active: true}
//   ▸ 2: {id: 3, name: "Charlie", role: "Manager", active: false}

Collapsed by default. Click to expand. Scroll. Forget which object you were looking at.

console.table() is the "what if we did not suffer" version:

console.table(users);

Same data, but you can actually compare values across rows at a glance.


Basic usage

Array of objects

const users = [
  { id: 1, name: "Alice", email: "alice@example.com" },
  { id: 2, name: "Bob", email: "bob@example.com" },
  { id: 3, name: "Charlie", email: "charlie@example.com" },
];

console.table(users);

DevTools renders a table with columns for id, name, and email.


Single object

const user = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  role: "Developer",
};

console.table(user);

Keys become the first column. Values become the second.


Show only specific columns

If each object has many properties, you can limit the output:

console.table(users, ["name", "email"]);

Only the selected keys appear as columns. Useful when you know what you are hunting for.


Real-world use cases

API responses

You fetch data from an API and want to confirm the shape and a few key fields.

Before:

fetch("/api/users")
  .then((r) => r.json())
  .then((users) => console.log(users));

You get a long list of collapsed objects.

After:

fetch("/api/users")
  .then((r) => r.json())
  .then((users) => console.table(users));

Now you see every row, can scroll through the table, and spot missing or odd values immediately.


Performance timings

Using the Performance API:

const timings = performance.getEntriesByType("measure");

// Harder to scan
console.log(timings);

// Easier to scan
console.table(timings, ["name", "duration"]);

You get a clean table showing each measurement and its duration.


Form validation errors

Large forms can generate many errors:

console.table(formik.errors);

Field names in one column, error messages in another. Easier than digging through nested objects.


State diffs

To see what actually changed:

const changes = [
  { field: "name", before: "Alice", after: "Alice Johnson" },
  { field: "role", before: "Developer", after: "Senior Developer" },
];

console.table(changes);

Side-by-side comparison, no mental join required.


When NOT to use console.table()

console.table() is great for structured data, but it is not the answer to every logging problem.

Skip it for:

  • Single values - console.log(42) is enough
  • Deeply nested objects - tables are flat; use console.log() or console.dir() instead
  • Huge datasets - DevTools will truncate after a certain number of rows; for large arrays, filter first
  • Non-tabular data - strings, functions, and DOM nodes are usually clearer with console.log()

Use it for:

  • Arrays of objects
  • Lists of errors or warnings
  • Performance metrics
  • Anything where you mostly care about comparing columns across rows

If it looks like a spreadsheet in your head, console.table() is probably a good fit.


Browser and Node support

console.table() works in all modern browsers:

  • Chrome
  • Firefox
  • Safari
  • Edge

It has been in DevTools for more than a decade. If someone can open the console, they can use console.table().

Node.js also supports it (v10+):

const data = [
  { id: 1, label: "one" },
  { id: 2, label: "two" },
];

console.table(data);

Same tabular output, just in the terminal.


Performance

Formatting a table is slightly more work than printing raw objects, so console.table() is a bit slower than console.log().

For normal debugging, the difference is irrelevant. You are inspecting dozens of rows, not running a trading bot.

If you are logging from inside a hot loop, collect your data first and print once:

const snapshots = [];

for (let i = 0; i < 1000; i++) {
  snapshots.push({ i, value: expensiveComputation(i) });
}

console.table(snapshots);

Readable and still cheap.


Tips and tricks

Combine with filtering

const activeUsers = users.filter((u) => u.active);
console.table(activeUsers, ["id", "name", "role"]);

Show only the rows and columns you care about.


Add computed columns

const usersWithFlags = users.map((u) => ({
  ...u,
  isAdmin: u.role === "Admin",
}));

console.table(usersWithFlags, ["name", "role", "isAdmin"]);

Pre-compute whatever you want to compare, then log once.


Keep it out of production (or not)

Most build setups strip console.* calls in production. If you want to keep console.table() around for admin/debug pages, gate it:

if (process.env.NODE_ENV !== "production") {
  console.table(users);
}

Or route it through your own logger with levels.


Try it yourself

Open DevTools (F12 or Cmd+Option+I) and paste:

const users = [
  { id: 1, name: "Alice", role: "Developer", active: true },
  { id: 2, name: "Bob", role: "Designer", active: true },
  { id: 3, name: "Charlie", role: "Manager", active: false },
];

console.log(users);
console.table(users);

Look at the two outputs next to each other. One is noise. One is information.


Resources

Copy. Paste. Debug. Your logs just became boringly readable.