To analyze your Twitter archive in Excel, convert the post-data records into a table and store post IDs as Text. Do not assume the ZIP contains tweets.csv, and do not double-click an untrusted CSV before choosing its column types.

There are two different starting points: a personal ZIP downloaded from X, or a collected archive already in Tweet Archivist. Choose the matching path below; they are not interchangeable datasets.

Choose the source before choosing columns

Your sourcePractical pathScope to record
Official personal X ZIPInspect the post files, then convert selected fields locally.Account, snapshot date, files included and missing fields.
Collected archive in Tweet ArchivistUse the archive export controls for an available format.Collection source, filters, row count and export time.
An X analytics report already exported elsewhereImport the supplied file with explicit column types.Report period and metrics actually provided.

Keep the original source unchanged. Make a working copy before converting it. A table of post text and timestamps is useful for content analysis, but it is not automatically an impressions report or a complete historical engagement dataset.

Convert personal archive files locally

First download and extract your own archive. Inspect the actual post-data file names and wrapper using our format reference. The example below supports records shaped as {"tweet": {...}} under window.YTD.tweets.partN = [...]. It stops on a different structure instead of silently producing an empty workbook.

For a modest-sized archive, install Python and the openpyxl library in your own local environment:

python -m pip install openpyxl

Download the Python conversion example, or copy the full code below. It runs locally with Python and openpyxl; it does not upload your archive data. Save it in a working folder. If you keep the downloaded filename, use twitter-archive-to-excel.py instead of archive_to_excel.py in the commands below.

Save the following as archive_to_excel.py in a working folder. It writes three literal-text columns, retains source timestamps, combines the file parts you explicitly supply and removes duplicate IDs (keeping the first record). The final message reports the duplicate count. No post data is sent to a website by this script.

import json
import re
import sys
from pathlib import Path
from openpyxl import Workbook
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE

output = Path("archive-tweets.xlsx")
if output.exists():
    raise FileExistsError("Move the previous workbook before running again")
if len(sys.argv) < 2:
    raise SystemExit("Pass one or more post-data .js files")

wb = Workbook()
ws = wb.active
ws.title = "Posts"
ws.append(["tweet_id", "created_at_source", "text"])
seen = set()
duplicates = 0
for filename in sys.argv[1:]:
    text = Path(filename).read_text(encoding="utf-8-sig")
    match = re.fullmatch(
        r"\s*window\.YTD\.tweets\.part\d+\s*=\s*(\[.*\])\s*;?\s*",
        text, flags=re.DOTALL
    )
    if not match:
        raise ValueError(f"Unexpected wrapper: {filename}")
    records = json.loads(match.group(1))
    if not isinstance(records, list):
        raise ValueError("Expected an array")
    for record in records:
        tweet = record["tweet"]
        post_id = tweet["id_str"]
        if not isinstance(post_id, str) or not re.fullmatch(r"[0-9]+", post_id):
            raise ValueError("Expected a string id_str; inspect this archive")
        if post_id in seen:
            duplicates += 1
            continue
        seen.add(post_id)
        values = [post_id, tweet.get("created_at", ""), tweet.get("full_text", "")]
        if ws.max_row >= 1048576:
            raise ValueError("Too many records for one Excel worksheet")
        row = ws.max_row + 1
        for col, value in enumerate(values, start=1):
            if not isinstance(value, str):
                raise ValueError("Expected text fields; inspect this record")
            if len(value) > 32767 or "\r" in value or ILLEGAL_CHARACTERS_RE.search(value):
                raise ValueError("A value cannot be preserved exactly in this worksheet")
            cell = ws.cell(row=row, column=col, value=value)
            cell.data_type = "s"  # Store even '=...' as literal text, not a formula.
            cell.number_format = "@"
ws.freeze_panes = "A2"
ws.auto_filter.ref = ws.dimensions
wb.save(output)
print(f"Saved {len(seen)} distinct posts to {output}; skipped {duplicates} duplicate IDs")

Run it with the actual paths to the files you inspected. For a single file:

python archive_to_excel.py data/tweets.js

For split data, pass every relevant part explicitly after the script name, separated by spaces. Keep them from the same snapshot. The output is archive-tweets.xlsx in the working folder. If that name exists, the script stops so you do not overwrite an earlier result.

The example deliberately fails on missing string IDs, incompatible characters, carriage returns or worksheet limits. It rejects carriage returns rather than silently normalizing them to newlines. Inspect the source and error before changing the conversion. For very large archives, use a suitable database or a reviewed streaming conversion instead of loading every record into a regular workbook.

Protect exact IDs and literal post text

Microsoft documents Excel's 15-digit numeric precision limit. A long post ID such as 1234567890123456789 is an identifier, not a quantity. The example stores it as text. Changing the number format after rounding will not restore the original ID.

Post text can begin with =, +, - or @. Spreadsheet software may interpret CSV values as formulas. The workbook example explicitly marks cells as string data. It does not construct formulas, external links or macros from archive values.

For a CSV from another source, use an import workflow such as Data → From Text/CSV → Transform Data, set identifier and content columns to Text, and check any automatically added type-conversion step before loading. Menus vary by Excel edition. OWASP notes that CSV escaping is not universally safe, especially after saving and reopening. Keep an untouched source and verify literal text after a format change.

Export an existing collected archive from Tweet Archivist

For a collected archive you own in Tweet Archivist, use its export controls to choose CSV, Excel or JSON. Export access requires an eligible paid account. The current collected-archive export applies the archive filters and caps a single export at 50,000 rows, so record the exported count and check whether you reached that cap.

This collected-archive export is separate from the personal-ZIP analysis workflow. Do not assume that uploading a ZIP creates an unlimited, newly collected historical dataset. A tool can only export the records and fields that it holds.

Review current plan access and your archive before choosing this route. For private local conversion of your own post files, use the example above instead.

Validate the workbook before building charts

  1. Count distinct IDs. Compare the workbook with the source parts processed, not the account's public profile counter.
  2. Check exact digits. Compare an early and a recent ID character by character with the source.
  3. Inspect special text. Check a multiline post and any text beginning with a formula-like character.
  4. Keep source dates. Add a separate parsed-date column for grouping; choose a timezone and preserve the original timestamp.
  5. Describe missing data. Do not convert unavailable impressions or metrics into measured zeroes.

For a first analysis, group the validated posts by month and count records. Label the chart “Posts in this archive by month.” Before comparing engagement, confirm that each source actually includes comparable metrics collected in comparable periods.

Keep a short conversion note with the workbook

Record the source archive date, input filenames, script version or changes, distinct row count and any exclusions. Keep raw fields on one sheet and derived analysis separately. That makes the workbook useful to a colleague who did not watch you create it.

If the objective is finding a deleted original, conversion alone cannot restore missing content. Use the deleted-post evidence checklist to establish whether a prior copy exists.