> ## Documentation Index
> Fetch the complete documentation index at: https://www.plain.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reading threads

> Fetch thread context for your agent to act on.

Most agents need to read the contents of a thread before they decide what to do, whether that's classifying it, adding a label, summarising it into a note, or generating a reply. This page covers how to read thread context via the GraphQL API.

## Get a thread

The `thread` query returns a thread by ID along with its metadata. It throws if the thread doesn't exist. This operation requires the `thread:read` permission.

```ts theme={null}
const thread = await plain.query.thread({
  threadId: "th_01H8H46YPB2S4MAJM382FG9423",
});

console.log(thread.title);
console.log(thread.status);
console.log(thread.priority);
```

Most thread fields are scalars on the model. Related objects (`customer`, `assignee`, `labels`, ...) are lazy-loaded, so accessing them triggers a separate API call. See the [GraphQL SDK](/graphql/sdk) for more on how this works.

## Get the thread content as LLM text

Each entry in a thread's timeline (an inbound message, an outbound reply, a note, an assignment change, a label change) exposes an `llmText` field. This is a plain-text rendering of the entry shaped for feeding into a language model.

To get the full thread as LLM-ready text, paginate through `timelineEntries` and concatenate `llmText`:

```ts theme={null}
async function getThreadAsLlmText(threadId: string): Promise<string> {
  const thread = await plain.query.thread({ threadId });
  const parts: string[] = [];

  let page = await thread.timelineEntries({ first: 50 });
  while (true) {
    for (const entry of page.nodes) {
      if (entry.llmText) parts.push(entry.llmText);
    }

    const next = await page.fetchNext();
    if (!next) break;
    page = next;
  }

  return parts.join("\n\n");
}
```

The result is a single string with every meaningful event in the thread in chronological order, ready to drop into a prompt:

```ts theme={null}
const context = await getThreadAsLlmText(thread.id);

const reply = await myModel.generate({
  system: "You are a customer support agent.",
  prompt: `Conversation so far:\n\n${context}\n\nWrite the next reply.`,
});
```

<Note>
  `llmText` is `null` for entry types where there's nothing meaningful to render. Skip those entries.
</Note>

## Reading individual fields

If you don't need the whole thread, you can read specific data directly. Some common patterns:

* **The customer**: use `thread.customer`, or `plain.query.customer({ customerId })`. Useful for looking up subscription tier, external IDs, or anything else attached to the customer.
* **Custom thread fields**: read structured data attached to the thread. See [thread fields](/graphql/threads/thread-fields).
* **The triggering message**: for events like [`thread.email_received`](/webhooks/thread-email-received) or [`thread.chat_received`](/webhooks/thread-chat-received), the message itself is on the webhook payload. No extra API call needed if that's all you want.
