Repository navigation
search
You need to find something that was written: a message, an agreement, a file someone sent, a code from months ago. This page shows how to search everything tg has saved on this computer — Telegram messages, and the mail and notes that memo imported — and how to ask Telegram's own search at the same time.
After reading it you can find messages by words, people, chats, dates, files and links, save a search and run it again, count matches, and tell a real "not found" from a gap in the saved history. Searching marks nothing read.
Terms used on this page:
- Local store (also called the archive): the database on this computer where tg keeps every message it has read or downloaded (the local store). Most searches read only this.
-
Query: what you search for. It can be plain words, or words with fields such as
from:anddate:. Your AI agent writes queries for you; the full language is in the query language reference. - Coverage: what a search could see — how many chats and messages were saved, and which chats were never downloaded or are behind.
Ask your agent to find messages answering a question and show the supporting messages. For example: “Find when the Helix daily export runs in the project chat, and check whether the schedule changed.” The agent can use archive discovery to find partial word matches and direct replies, then inspect those messages before answering. This needs downloaded history; it does not download a model.
For command-line control:
tg personal search messages 'What time does Helix export run? chat:990' --discover --jsonThis searches the local archive only. Chat, sender and date filters still apply. In MCP, pass
discover: true to the message-search tool. Each item's discovery says how much of the query it
matched: missing_terms names unmatched terms, and parent links a reply to its matching parent. A high score is not answer confidence: a question, proposal or old decision can rank first.
Read the evidence and check archive coverage before concluding that a fact is absent.
Without --discover, the usual strict search remains. Boolean syntax, quoted phrases, wildcards,
AST requests, --exact and --newest keep strict behavior. Discovery does not combine with legacy,
regular-expression search or --backend server. Meaning-based conversation search remains a
separate topic-search option.
Every search is under one group of commands, tg personal search. When you don't know where something was
written, start with search all.
| Task | Command |
|---|---|
| Search messages and notes in one answer | tg personal search all '<query>' |
Search Telegram messages only; without --discover, also on Telegram's server |
tg personal search messages '<query>' |
| Search mail | memo's: install @wirecat/cli-memo, then memo search mail '<query>'
|
| Search notes written in memo or imported from a folder | tg personal search notes '<query>' |
| Find a discussion by what it was about |
tg personal search conversations '<question>' (topic search) |
| Find a topic in a forum group by its title | tg personal search topics <chat> '<words>' |
| Count matches by chat, sender, day or hour | tg personal stats messages show '<query>' |
| Save a search and run it again later |
tg personal searches create, tg personal search messages --saved <name>
|
tg personal search all 'lease agreement' # messages and notes, best match firsttg personal search all 'lease' --only notes # notes onlytg personal search messages 'lease chat:"Book club"' # Telegram messages only, never mailtg personal search notes 'budget' --type internal # only notes written in memotg personal search conversations 'moving to the country' # conversations close in meaningtg personal search topics "Hiking" "gear" # topic titles in one forum groupsearch all says what each hit is — a message (msg:…) or a note (note:…). tg never searches mail:
tg personal search mail answers "mail is memo's: install @wirecat/cli-memo, then memo search mail …", and
search all --only mail is refused the same way. --type narrows search messages to text, voice or files
(text|voice|file) and search notes to notes written in memo or imported from a folder (internal|file).
When a field does not apply — chat:/from: for notes — search all skips that resource and explains why.
Mail and notes reach the store through memo: memo sync --mail and memo import. Without them,
search all searches messages only.
The rest of this page is about searching messages, tg personal search messages. It reads the local store;
ordinary search also asks Telegram (below); discovery searches only the archive, even with --backend both.
Start with a phrase and one chat. This example searches saved history without asking the messenger.
Your request:
Find the message saying “invoice paid” in Book club. Show the match and any gaps in the history.
Command:
tg personal search messages '"invoice paid" chat:"Book club"' --backend archive --jsonExample agent answer:
One matching message in saved history.
Person Message Alice Synthetic Invoice paid yesterday. History is incomplete: other matches may be missing. I can open this message and its surrounding conversation.
An empty result is not proof that the message never existed. Check the reported history gaps before broadening the search. The examples on this page are fictional.
Good search needs your chats downloaded. Telegram's search finds a message by its words even if tg never
fetched it, but everything else reads only the store: counting with stats, topic search, has:,
filename:, regex, presets, tags, and the ranking of word forms. Start by downloading every chat:
tg personal messages sync --all --background # the last 90 days of every chat, as a background jobtg personal store jobs show # how far it gotEach run fetches at most 1,000 messages per chat by default; repeat it to continue busy chats.
Add --since-time 365d to go further back, or fetch one chat with tg personal messages sync "Book club"
(download a chat's history). After that, tg personal serve keeps the
store current.
Every search says what it searched. In the terminal, when the store could hold more or nothing was found, one line says how many messages and chats were searched, how many chats were never fetched or are behind, and the command that fixes it:
searched 12,430 messages in 37 chats — 5 never fetched; `tg personal messages sync --all --background` fetches them
With --json, coverage carries the same: messages, chats, up to ten attention chats and next.
When nothing is found and next is set, run that command, or ask your agent to, before you decide the
message does not exist.
Put the query in single quotes, so the shell leaves its quotes and brackets alone. The names below are examples; use your own chats and people.
tg personal search messages invoicetg personal search messages '"invoice paid"' # words togethertg personal search messages 'cafe OR library'tg personal search messages '(cafe OR library) NOT loud'tg personal search messages 'invoic*' # every word that starts with "invoic"Words next to each other must all be in the message. Search includes word forms, according to
the store's language settings: piso can find pisos. Quotes keep words together and also allow
word forms. Use exact:piso or add --exact for words without an explicit field. An explicit
text: still matches forms. Case and accents are ignored. Typos are not corrected automatically.
tg personal search messages 'from:"Alice Synthetic" invoice'tg personal search messages 'from:("Alice Synthetic" OR "Bob Synthetic") library'tg personal search messages 'from:me date:7d' # what you wrote this weektg personal search messages 'chat:"Book club" library'tg personal search messages 'passport kind:private' # one-to-one chats onlykind: takes private, group, channel, saved (Saved Messages) and bot. topic: keeps to one
forum topic of a group; it needs that group in chat: or --chat. To search every account in the
store, add --source all.
tg personal search messages 'date:today'tg personal search messages 'library date:yesterday'tg personal search messages 'invoice date:7d' # from 7 days ago until now; also 30m, 2htg personal search messages 'invoice date:[2026-01-01 TO 2026-02-01}' --timezone Europe/Madridtoday, yesterday and calendar dates are days in your computer's time zone; --timezone picks
another. In a range, [ and ] include that day, { and } exclude it.
tg personal search messages 'has:file'tg personal search messages 'filename:*.pdf'tg personal search messages 'filename:*contract*' # part of the nametg personal search messages 'size>10MB'tg personal search messages 'mime:image' # any picture sent as a filetg personal search messages 'mime:"application/pdf"' # quote a full typetg personal search messages 'has:photo chat:"Book club"'tg personal search messages 'has:link AND "github.com"' # a link to a siteA file is found by its name, size and type even when the message has no text. filename: compares the
whole name, ignoring case and accents. Sizes use KB, MB and GB of 1,024. has: also takes attachment,
video, audio, voice, sticker, contact, location and poll. A link counts when it is in the
text or only in its preview card. To find a phone, an e-mail address, a link's site or a confirmed person
mentioned in the text, use phone:, email:, url:, domain: and mentions:; see
Mentions.
content: searches the text inside attachments: a PDF, a Word file, a scan. The text must first be
extracted into the store, by tg or by your agent. More about files: file attachments.
tg personal attachments extract --chat "Book club" --download --output-dir ./filestg personal search messages 'content:invoice'tg personal attachments list 'chat:"Book club" is:needs_text'tg personal attachments text set "Book club" 204 --text-file ./scan.txtattachments extract reads plain text, Word files and PDFs with a text layer on this computer.
--download requires --output-dir; without them extraction reads files already saved. With several
attachments in one message, choose one with --attachment, starting at 1.
Local extraction also reads BOM-marked UTF-16, high-confidence legacy encodings, ODT, ODS, XLSX, PPTX and EPUB without a model or extra installation. It keeps the order of sheets, slides and chapters and the saved cell values; it does not calculate formulas or read text inside images. Ambiguous encodings need inspection or conversion by your agent. The source files stay unchanged. ODT, ODS, XLSX, PPTX and EPUB are limited to 1,000 archive parts and 50 MiB expanded, with at most 10 MiB per text XML/HTML part; malformed or partial results are not indexed as complete text. A failed local read can be retried; text your agent wrote and text indexed earlier stay protected.
PDF extraction needs the optional package unpdf; Word needs mammoth, installed where tg is. For a
global npm install: npm install -g unpdf mammoth. A missing package is reported; your agent can supply
the text instead.
Photos and scans have no text layer. Your agent normally reads them with its own OCR or vision
tools and writes the text with attachments text set. attachments list is:needs_text gives it the
saved path, the message locator and the attachment number; it shows paths and text status, never the
text itself. Check the result with a content: search. A path on an MCP server does not move the file
to an agent on another computer: the agent needs access to the file. Such an agent can receive the
saved file's bytes in bounded parts and check their hash through
attachments show; delivering the file does not recognize or index text, so it still
writes the text back with attachments text set.
Many scans at once can go to a model service that you choose. Configure a vision model under
models.ocr and keep its key with the usual models text key set. Replace your-vision-model with
your model's name.
tg config set models.ocr.provider openaitg config set models.ocr.model your-vision-modeltg personal models text key set openaitg personal attachments extract --chat "Book club" --ocr --concurrency 4 --limit 100 --json--ocr sends images to that service; without it no model is called. Concurrency is 1–8, default 4; the
file limit is 1–500, default 100. Pass the returned cursor to continue a bounded scan. Scanned PDFs need
the optional unpdf and @napi-rs/canvas, with at most 20 pages per document; pages with a text layer
stay local. A repeat reuses the file hash and the model identity. Text your agent wrote and earlier
indexed text survive a failed or cancelled OCR run. Check the failed count and each file's status; a
provider rate limit stops later calls in that run. --offline cannot be combined with --ocr.
Files you already have. tg personal attachments extract --chat <chat> --from-dir ./files reads one folder,
without its subfolders. A file needs a unique original name or a complete set of downloader names. Do
not combine --from-dir with --download or --output-dir. tg personal messages download <chat> <id> --extract
extracts only the files this run downloaded; --all --extract does the same for the whole batch.
Extraction notices changed files by their hash and keeps text your agent wrote. Over MCP, a bounded
extraction returns a continuation cursor and metadata, without file text.
--from-dir protects known credential files and folders, the CLI's own folders and the message store.
Ordinary hidden working folders are allowed. MCP extraction downloads also require output_dir
outside protected locations. Local PDF text extraction has no fixed page-count limit or separate
30-second cutoff; command cancellation and file/text budgets still apply.
Picture search is off. It looks at pictures on this computer with a small model and never sends them anywhere. Turn it on for one profile, download the model once (about 155 MB, shared by all profiles), then read the pictures you have stored:
tg personal config set picture_search truetg personal images models download clip-b32tg personal images embed --chat "Book club" --limit 100images embed downloads each stored picture through Telegram, one at a time, and marks nothing read. It keeps only
a vector and the picture's hash. After that, a message search with words also returns a pictures list beside the
usual hits, never mixed into the text ranking. With the setting on but no model, a note on stderr says the pictures were not searched and why.
Write picture queries in English. The model knows no other language. An agent translates the query first
("собака на пляже" becomes "dog on a beach"). tg personal images models list shows which models are downloaded.
tg personal search messages 'preset:secret kind:saved' # something that looks like a password or tokentg personal search messages 'preset:card'A preset finds messages that look like a password, a login code, an API key, a card or IBAN number, a passport, a phone, an email or a link. It checks the shape only: it does not prove that a password works or a card is real. The full list is in the query language.
tg personal tags add work --chat "Book club"tg personal tags add work --contact "Bob Synthetic"tg personal tags list tag:work type:chattg personal search messages 'tag:work invoice'tg personal search messages 'invoice NOT tag:work'tg personal tags remove work --chat "Book club"A tag is your own label on a chat, a person, one message (--message <id> --chat <chat>)
or a task (--task <id-or-key>). It is kept
in the local store only and is never sent to Telegram. tag:work finds messages tagged work,
messages in a chat tagged work and messages from a person tagged work. A tag is 1–32 letters a–z,
digits and hyphens.
Groups and channels can be tagged automatically, from their title, username and description — no messages and no model:
tg personal metadata refresh --chat "Book club" # read the chat's description from Telegram; the chat is not changedtg personal metadata refresh --only-missing # every stored group and channel with no description read yettg personal tags auto --dry-run # what it would tag, without writingtg personal tags auto # write the automatic tagstg personal tags list sources:auto # only the automatic ones--sync-first reads current descriptions from Telegram before classifying; it is refused with
--dry-run, which uses the cached descriptions. One run handles at most 500 chats, 50 by default (--limit).
Automatic tags never touch yours: a rerun removes only stale automatic ones. Adding a tag the automatic run already gave makes it yours.
tg personal searches create meetings 'library OR cafe' --chat "Book club"tg personal search messages --saved meetingstg personal search messages --saved meetings 'date:today' # extra words are added with ANDtg personal stats messages show --saved meetings --by daytg personal searches listtg personal searches history --limit 10tg personal search messages --saved 42 # a row of the history, by its numbersearches create saves a query with its options and runs nothing; an existing name needs --replace.
Options you type with --saved replace the saved ones. The saved text is read again on every run, so
date:7d always means the last 7 days. searches show prints one, searches delete removes one.
Every search and count that succeeds is written to the history: the query and its options, never the
messages it found. The newest 1,000 runs are kept. --no-record keeps one run out of it; for MCP,
tg personal mcp --no-record or record set to false keeps the server's calls out. searches clear empties
the history and keeps the saved searches. This history is separate from the run records of tg personal runs.
Saved searches and the history live in the store that tg and max share: both see the same ones, and
delete or clear in one changes the other. Tags stay with their account.
tg personal stats messages show invoice # how many in each chattg personal stats messages show 'date:7d' --by sendertg personal stats messages show 'from:me' --by day --timezone Europe/Madridtg personal stats messages show --by hour # every stored messagestats messages show counts the messages search messages would find with the same query, each one once.
--by chat (the default) and --by sender put the largest first; --by day and --by hour go in
order. When some chats are not stored in full, the numbers are a lower bound, and stderr says how
many chats that is.
Telegram can search its own copy of your chats, including messages tg never fetched. Without --discover, tg asks
Telegram and the store by default in one run (--backend both). --backend server shows Telegram's results alone,
and --backend archive searches only the store.
tg personal search messages 'invoice' --backend bothtg personal search messages 'invoice chat:"Book club" from:Olga' --backend bothtg personal search messages 'invoice date:2026-09' --backend server --server-time 10sTelegram decides on its own what matches a word, and does not document it. So tg treats its answer as
candidates: it saves them in the store and runs your query over them with the store's own rules.
exact:, -word, quotes and the ranking mean the same as without --backend, and a message is never
listed twice. A message Telegram returned but your query rejects is not shown; it stays in the store.
Telegram gets only the words your query requires, one chat, a sender together with a chat, and dates.
OR makes up to three searches. Negations, wildcards, has:, tag: and presets are applied by tg
afterwards. A query without words does not ask Telegram. tg waits at most 5 seconds (--server-time,
up to 60 s) and takes up to 100 messages per search; a later answer is dropped. Nothing is marked read.
With --json, each message says where it came from (source: archive, server or both) and a
server block says what Telegram returned and what failed. --backend both never fails because of
Telegram: offline, without permission or with no words it answers from the store and says why.
--backend server refuses instead. The permission is messages.server-search; a read-only profile
answers from the store. stats messages show counts the store only: Telegram's counts follow its
own rules, not your query.
tg personal search messages 'invoice chat:"Book club"' --sync-first--sync-first downloads new messages before it searches, and marks nothing read. It takes at most 5
chats, 500 messages and 30 seconds; change these bounds with --max-chats, --max-messages and
--sync-time. When the download fails or stops early, you still get the results from the store, marked
with stale coverage and the details of the download.
--newest orders by time instead of by relevance, and --context 2 shows two messages before and
after each one found (2 in the terminal, 0 otherwise by default).
--thread shows the reply chain and the replies around each hit instead of its neighbours in time. It
follows the reply links saved in the store, and in messages context it replaces the neighbours in
time too. Defaults are 8 links from the hit, 50 messages, 65,536 bytes and one day either side; change
them with --thread-hops, --thread-messages, --thread-bytes and --thread-within. Without saved
links it falls back to neighbours in time; links that are out of date are marked and not followed.
messages context with --offline reads only saved messages.
At a terminal tg prints a readable transcript. --json returns one object with the messages and what
was searched; --jsonl streams the messages only, one per line. The fields of the answer are in the
query language reference.
An empty answer means "not in the store you searched", not "never sent". Check what is stored with
tg personal store status and fetch more with tg personal messages sync. With --json the answer says which chats were
searched and how complete they are, even when nothing matched. If tg asks for tg personal store migrate, the
word index is still being built; searches without words (has:file, date:today) already work.
Gaps in a chat's history. tg personal store gaps plan <chat> looks, on this computer, for gaps between the
stretches of history the store holds. Missing message ids and quiet periods alone do not prove missing
history; the edges of the saved history stay unknown. Check the plan, then run
tg personal store gaps repair <chat> --fingerprint <hash> to download the gaps. Defaults are five gaps, 500
messages and 30 seconds; --max-gaps, --limit, --repair-time, --page-size and --pause set the
bounds. A repeat repairs the remaining gaps and never deletes a message because Telegram did not return
it. Pages where several messages share one timestamp stay pending. --background runs it as a job that
store jobs show, store jobs list and store jobs cancel follow. Repair needs the store.gaps.repair
write permission and permission to read messages. Over MCP the same commands are found through
tg_tools_search and run through tg_read or tg_write; jobs belong to one profile.
Prepare topic search while you fetch. tg personal messages sync <chat> --catch-up builds that chat's
conversations and computes its local vectors right after fetching, for
topic search. It is off by default; the profile setting search_catch_up: true turns it
on, and --no-catch-up turns it off for one run. Bounds are
--catch-up-chunks 500 --catch-up-messages 10000 --catch-up-time 30s. It never downloads a model or
calls a remote service. A separate prepared result reports when preparation is incomplete; the
fetched history is saved either way. During a gap repair, catch-up shares the repair's time budget.
- Topic search: find a discussion by what it was about, when you do not remember its words.
- Query language: every field, operator, limit and the JSON answer.
- How search works: the technical page — the word index, the conversation graph, vectors and how results are ranked.