Abstract Syntax Trees AST in JavaScript Guide

An Abstract Syntax Tree is the structured representation of your JavaScript code after parsing. Learn what ASTs are, how tools like Babel and ESLint use them, and how to explore ASTs yourself.

7 min read

An Abstract Syntax Tree, or AST, is the data structure a JavaScript engine builds after reading your source code and before running it. Think of it as a structured map of every keyword, operator, variable name, and expression in your program, organized as a tree of nodes.

Every JavaScript tool that understands your code, from the V8 engine to Prettier, works with an AST. The engine uses it to generate bytecode.

A linter walks it to find problems. A formatter rewrites it and prints the result back as clean code.

How Source Code Becomes an AST

Before any JavaScript runs, the engine must parse it. Parsing happens in two stages:

JavaScript parsing pipeline

The lexer scans your code character by character and groups them into tokens: keywords, operators, identifiers, and literals. The parser takes that flat list of tokens and builds a nested tree that captures the grammatical structure of your program.

Here is a tiny example. Given this line:

javascriptjavascript
const sum = a + 1;

The parser turns that single line into a tree of typed objects, one per keyword, operator, and identifier. Each object records its own kind and points to the objects nested inside it, so the whole statement becomes a small, walkable structure instead of a flat string. Here is that structure written out as JSON:

jsonjson
{
  "type": "VariableDeclaration",
  "kind": "const",
  "declarations": [{
    "type": "VariableDeclarator",
    "id": { "type": "Identifier", "name": "sum" },
    "init": {
      "type": "BinaryExpression",
      "operator": "+",
      "left": { "type": "Identifier", "name": "a" },
      "right": { "type": "Literal", "value": 1 }
    }
  }]
}

The tree says: this is a const declaration named sum, whose value is a binary expression adding the identifier a and the literal 1. Every piece of meaning is preserved as a typed node with a known position in the tree.

Why ASTs Matter

You may never write an AST by hand, but you depend on them constantly:

ToolWhat it does with the AST
BabelReads your modern JS, walks the AST, transforms nodes (like arrow functions into regular functions), and prints ES5 code
ESLintParses your code, walks the AST, and checks each node against rules
PrettierParses your code into an AST, then prints it back with consistent formatting
TypeScriptBuilds a typed AST, checks type constraints, then emits plain JavaScript
Minifiers (Terser)Walk the AST and shorten identifiers, remove dead branches, and collapse expressions
V8 / SpiderMonkeyBuild an AST, then compile it to bytecode or machine code

Every time you save a file and your editor auto-formats it, an AST was built, inspected, and reprinted. Every time you run a linter, every rule walks the AST looking for patterns. The AST is the universal interface between your source text and any tool that reasons about it.

Exploring an AST with AST Explorer

The fastest way to understand ASTs is to see one. Open AST Explorer, paste a line of JavaScript in the left pane, and watch the tree appear on the right. Try pasting this:

javascriptjavascript
function greet(name) {
  return `Hello, ${name}!`;
}

You will see a root program node, containing a function declaration. That declaration has a name, a list of parameters, and a body block holding the return statement. Click any node to see its source location highlighted in the original code.

AST Explorer supports multiple parsers. The three you will see most often are:

ParserUsed by
espreeESLint
@babel/parserBabel
acornWebpack, Rollup, many bundlers

The node types are similar but not identical. Babel's AST adds extra metadata for source maps and comments. ESLint's AST is stricter and follows the ESTree specification.

AST Node Types Every Developer Should Recognize

While the full ESTree specification lists dozens of node types, a few appear in nearly every program:

Node TypeWhat it representsExample source
ProgramThe root of every filethe whole script
VariableDeclarationconst, let, or var statementconst x = 1;
FunctionDeclarationA named functionfunction foo() {}
ArrowFunctionExpressionAn arrow function(x) => x + 1
BinaryExpressionAn operation with two operandsa + b
CallExpressionA function callconsole.log(1)
MemberExpressionProperty accessobj.prop or obj["prop"]
IfStatementAn if/else blockif (x) {}
LiteralA raw value42, "hello", true
IdentifierA variable or function namemyVar

Each node has a type property and a set of children that reference other nodes.

A binary expression node has a left side, an operator, and a right side. A call expression node has a callee and a list of arguments. The tree is recursive: every child is itself a valid AST node.

How Babel Transforms Code Using an AST

Babel is the most widely used AST manipulation tool in the JavaScript ecosystem. It works in three phases:

Babel transform pipeline

A Babel plugin is a plain object with a visitor property. Babel calls the matching function on the visitor every time it walks past a node of that type, and the function can read or rewrite that node in place. Here is a minimal plugin that turns every const into let:

javascriptjavascript
// A toy Babel plugin: replace every const with let
module.exports = function () {
  return {
    visitor: {
      VariableDeclaration(path) {
        if (path.node.kind === "const") {
          path.node.kind = "let";
        }
      }
    }
  };
};

Babel calls the VariableDeclaration function every time it walks past a node of that type. The path object wraps the current node and provides methods for replacing, removing, or inserting sibling nodes. The plugin above simply flips kind from const to let.

Real plugins are more involved. The arrow function transform plugin, for instance, replaces an arrow function node with a regular function expression, moves the this binding, and adjusts the body.

The pattern stays the same across every plugin: visit a node, read its properties, and replace it with new nodes.

How ESLint Uses the AST to Find Problems

ESLint rules follow the same visitor pattern as Babel plugins, but instead of transforming nodes they report problems:

javascriptjavascript
// A toy ESLint rule: forbid var declarations
module.exports = {
  meta: {
    type: "suggestion",
    docs: { description: "disallow var declarations" }
  },
  create(context) {
    return {
      VariableDeclaration(node) {
        if (node.kind === "var") {
          context.report({
            node,
            message: "Unexpected var, use const or let instead."
          });
        }
      }
    };
  }
};

The structure is identical: select a node type, inspect its properties, and react. ESLint reports a message with a severity instead of mutating the tree.

This is the power of ASTs. Transformers, linters, and formatters all use the same tree structure. Once you can read an AST, you can write any of these tools.

Practical Use Cases for Custom AST Tools

You do not need to write a full Babel plugin to benefit from ASTs. Smaller practical uses include:

  • Codemods: Scripts that automate large-scale refactors across hundreds of files. A codemod to rename a deprecated API or restructure imports is a Babel plugin run in batch mode.
  • Custom lint rules: Enforce project-specific conventions that no public ESLint plugin covers, like "all API calls must include an error boundary" or "use a specific import order."
  • Code generation: Read a schema, configuration, or API definition and output JavaScript. Tools like GraphQL Code Generator build an AST and print it, ensuring valid syntax.
  • Documentation extractors: Walk the AST to find exported functions and their JSDoc comments, then generate markdown.
  • Dead code detection: Walk the AST to find unreachable branches, unused variables, or imports that are never referenced, then remove or report them.

The common thread is: you need to understand your own code programmatically. An AST gives you a precise, queryable model of that code.

Exploring Further

The best next step is hands-on: paste some code into AST Explorer, study the tree, and try writing a one-rule Babel plugin that changes a single node type. The mental model transfers to every AST-based tool you will ever use.

Related reading:

Rune AI

Rune AI

Key Insights

  • An AST is a tree-shaped data structure that represents the syntax of your source code.
  • The JavaScript engine builds an AST before compiling or interpreting your code.
  • Tools like Babel, ESLint, and Prettier read, analyze, and rewrite ASTs.
  • Use AST Explorer to paste code and see its tree structure in real time.
  • Writing a Babel plugin or ESLint rule means walking and modifying an AST.
RunePowered by Rune AI

Frequently Asked Questions

Do I need to understand ASTs to write JavaScript?

No. Most JavaScript developers never interact with ASTs directly. But if you write Babel plugins, ESLint rules, codemods, or build developer tools, understanding ASTs is essential.

Are all ASTs the same across different parsers?

No. Each parser produces a slightly different node structure. ESLint uses espree, Babel uses its own parser, and TypeScript has its own AST. The core idea is the same, but node types and property names differ.

Conclusion

An AST is the bridge between the text you write and the instructions the engine runs. Every time you format, lint, or transform JavaScript code, an AST is built, inspected, and sometimes rewritten. You do not need to master ASTs to be a productive developer, but peeking under the hood reveals how your tools really work.