query language - WireCatLabs/tg-cli GitHub Wiki

Search query language

If you use an AI agent with tg, you do not need to learn this language: describe what you want in plain words and the agent writes the query. This page is for people who type searches themselves, and for anyone who wants every filter: exact operators, every field, presets, date rules and limits.

It is the reference for the queries of tg personal search messages, tg personal search all, tg personal stats messages show, saved searches, and the --filter of topic search. For everyday examples start with message search.

Terms used on this page:

  • Query: the text you search for, for example invoice from:me date:7d.
  • Field: a name and a colon that limit one part of the query to one property of a message, such as from: (the sender) or date: (when it was sent). Words without a field search the message text.
  • Operator: a word or sign that joins conditions, such as AND, OR and NOT.
  • Word forms: the same word with different endings. piso and pisos are forms of one word.

The language is a strict profile of Apache Lucene's query syntax: words, phrases, AND/OR/NOT, groups, fields, ranges, bounded wildcards and regular expressions. "Strict" means that anything it does not support is an error, never silently ignored. The full reference (in Russian) has the generated tables of fields, operators, presets and limits, and executable examples; the technical specification describes the grammar and the compiler.

For mail, chat: and --chat select an email thread by id or subject; from: selects a correspondent. Mail does not have kind: or topic:.

What it can do

You want Write
messages with all of these words, in any form invoice paid
these words together, in this order "invoice paid"
one word or another, without a third (cafe OR library) NOT loud
only this exact form of a word exact:piso, or --exact for every word without a field
words that start with something invo*
a sender, a chat, a kind of chat from:me, chat:"Book club", kind:private
a period date:7d, date:[2026-01-01 TO 2026-02-01}
files by name, type or size filename:*.pdf, mime:image, size>10MB
text that looks like a password, card or phone preset:secret, preset:card
your own labels tag:work
a pattern text:/pass(port)?/

Words and phrases without a field match word forms. --exact selects exact forms for words without a field; an explicit text: still matches forms. The store's language settings decide which forms match (word forms).

Operators

Operator Example Meaning
words invoice paid both words
phrase "invoice paid" the words in this order
AND, && alpha AND beta both
OR, || alpha OR beta either
NOT, !, - alpha NOT beta the first without the second
+ +alpha OR beta alpha required, beta optional
group (alpha OR beta) gamma brackets set the order
field group from:(alice OR bob) the field applies to each value
range date:[2026-01-01 TO 2026-02-01} [ ] include, { } exclude, * open
comparison size>10MB, date>=7d, date:>=7d an open range; the colon form is the same
wildcard invo*, te?t * any characters, ? one
regex text:/pass(port)?/ a bounded Lucene regular expression

alpha OR beta gamma means (alpha OR beta) AND gamma; alpha OR beta AND gamma means alpha OR (beta AND gamma). Use brackets for clarity. Lowercase and, or, not are plain words. A query with only NOT finds nothing: give a positive condition, for example kind:group NOT preset:secret. Fuzzy ~, proximity, boosts and intervals are refused with an error, not ignored. Typos are not corrected: to catch several endings, use a wildcard such as invo*.

Fields

Field Finds Example
text words of the message (the default field) text:invoice
exact the exact word or phrase form exact:piso, exact:"invoice paid"
body the whole original text, case-sensitive body:/.*invoice.*/
from the sender, by name, @username or id; me is you from:"Alice Synthetic"
chat the chat, by title, @username or id chat:"Book club"
date when it was sent date:today, date:7d, date:[2026-01-01 TO 2026-02-01}
kind the kind of chat: private, group, channel, saved, bot, service, unknown kind:private
has attachment, link, file, photo, image, video, audio, voice, sticker, contact, location, poll has:file
topic one forum topic; needs one chat chat:"Book club" AND topic:42
in which accounts: a provider or bots in:bots
preset text shaped like a secret or a contact detail preset:secret
content indexed attachment text content:invoice
filename an attached file's whole name filename:*.pdf
mime an attached file's type; a value without / matches the first part mime:image
size an attached file's size, in bytes or KB/MB/GB of 1,024 size>10MB
tag your own local tag on the message, its chat or its sender tag:work
phone a phone number found in the text, however it is written phone:"+34 600 000 000"
email an e-mail address found in the text email:"[email protected]"
url a link found in the text, without tracking parameters url:"https://example.com/a"
domain the site of a link or an address found in the text domain:example.com
mentions a person, organization or document that a found mention is linked to mentions:"person:12"

Field names are case-sensitive. An unknown field, value or combination is an error, never an empty answer and never plain text. A name that the store does not know is not looked up on Telegram.

kind:bot selects a chat with a bot; in:bots selects the archives of tg personal bot accounts. topic: needs exactly one chat in chat: or --chat, since topic numbers repeat across groups. filename, mime and size match a message when at least one of its files matches. / starts a regular expression, so quote a full type: mime:"application/pdf".

Mentions

tg finds phones, e-mail addresses, links, documents and people's names in stored messages. Use these fields to find the messages that hold them. The finds themselves are listed by tg mentions list.

tg personal search messages 'phone:"+34 600 000 000"'
tg personal search messages 'email:"[email protected]" OR domain:example.com'
tg personal search messages 'mentions:"person:12"'

Quote a value that has + or @, since both are operators. A phone is found however it is written: +34 600 000 000 and 600000000 are one phone, and a number without + is read with the countries of the phone_countries setting. domain: treats docs.example.com as example.com. mentions: takes the reference of a person, an organization or a document, and finds the messages whose mentions you linked to it with tg mentions confirm.

Presets

A preset finds text by its shape. Use it to find a password, a code or a card number that someone sent.

Preset A candidate is
password a password label followed by a value
code a verification-code label and 4–8 digits
api-key an API-key label and a value
secret a password, secret, token or API-key label and a value
card 13–19 digits with optional spaces or hyphens
bank an IBAN-shaped value
passport a labelled passport value or a Russian 4+6 digit shape
phone a plus-prefixed international phone shape
email an email-address shape
telegram-link a t.me or telegram.me link
url an HTTP(S) link
contact a contact attachment, or an email or phone
location a location attachment or a geo: link

A preset reports a candidate by its shape. It does not verify a password, a card or a document, and it can match something harmless. Do not delete or forward messages automatically on its word.

Dates

--timezone takes an IANA zone such as Europe/Madrid; without it, the computer's zone is used and returned in the answer. A date without a time is a whole calendar day. An inclusive upper day includes that whole day, an exclusive one excludes it; a day when clocks change can last 23 or 25 hours.

date:today and date:yesterday are calendar days. date:7d means from 7 days ago until now (also 30m, 2h); date>=7d and date:[30d TO 7d} work in comparisons and ranges, counted from the moment the query runs. An exact time is quoted, with seconds and an offset: date>="2026-01-01T10:00:00+02:00".

Words, wildcards and regular expressions

Text is folded before it is indexed and searched: lower case, without accents. A side effect: some different words become the same, such as año and ano. text: regular expressions and wildcards are folded the same way.

text:/pay/ matches the whole word pay, not payment. body:/pay/ matches only a message whose entire text is pay, case-sensitive; to find it anywhere use body:/.*pay.*/. This is Lucene's regular-expression syntax, without JavaScript lookaround, backreferences, anchors or flags.

On a large store a short prefix such as a* can expand to more than 10,000 words and is refused; lengthen it. Long queries, deep nesting, large patterns and slow scans are refused with query_limit, not cut short: narrow the chat, the dates or the pattern.

The answer

--json returns { items, page: { limit, has_more }, next?, missing?, meta }, even when nothing matched. --jsonl streams the items only.

  • items: the hits, best first (or newest first with --newest), each with its ref (msg:…), timestamp, sender_name, excerpt and matched_in.
  • meta.accounts: which accounts were searched.
  • missing: what the answer could not see, each with the step that fills it. coverage means some chats in scope were never fetched or the fetch before searching stopped early (step messages.sync); text means the word index is still being built (step store.migrate, tg personal store migrate).
  • next: the steps to run. page.next repeats the query for the next page.
  • page.has_more is about the page, not about whether Telegram holds more.

An error carries the position of the problem in the query and a hint.

Over MCP

When an agent searches through tg's MCP server, tg_read (command: "search messages") takes the query as text, or as a versioned syntax tree in ast (not both), and timezone for the calendar zone. chat takes an id or a stored name; source, newest, context and limit work as the command options do; saved runs a saved search. thread, thread_hops, thread_messages, thread_bytes, thread_within and sync_first match the options --thread… and --sync-first; sync_first is offered only with messages.sync-first: allow. The answer has the same fields as --json. tg_read (command: "stats messages show") counts the same queries. The query history follows the server: tg personal mcp --no-record, or record set to false, keeps its calls out.

Next

  • Message search: everyday searches, saved searches and counting.
  • Topic search: find a discussion by its meaning when you do not know its words.