denoland/deno · error

--outdir is required when bundling HTML entrypoints

Error message

--outdir is required when bundling HTML entrypoints

What it means

`deno bundle` refuses to bundle HTML entrypoints without `--outdir`. An HTML bundle produces several files (hash-renamed JS, sourcemaps, patched HTML), so stdout output or a single-file target is impossible; the command fails up-front with this message.

Solutions

  1. Pass an output directory: `deno bundle --outdir=dist index.html`
  2. If you wanted a single-file JS bundle, point the command at the script module (e.g. `src/main.ts`) instead of the HTML file

Example fix

# before
deno bundle index.html

# after
deno bundle --outdir=dist index.html
Defensive patterns

Strategy: validation

Validate before calling

// Wrapper: require --outdir when any entry ends with .html
const entries = Deno.args.filter((a) => !a.startsWith("-"));
if (
  entries.some((e) => e.endsWith(".html")) &&
  !Deno.args.some((a) => a.startsWith("--outdir"))
) {
  throw new Error("HTML entrypoints require --outdir");
}

Prevention

When it happens

Trigger: Running `deno bundle page.html` (any invocation whose entrypoints include an HTML file) without an `--outdir` flag.

Common situations: Habit from JS bundling where `deno bundle mod.ts` prints to stdout; CI scripts and npm-style tasks that call `deno bundle` without an output directory.

Understand the failure class

Background: "--flag is required" and "must specify" CLI errors: how missing-required-flag validation works and how to fix it — this error's family across 20 libraries.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/2cc65e0932c9d5f0. Report an issue: GitHub.

Appendix: source

Thrown at cli/tools/bundle/mod.rs:148

      init_cwd,
      npm_resolver,
      node_resolver,
    );
    plugin_handler.prepare_module_load(&roots).await?;
    let graph = plugin_handler.module_graph_container.graph();
    let mut fully_resolved_roots = IndexSet::with_capacity(graph.roots.len());
    for root in &graph.roots {
      fully_resolved_roots.insert(graph.resolve(root).clone());
    }
    *plugin_handler.resolved_roots.write() = Arc::new(fully_resolved_roots);

    Ok(BundlerInput::Entrypoints(
      roots.into_iter().map(|e| ("".into(), e.into())).collect(),
    ))
  } else {
    // require an outdir when any HTML is present
    if bundle_flags.output_dir.is_none() {
      return Err(deno_core::anyhow::anyhow!(
        "--outdir is required when bundling HTML entrypoints",
      ));
    }
    if bundle_flags.output_path.is_some() {
      return Err(deno_core::anyhow::anyhow!(
        "--output is not supported with HTML entrypoints; use --outdir",
      ));
    }

    // Prepare HTML pages and temp entry modules
    let mut html_pages = Vec::new();
    let mut to_cache_urls = Vec::new();
    let mut entries: Vec<(String, String)> = Vec::new();
    let virtual_modules = Arc::new(VirtualModules::new());

    for html_path in &html_paths {
      let entry = html::load_html_entrypoint(
        init_cwd,

View on GitHub (pinned to 9ad36f7a2c)