Skip to content

Chainlit Cheat Sheet

Overview

Chainlit is an open-source Python framework for building conversational AI applications. It provides a polished chat UI with streaming responses, multi-step reasoning visualization, file uploads, human-in-the-loop feedback, authentication, and data persistence. Chainlit integrates with LangChain, LlamaIndex, and any Python-based LLM framework to turn backend AI logic into a production-ready chat application in minutes.

The framework focuses on observability and user experience: it visualizes intermediate chain steps, supports multi-modal messages (text, images, files, audio), provides user authentication with OAuth, and includes a feedback system for collecting user ratings. Chainlit apps can be deployed as standalone web services or embedded into existing applications.

Installation

pip install chainlit

# Create starter app
chainlit init

# Run app
chainlit run app.py
# Opens at http://localhost:8000

Core Usage

Basic Chat App

# app.py
import chainlit as cl

@cl.on_message
async def main(message: cl.Message):
    response = f"You said: {message.content}"
    await cl.Message(content=response).send()

OpenAI Integration

import chainlit as cl
from openai import AsyncOpenAI

client = AsyncOpenAI()

@cl.on_chat_start
async def start():
    cl.user_session.set("history", [
        {"role": "system", "content": "You are a helpful assistant."}
    ])

@cl.on_message
async def main(message: cl.Message):
    history = cl.user_session.get("history")
    history.append({"role": "user", "content": message.content})

    msg = cl.Message(content="")
    await msg.send()

    stream = await client.chat.completions.create(
        model="gpt-4o",
        messages=history,
        stream=True
    )

    async for chunk in stream:
        if chunk.choices[0].delta.content:
            await msg.stream_token(chunk.choices[0].delta.content)

    await msg.update()
    history.append({"role": "assistant", "content": msg.content})
    cl.user_session.set("history", history)

LangChain Integration

import chainlit as cl
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage
from langchain.schema.runnable.config import RunnableConfig

@cl.on_chat_start
async def start():
    model = ChatOpenAI(model="gpt-4o", streaming=True)
    cl.user_session.set("model", model)

@cl.on_message
async def main(message: cl.Message):
    model = cl.user_session.get("model")
    msg = cl.Message(content="")

    async for chunk in model.astream(
        [SystemMessage(content="You are helpful."), HumanMessage(content=message.content)],
        config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()])
    ):
        await msg.stream_token(chunk.content)

    await msg.send()

Message Types

import chainlit as cl

@cl.on_message
async def main(message: cl.Message):
    # Text message
    await cl.Message(content="Hello!").send()

    # Message with author
    await cl.Message(content="Analyzing...", author="Agent").send()

    # Message with elements (attachments)
    elements = [
        cl.Text(name="result", content="Detailed output here", display="inline"),
        cl.Image(name="chart", path="chart.png", display="inline"),
        cl.File(name="report", path="report.pdf", display="inline"),
        cl.Pdf(name="document", path="doc.pdf", display="side"),
    ]
    await cl.Message(content="Here are the results:", elements=elements).send()

    # Code block
    await cl.Message(content="```python\nprint('hello')\n```").send()

Steps and Chains

import chainlit as cl

@cl.on_message
async def main(message: cl.Message):
    # Show intermediate steps
    async with cl.Step(name="Retrieval") as step:
        step.input = message.content
        # Simulate retrieval
        step.output = "Found 5 relevant documents"

    async with cl.Step(name="Generation") as step:
        step.input = "Context + Question"
        msg = cl.Message(content="")
        await msg.send()
        # Stream tokens
        for word in "The answer based on context is...".split():
            await msg.stream_token(word + " ")
        await msg.update()
        step.output = msg.content

User Input and Actions

import chainlit as cl

@cl.on_chat_start
async def start():
    # Ask user for input
    res = await cl.AskUserMessage(
        content="What topic would you like to explore?",
        timeout=60
    ).send()

    if res:
        await cl.Message(content=f"Great! Let's discuss {res['output']}").send()

@cl.on_message
async def main(message: cl.Message):
    # Action buttons
    actions = [
        cl.Action(name="summarize", value="summarize", label="Summarize"),
        cl.Action(name="expand", value="expand", label="Expand"),
        cl.Action(name="translate", value="translate", label="Translate"),
    ]
    await cl.Message(content="What would you like to do?", actions=actions).send()

@cl.action_callback("summarize")
async def on_summarize(action: cl.Action):
    await cl.Message(content="Summarizing...").send()

@cl.action_callback("expand")
async def on_expand(action: cl.Action):
    await cl.Message(content="Expanding...").send()

File Handling

import chainlit as cl

@cl.on_message
async def main(message: cl.Message):
    # Handle uploaded files
    if message.elements:
        for element in message.elements:
            if isinstance(element, cl.File):
                # Process file
                content = element.path  # Local path to uploaded file
                await cl.Message(content=f"Received file: {element.name}").send()

    # Ask for file upload
    files = await cl.AskFileMessage(
        content="Upload a document to analyze",
        accept=["application/pdf", "text/plain"],
        max_size_mb=20,
        max_files=5
    ).send()

    if files:
        for f in files:
            await cl.Message(content=f"Processing {f.name} ({f.size} bytes)").send()

Configuration

.chainlit/config.toml

[project]
name = "My AI Assistant"
enable_telemetry = false

[features]
prompt_playground = true
multi_modal = true

[features.spontaneous_file_upload]
enabled = true
accept = ["*/*"]
max_files = 10
max_size_mb = 500

[UI]
name = "AI Assistant"
description = "A helpful AI assistant"
default_collapse_content = true
default_expand_messages = false
hide_cot = false

[UI.theme]
layout = "wide"

[UI.theme.light]
background = "#FFFFFF"
paper = "#F5F5F5"

[UI.theme.dark]
background = "#1A1A2E"
paper = "#16213E"

Authentication

# OAuth (Google, GitHub, etc.)
# .chainlit/config.toml
# [project]
# enable_auth = true

import chainlit as cl

@cl.oauth_callback
def oauth_callback(provider_id, token, raw_user_data, default_user):
    return default_user

@cl.on_chat_start
async def start():
    user = cl.user_session.get("user")
    await cl.Message(content=f"Welcome, {user.identifier}!").send()

Environment Variables

CHAINLIT_AUTH_SECRET=your-secret-key
OPENAI_API_KEY=sk-...
CHAINLIT_PORT=8000
CHAINLIT_HOST=0.0.0.0

Advanced Usage

Custom Data Layer (Persistence)

import chainlit as cl
from chainlit.data import BaseDataLayer

class CustomDataLayer(BaseDataLayer):
    async def create_user(self, user):
        # Store user in database
        pass

    async def get_thread(self, thread_id):
        # Retrieve conversation
        pass

    async def create_step(self, step_dict):
        # Store step
        pass

# Set in app startup
cl.data_layer = CustomDataLayer()

Human Feedback

@cl.on_message
async def main(message: cl.Message):
    msg = cl.Message(content="Here is the answer...")
    await msg.send()

    # Ask for feedback
    res = await cl.AskActionMessage(
        content="Was this helpful?",
        actions=[
            cl.Action(name="thumbs_up", value="positive", label="Yes"),
            cl.Action(name="thumbs_down", value="negative", label="No"),
        ]
    ).send()

    if res and res.get("value") == "negative":
        await cl.Message(content="Sorry! Let me try again...").send()

Deployment

# Docker
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["chainlit", "run", "app.py", "--host", "0.0.0.0", "--port", "8000"]

Troubleshooting

IssueSolution
Port 8000 in useUse chainlit run app.py --port 8001
Streaming not workingUse await msg.stream_token() then await msg.update()
Auth not workingSet CHAINLIT_AUTH_SECRET env var
Files not uploadingCheck max_size_mb in config, verify multi_modal=true
Steps not showingWrap in async with cl.Step() context manager
Session state lostUse cl.user_session.set/get for persistence
Hot reload brokenSave file, ensure --watch flag (default)
CORS errorsSet allowed origins in config
# Debug mode
chainlit run app.py --debug

# Watch mode (auto-reload)
chainlit run app.py --watch

# Custom host/port
chainlit run app.py --host 0.0.0.0 --port 8080