Polls

Bot API 9.6 — April 2026

Polls grew from “pick one of ten strings” into something closer to a form: quizzes can have more than one right answer, options can be added after the poll is live, and results can stay hidden until it closes.


Sending a poll

from pyrogram import enums

await app.send_poll(
    chat_id="me",
    question="Which layer are we on?",
    options=["226", "227", "228"],
    type=enums.PollType.QUIZ,
    correct_option_ids=[2],
    explanation="228 since July.",
    open_period=600,
)

Options are plain strings or InputPollOption objects. The question and the explanation accept a FormattedText if you want entities in them.

Multiple correct answers

correct_option_ids takes a list, so a quiz can accept several answers as right. The older single-valued correct_option_id still works and is folded into the list for you.

await app.send_poll(
    chat_id="me",
    question="Which of these are async?",
    options=["send_message", "rnd_id", "get_chat"],
    type=enums.PollType.QUIZ,
    correct_option_ids=[0, 2],
)

Poll behaviour flags

send_poll carries the switches that decide how the poll behaves once it is live:

  • allows_multiple_answers — a regular poll where voters pick several options

  • allows_revoting — a voter may change their mind

  • shuffle_options — each voter sees a different order

  • allow_adding_options — voters may append options of their own

  • hide_results_until_closes — nobody sees the tally until the poll ends

  • members_only — only subscribers may vote

  • country_codes — restrict voting by country

  • open_period / close_date — close it automatically

Voting and reading results

await app.vote_poll(chat_id, message_id, options=[0])
await app.retract_vote(chat_id, message_id)

poll = await app.get_poll_results(chat_id, message_id)

for option in poll.options:
    print(option.text, option.voter_count, f"{option.vote_percentage}%")

await app.stop_poll(chat_id, message_id)

get_poll_stats() returns who voted for what in a non-anonymous poll, and add_poll_option() appends an option to a live poll that allows it.

New poll votes reach a bot through on_poll().

Gotchas

  • correct_option_ids must be a list of int. It was a bytes field before layer 228 and a leftover bytes there fails deep inside serialisation, not at the call.

  • A quiz with no explanation is legal but wastes the one moment the voter is paying attention to why they were wrong.

  • send_poll accepts explanation_media, description and description_media, and currently drops them: nothing in this layer carries a poll description, and the solution media field is not wired up. They are accepted so code written against the Bot API shape does not break, not because they do anything.

  • Option media has the same limitation: InputPollOption takes a media, but only its text reaches the wire.