Excel to HTML Table

Copy a range in Excel, Google Sheets or Numbers, paste it here, and get a real <table>. Take it away as clean HTML for a page, as Markdown for a README, or as a bordered table that keeps its layout when pasted into an email. You skip the file export and the Office CSS that Excel's Save as Web Page adds to every cell.

Pasted Cells
Table Preview
Table will appear here...

1 Excel to HTML Table example

What the clipboard holds (→ is a tab)

Region→Q1→Q2→Owner→Notes
North→12,400→13,150→Priya→On track
South→9,800→8,900→Marco→"Missed Q2:
supplier delay"

Copy HTML output

<table>
  <thead>
    <tr>
      <th>Region</th>
      <th>Q1</th>
      <th>Q2</th>
      <th>Owner</th>
      <th>Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>North</td>
      <td>12,400</td>
      <td>13,150</td>
      <td>Priya</td>
      <td>On track</td>
    </tr>
    ...
  </tbody>
</table>

The Marco row has a line break inside one cell (Alt+Enter in Excel). The spreadsheet wraps that cell in quotes on the clipboard, and it comes out as one cell, not two rows.

2 The clipboard carries values, not formatting

Values as displayed

Spreadsheets put the formatted value on the clipboard, so 12,400, 15% and 03/04/2026 arrive exactly as you saw them. Formulas are not copied, only their results.

No formatting

Colors, bold, number alignment and column widths are dropped. The HTML is plain markup that picks up your own CSS, which suits most websites.

Merged cells split

A merged range is copied as its value followed by empty cells. The output has no colspan or rowspan, so add those by hand if you need them.

Hidden rows

Whether hidden or filtered-out rows end up on the clipboard depends on the app and on how they were hidden. Be sure to check the row count before you publish the table.

3 The three copy buttons

Copy HTML gives indented <table>, <thead> and <tbody> markup with escaped cell text and no classes. Copy Markdown gives a GitHub pipe table. Copy for Email puts a rich-text table with inline borders and a shaded header row on the clipboard, for Gmail, Outlook or Google Docs. That pastes more cleanly than the spreadsheet selection itself, which can bring the sheet's fonts and column widths along.

Untick First row is header when the selection has no heading row: the HTML then has no <thead>, and the Markdown copy invents Column 1, Column 2 headers because Markdown tables require one.

How to use the Excel to HTML Table tool

  1. In Excel, Google Sheets or Numbers, select the cells you want and copy them with Ctrl+C (Cmd+C on a Mac).
  2. Click into the Pasted Cells pane and paste. The Table Preview on the right updates immediately. Dummy Data loads a sample with a multi-line cell.
  3. Keep First row is header ticked if the selection starts with column names; untick it and the HTML has no <thead>. Trim cells removes stray spaces around values.
  4. Click Copy HTML for clean <table> code, Copy Markdown for a GitHub pipe table, or Copy for Email for a bordered table you can paste into Gmail or Outlook.
  5. Clear empties the input so you can paste the next range.

How to turn pasted spreadsheet cells into an HTML table in JavaScript and Python

JavaScript (browser)

// Read what the user pasted, e.g. from a paste event or navigator.clipboard.readText()
document.querySelector("#paste").addEventListener("paste", (e) => {
  const text = e.clipboardData.getData("text/plain");
  const rows = parseSheetClipboard(text);
  e.preventDefault();
  document.querySelector("#out").innerHTML = toHtmlTable(rows, true);
});

// Tabs between cells, newlines between rows. Cells with a line break are "quoted", "" is a quote.
function parseSheetClipboard(text) {
  const rows = [];
  let row = [], field = "", q = false;
  for (let i = 0; i < text.length; i++) {
    const c = text[i];
    if (q) {
      if (c === '"' && text[i + 1] === '"') { field += '"'; i++; }
      else if (c === '"') q = false;
      else field += c;
    } else if (c === '"' && field === "") q = true;
    else if (c === "\t") { row.push(field); field = ""; }
    else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; }
    else if (c !== "\r") field += c;
  }
  if (field !== "" || row.length) { row.push(field); rows.push(row); }
  return rows;
}

const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

function toHtmlTable(rows, header) {
  const [first, ...rest] = rows;
  const head = header ? `<thead><tr>${first.map((c) => `<th>${esc(c)}</th>`).join("")}</tr></thead>` : "";
  const body = (header ? rest : rows)
    .map((r) => `<tr>${r.map((c) => `<td>${esc(c)}</td>`).join("")}</tr>`).join("");
  return `<table>${head}<tbody>${body}</tbody></table>`;
}

A quote only opens a quoted cell at the start of a field, so a value like 5" screen in the middle of a row is kept literally.

Python

import csv
import html
import io

# Text copied from Excel or Google Sheets, e.g. via pyperclip.paste()
pasted = 'Region\tQ1\tNotes\r\nNorth\t12,400\tOn track\r\nSouth\t9,800\t"Missed:\nsupplier"\r\n'

# The clipboard format is tab-separated with CSV-style quoting, so the csv module reads it
rows = list(csv.reader(io.StringIO(pasted, newline=""), delimiter="\t"))
head, body = rows[0], rows[1:]

out = ["<table>", "  <thead><tr>" + "".join(f"<th>{html.escape(c)}</th>" for c in head) + "</tr></thead>", "  <tbody>"]
for r in body:
    out.append("    <tr>" + "".join(f"<td>{html.escape(c)}</td>" for c in r) + "</tr>")
out += ["  </tbody>", "</table>"]
print("\n".join(out))

html.escape also escapes quotes by default, which keeps the output safe inside attributes as well.

Common errors and how to fix them

Everything lands in a single column
The pasted text has no tabs, usually because it came from a CSV file or a text editor instead of a spreadsheet. Use CSV to Table for comma-separated data.
One cell with a line break turned into two rows
The text lost its quotes on the way, for example by going through a chat app or an editor first. Copy straight from the spreadsheet into the tool.
Long numbers arrive in scientific notation (1.23457E+15)
The clipboard carries the value as the cell displays it, and General format shortens long numbers. Format the column as Number with zero decimals, or as Text for IDs, then copy again.
Merged cells come out as one value followed by blanks
HTML has colspan and rowspan, but the clipboard text does not say which cells were merged. Add the attributes by hand after copying the HTML.

Specifications and references

This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.

Frequently Asked Questions

How do I convert Excel cells to an HTML table?

Select the range in Excel, press Ctrl+C (Cmd+C on a Mac), click into the Pasted Cells pane and paste. Click Copy HTML to get the <table> markup.

Does it work with Google Sheets and Numbers?

Yes. Excel, Google Sheets and Numbers all put tab-separated text on the clipboard, which is what the tool reads. LibreOffice Calc works the same way.

Are formatting, colors and formulas kept?

No. The clipboard text holds the displayed values only, so you get the numbers as formatted in the sheet but not colors, bold, borders or formulas. The HTML is plain markup that uses your own CSS.

What happens to cells with line breaks?

Excel wraps a cell containing a line break in double quotes when it copies, and the tool reads it back as a single cell with the break intact.

How do I paste the table into an email?

Click Copy for Email and paste into the message body in Gmail or Outlook. It copies rich text with inline borders and a shaded header row.

Copy HTML or Copy for Email, which one do I need?

Copy HTML when you will put the markup into a page, a CMS code view or an email template that has its own CSS: it is clean <table> code with no styles. Copy for Email when you will paste into a visual editor such as Gmail, Outlook, Google Docs, Word or Notion. That copy is a rendered table with the borders and header shading inlined, so it looks right straight away.

Can I convert Excel cells to a Markdown table?

Yes. Paste the range and click Copy Markdown to get a GitHub-flavored pipe table, ready for a README, a pull request description, a wiki page or Notion. Cells with line breaks become <br> so the row stays intact, and if First row is header is unticked the tool invents Column 1, Column 2 headers, because Markdown tables cannot exist without a header row.

Related

If you have the file instead of a selection, export it as CSV and use CSV to Table, or turn the workbook into data with Excel to JSON. For a Markdown table with alignment options, use CSV to Markdown Table (pick Tab as the delimiter for pasted cells).

More Data Converters

Browse all data converters

Copied to clipboard!