Your search history: the unintentional self
The search list is the one part of your Instagram export you never performed for anyone — five files merged into four kinds of searches, what each kind records, the 100–500 entry sample limit, and why an unposted thought is the most honest longitudinal record in the archive.
Posts are performances. Stories are performances. Even likes are half-performances —
tiny public-ish signals you chose to leave behind. The search bar is the one place
in the whole platform where you typed something before you knew whether you'd
follow through: a name you looked up at 1am, a symptom, an ex, a job title, a city
you were considering moving to. Nothing was published. Nobody was watching. And
then it landed in your export anyway — as a plain row with a term and a time.
This post is about that list: where it physically lives in the archive (it is less
"one file" than you'd think — five sources, four kinds), what each kind actually
records, how this product surfaces it without editorialising, the sample limit
that decides how much of it exists, and why a list of unperformed thoughts is the
closest thing an export has to a longitudinal portrait of a mind at work.
Your searches arrive from five separate sources — profile searches, tag
searches, word-or-phrase searches, a generic recent-searches file, and a legacysearches.json — merged into one record shape (value, href, timestamp,title) and, for JSON exports, split back into four kinds the platform itself
keeps apart.1 Profile searches record accounts you looked up; tag
searches record hashtags you opened; keyword searches record queries you typed;
the recent/legacy files carry whatever else the platform logged. The product keeps
the kinds separate on purpose — the Insights page says so: "A keyword search, a
hashtag search and a profile search are different actions and are counted
separately. The old tab merged them into one meaningless number."2 The
honest limits are structural: Instagram only lets you export your most recent
100–500 searches, so the list is a sample, not a total;3 rows without
parseable timestamps stay in the counts but leave the timing views; HTML exports
lose the four-way split entirely (one merged list, no categories);4 and a
search record contains a term and a time — never what you found, never what you
felt, never whether you clicked anything.
- 2minthe five source files and what each of the four kinds records.
- 4minthe emotional read: why unposted thoughts age differently than posts.
- 5minwhere to see it in the app, and the limits that bound the list.
Where the list physically lives
Under logged_information/recent_searches/ the export hands you up to four JSON
files, plus a legacy file at the activity root and an HTML equivalent in older
archives:5
| Source | What it holds | Extractor |
|---|---|---|
| `profile_searches.json` | Accounts you searched by username | string-list reader (`value`, `href`, timestamp) |
| `tag_searches.json` | Hashtag searches | query reader |
| `word_or_phrase_searches.json` | Typed keyword queries | query reader |
| `recent_searches.json` | The generic catch-all list | query reader |
| `searches.json` (legacy) | Older archives' single list | record reader |
| `search_history.html` | HTML-era equivalent | HTML→JSON bridge |
Three record shapes hide behind those filenames — label-value rows
(Search query / Update time), string-map rows (Search + a time key), and
plain string lists — and the parser reads all three into the same four fields:6
value— the term, the username, the hashtag. The thing you typed.href— a profile link when the record carries one (profile searchesusually do).
timestamp— when the platform says you searched, when it says anything.title— the raw label as written; sometimes the only place the termexists (modern profile exports are title-only, and the parser knows to read it
there).
For JSON exports the four kinds stay separated end to end — parser, Insights,
heatmap modes, the works. For HTML exports, the three typed buckets come back
empty and everything is one merged list: a format limitation, not a data
difference.4 The activity post documented the merge rule from the other
side; here the point is what the split means.
What each kind records — and why the difference matters
Profile searches are the people list: every account you typed into the search
bar. This is the kind that reads like a diary entry you never meant to write,
because looking someone up is a complete action with no public residue — you
can check on someone a hundred times and they will never know once. The app knows
this is the emotionally loaded one: the per-account page has a "Times you
searched for them" chart, and the accounts directory can filter and sort by
"Has searches" / "most searches".7
Tag searches are your curiosity by topic — the hashtags you opened to see what
was inside them. Moods, fandoms, causes, events. A tag search is a step past
interest: you didn't just hear the word, you went to look at its river.
Keyword searches are the free-typed queries: half-thoughts, questions,
symptoms, songs, "how to know if…". The rawest of the three, because a typed
phrase is closer to speech than to navigation.
Recent and legacy lists fill in the rest — whatever the platform's generic
log captured. The parser merges them rather than inventing a fifth category the
product cannot name honestly.
The Insights page's caveat is worth repeating because it is the whole editorial
position: the four kinds are different actions, and any product that sums them
into one "searches" number has destroyed the only interesting property of the
data — which of these were you reaching for people, topics, or answers?2
Anatomy of a single row
Before the emotional read, one sober minute with the object itself, because
everything above is abstraction over four strings. A profile-search row in a
modern JSON export looks, stripped to its shape, like this:
{
"title": "someone_from_years_ago",
"href": "https://www.instagram.com/someone_from_years_ago/",
"string_map_data": {
"Update time": { "timestamp": 1712345678 }
}
}(Illustrative. Synthetic username — real handles never appear in this
repository's examples.) Three of the four record fields are visible:title carries the term, href the destination when there is one, and the
timestamp under its raw Unix form. Older exports use a different wrapper —
a Search query label paired with an Update time value — and some files are
nothing but a bare list of strings with no time at all. The parser reads all
three shapes into the same ActivityRecord so the rest of the product never
has to care which era it is looking at.6
That uniformity is also why the honest limits are structural rather than
defensive: a row literally is four fields. When the product says a search
carries no outcome, that is not modesty — there is no fifth field to read.
When a heatmap cell is empty for an hour, either nothing was searched or the
row for that hour had no readable time; the record cannot distinguish those
two cases, so the views don't pretend it can.
Checking any of this against your own file
The claims in this post are filesystem claims, and they are cheaply verifiable
on your own machine:
- Unzip your export and find
logged_information/recent_searches/— countthe JSON files present. Four in a current JSON export; fewer if you chose a
narrower download, none if the category was excluded. - Open one and read the wrappers. If every row has a
string_map_datablockwith a
timestamp, your rows are dated; if the file is a bare string list,
yours are undated — which is why totals and heatmap cells can disagree. - If you requested the HTML format instead, look for
search_history.htmland note the absence of any
profile_searchessibling: the split is gone at
the source, not hidden by any viewer. - Compare your file's row count against the sample cap: if you see a number
in the low hundreds on an account you have used for a decade, you are
looking at the platform's 100–500 window, not your life.
A tool that cannot survive that four-step check is not worth trusting with the
list, and this one is built to. Every number on the views above can be traced
back to a row you can open in a text editor — that is the whole contract of a
local-first reader: the file stays yours, the derivation stays visible, and if
the two ever disagree, the file wins.
The unintentional self
Here is the property that makes this list unlike anything else in the archive: a
search is recorded before you knew how you felt about the result. You hadn't
decided yet. There was no audience, no curation, no even-self-consciousness — the
term you typed was the unedited version. Psychologists studying "self-regulation"
would call some of these goal-related search; a reader of their own list usually
just calls it embarrassing, then accurate.
Three properties make it longitudinal in a way posts are not:
- Repetition without performance. Searching the same name thirty times over
four years would be bizarre to publish and is completely normal to do. The
list therefore tracks attention — the thing people actually want from a
journal — without the distortion of an audience watching the tracker. - Pre-decision moments. A search usually sits before the action: looking up
a person before a message, a hashtag before a follow, a symptom before a
doctor, a city before a trip. It is the archive's only "thinking" layer —
everything else records what happened, this records what you were considering. - Nothing to curate. Because searches were never visible, nobody prunes them
for taste. The embarrassing and the profound get the same font size, which is
exactly why the list feels more honest than your feed ever could.
The healthy way to read it is as weather, not as testimony: a record of what the
mind passed through, with all the context the file lacks (see the limits below).
The unhealthy way is to treat every row as a revealed preference carved in stone.
Both readings are available to you; only one of them is supported by the data —
and the app's job is to show the rows, not to pick one.
Where you can see it in the app
- The heatmap's four search modes — All Searches, People Searches, Tag
Searches, Keyword Searches — each a day-hour grid of that kind only, with its
own search box underneath ("Search people you looked up by username…", "Search
hashtags you looked up…").9 - Insights' search section — "Searches you ran", "People you looked up", a
section head that says the quiet part: "Instagram splits searches into four
files — they are not the same thing."2 - Per-account pages — the "Times you searched for them" chart per person:
distribution across time for one lookup relationship.7
- The accounts directory — filter to accounts you've searched, sort by
search count.7
- Ask-anything — "what did I search for in 2023" runs as a retrieval query
over the same records; the index covers searches alongside messages, captions,
hashtags and links.10 - Compare — search totals and profile-search totals as side-by-side metrics
between two exports, with the sample caveat attached to the number.3
Everything above is derived in the browser from the rows in your ZIP. Nothing is
uploaded, nothing is enriched, nothing is inferred.
The limits, in the order they bite
It is a sample — the platform's own export cap. The comparison view's caveat
states it plainly: "Instagram only lets you export your most recent 100–500
searches, so treat this as a sample rather than a total."3 "Recent" does
the heavy lifting: your earliest searches — sometimes most of them — are simply
outside the window. Counts are therefore floors, never ceilings: "412 searches"
means at least 412, from a slice.
Four kinds only exist in JSON exports. If your archive came down as HTML, the
typed buckets are empty and the parser hands you one merged list — same rows,
lost categorisation.4 The product shows what the format supports rather
than guessing which file each row came from.
Timing needs a readable timestamp. Rows without a parseable time still count
in totals (the taxonomy deliberately never filters them) but never enter a heatmap
cell or a dated chart — so "searches by hour" is built from the dated subset, and
the two numbers can legitimately differ.11
A search is a term, not an outcome. The record does not say what you found,
whether you clicked, whether you messaged the person, or whether you closed the
app afterwards. It also does not know why — the same string is a joke, a worry
and a homework assignment in three different rows. Any story the list seems to
tell is a story you supply from memory; the file's contribution is the when and
the how-often, delivered flatly.
No feelings, no blocks, no receipts — the standing rule. The product never
characterises a person's inner life from data, and searches are not an exception
because they feel more intimate.8 The intimacy changes how you should
read the list, not what the software is allowed to say about it.
Why does my export only have a few hundred searches when I've used Instagram for years?
Because the platform caps what it hands back — the most recent 100–500, per the
caveat attached to the comparison view. The cap is a property of the export, not
of your account or this app. Ask for "all time" when you request the archive
(it helps everything else too), and read search counts as a floor.
Can I see searches people did on *my* profile?
No — and no export can. You receive your own account's data; other people's
searches for you are not in your archive, in anyone else's archive, or in any
feature of this product. The per-account "Times you searched for them" chart is
always your searches for them.
Do searches I deleted before requesting the export show up?
You get what the platform recorded for export at request time — the parser
neither adds nor recovers anything. Treat the list as the platform's recent
sample of your search activity, subject to whatever the platform retained,
cleared or truncated, none of which any downloader can reverse.
Why do some searches have no date?
Because the row came without a parseable timestamp — a real and common shape in
these files (the FAQ's standing rule: show "Unknown" rather than invent a date).
They count in totals and appear in the record feed; they stay out of heatmaps and
dated charts, which is why the two views can differ.
The four search kinds are collapsed in my heatmap — bug?
No: an HTML export loses the file split at the source (the typed buckets come
back empty by construction), so everything is one merged kind. JSON exports keep
All/People/Tag/Keyword separate. If you need the split, re-request the archive in
the JSON format — the how-to post covers the settings that matter.
Is there anything revealing in searches that the app judges me for?
There is no judging code. No sentiment, no labels, no scoring of query text — the
same record-only rule as everywhere: counts, times, filters. What the list is,
honestly: the least performed part of your archive, which is why it usually feels
the most true. Read it yourself; the app will keep the timestamps straight while
you do.
Questions this comes up
The what's-in-the-export post for the folder this list came from and the
four-kinds inventory; the activity post for the parser's merge rule; the heatmap
post for how any timestamped record becomes a day-hour cell; the person-in-your-
archive post for the profile-lookup layer that profile searches feed.
1: packages/shared/src/parsers/activity.ts — five sources collected and
concatenated into searches: profile/tag/word-phrase/recent path predicates plus
the legacy searches.json record reader; the typed buckets are kept as separate
arrays alongside the merge.
2: apps/web-next/src/views/Insights.tsx — section "What you searched for"
("Instagram splits searches into four files — they are not the same thing") and
its caveat: keyword, hashtag and profile searches "are different actions and are
counted separately. The old tab merged them into one meaningless number."
3: packages/shared/src/analytics/compare.ts — the in-product caveat:
"Instagram only lets you export your most recent 100–500 searches, so treat this
as a sample rather than a total."
4: parsers/activity.ts — HTML path collects search_history.html into the
merged list and returns empty profile/tag/keyword buckets by construction.
5: parsers/activity.ts — path predicates forlogged_information/recent_searches/{profile,tag,word_or_phrase,recent}_searches.json
and legacy searches.json; parsers/html-to-json.ts — the HTML→JSON bridge forsearch_history.html.
6: types/activity.ts — ActivityRecord { value, href, timestamp, title };parsers/activity.ts — three extractors (label-value, string-map, string-list)
and the title-only fallback for modern profile exports.
7: views/AccountProfile.tsx — "Times you searched for them" and the
per-account search chart; views/Accounts.tsx — "Has searches" filter, "most
searches" sort, "You searched" column.
9: components/ActivityHeatmap.tsx — the four search modes with labels and
tips; views/Activity.tsx — per-mode search-box placeholders.
10: packages/shared/src/ama/intents.ts — "Times you searched them" row;views/Ama.tsx — the index covering searches alongside messages, captions,
hashtags and links.
11: analytics/activity-taxonomy.ts — raw counts "never timestamp-filtered"
beside a separate dated count, which is why heatmap cells and record totals can
legitimately differ.
8: ama/tone.ts — the refusal contract for questions about inner life
("why did they stop texting me — the archetypal unanswerable question"); the
product's no-feelings-inference rule applies to query text exactly as it applies
to messages.
Footnotes
- merge
- split
- sample
- html
- files
- shape
- account
- refuse
- heat
- ama
- dated