Intro
While the .app() method is a great quick start for exploring data, building custom apps with querychat unlocks the full power of integrating natural language data exploration with custom visualizations, layouts, and interactivity.
querychat lets users ask questions of their data in plain language — filtering, sorting, summarizing, joining across tables, and creating visualizations — all without needing to write SQL or navigate complex filter UIs. You can use it as the primary exploration interface in a standalone app, or embed it alongside curated views in an existing dashboard to let users go deeper than the views you designed.
This is especially valuable when:
- Your data has many columns and building a UI for all possible filters would be overwhelming
- Users want to explore ad-hoc combinations of filters that you didn’t anticipate
- You have multiple related tables that users may want to query and join
- You want to make data exploration more accessible to non-technical users
General pattern
Regardless of framework, building a custom querychat app follows the same pattern:
- Initialize a
QueryChatinstance with your data - Place the chat UI in your layout (
.sidebar()or.ui()) - Access query state (
.df(),.sql(),.title()) to build reactive outputs - Connect visualizations and tables to the filtered data
Each framework has its own way of handling reactivity and state updates, but the core querychat API is largely consistent across all of them.
Choosing a framework
querychat supports four Python web frameworks. Choose based on your needs:
| Framework | Best for | Guide |
|---|---|---|
| Shiny | Complex reactive apps, fine-grained control, Posit Connect deployment | Build with Shiny |
| Streamlit | Quick prototypes, simple apps, script-based development | Build with Streamlit |
| Gradio | ML demos, easy sharing, Hugging Face Spaces | Build with Gradio |
| Dash | Production enterprise apps, complex callbacks | Build with Dash |
API differences
While querychat provides a consistent interface across frameworks, each implementation follows its framework’s idioms for state management. The key difference is how you access query state:
| Framework | State access | Example |
|---|---|---|
| Shiny | Reactive callables | qc.df()(), qc.sql()(), qc.title()() |
| Streamlit | Direct methods (session state) | qc.df(), qc.sql(), qc.title() |
| Gradio | Methods with state dict | qc.df(state), qc.sql(state), qc.title(state) |
| Dash | Methods with state dict | qc.df(state), qc.sql(state), qc.title(state) |
Why the difference?
- Shiny uses reactive programming where values are wrapped in reactive containers. You call
.df()to get the reactive, then call it again (or use it in a reactive context) to get the value. - Streamlit manages state internally via
st.session_state, so methods can access it directly without arguments. - Gradio and Dash use callback-based architectures where state is passed explicitly to callback functions, so you pass the state dict to accessors.
Each approach matches how developers typically work in that framework, making querychat feel native regardless of which framework you choose.