Receiving Inline Images
When a user pastes an image into the Teams compose box, the image does not arrive inside activity.text. Teams sends it as an attachment on the inbound message activity, and ctx.files lists it alongside the files the user attached, as a lazy handle you can download from in the same way as an attached file.
Receiving inline images through ctx.files is planned for the next SDK release, version 2.2.0.
List pasted images
Each listed file has an attachment_style that says how it was attached: "inlineImage" for an image pasted into the message, or "file" for a file attachment. Pass the style as a filter to list() or first() to get only pasted images:
@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]) -> None:
pasted_images = await ctx.files.list(attachment_style="inlineImage")
if not pasted_images:
await ctx.reply("Paste an image into your message and I will read it.")
return
await ctx.reply(f"You pasted {len(pasted_images)} image(s).")
Without a filter, both styles are returned in the order they arrived. Like any listing, this makes no network requests.
Download a pasted image
A pasted image downloads through the same methods as an attached file, described in Receiving Files:
import base64
image = await ctx.files.first(attachment_style="inlineImage")
if image:
downloaded = await image.download()
image_base64 = base64.b64encode(downloaded.bytes).decode("ascii")
# Pass downloaded.bytes or image_base64, with downloaded.content_type, to your image-processing or model client.
How pasted images are named and typed
A pasted image has no file name, so the SDK names it image-<attachmentId>. Its listed metadata comes from a format hint Teams sends with the message:
namegets an extension only for PNG, JPEG, GIF, or WebP, andextensionis taken from it, so it is absent when the name has none.content_typeis the matching MIME type for those four formats, otherwiseapplication/octet-stream.unique_idis always unset, because a pasted image is not stored in OneDrive or SharePoint.content_urlis the image's Bot API location.
On the downloaded file, content_type is detected from the bytes, and the extension of filename follows it, so the downloaded file name can differ from the listed name. If the bytes are not PNG, JPEG, GIF, or WebP, the listed type is used.
Teams saves a pasted GIF as a static PNG. It is listed as .gif and image/gif but downloads as a PNG.
Formats that cannot be downloaded
Some image formats, such as BMP and AVIF, cannot be converted by the Teams service, so Teams shows a placeholder, and they cannot be downloaded. Downloading one of these fails with InlineImageTransformationError. The same image sent as a file attachment downloads normally, so asking the user to resend it that way is the recovery:
from microsoft_teams.apps import InlineImageTransformationError
try:
downloaded = await image.download()
# ...
except InlineImageTransformationError:
await ctx.reply("That pasted image cannot be downloaded. Try sending it as a file attachment instead.")
If the Bot API refuses the token presented for the image, the download fails with InlineImageAccessError with a 401 or 403 status. Both errors share the common base error described in Handle errors.
How the image is read
A pasted image is read from the Bot API with the same token the app uses to reply in that conversation, not through Microsoft Graph. This works for traditional bots and for agentic users, which read as their own identity. Inline images list and download in personal chats, group chats, and channels. Outside personal chats, only messages that @mention the agent reach it.
The token is sent only to a URL under the activity's serviceUrl. An inline image hosted anywhere else is skipped by list() with a warning in the log.
Access the raw attachments
An inbound message with a pasted image typically contains two related attachments:
{
"attachments": [
{
"contentType": "image/png",
"contentUrl": "https://.../v3/attachments/{id}/views/original"
},
{
"contentType": "text/html",
"content": "<p><img src=\"https://.../objects/{id}/views/imgo\" ...></p>"
}
]
}
The listed file's raw holds only the image/* attachment. The text/html attachment, which preserves where the image appeared among the message's text and other content, stays in ctx.activity.attachments.