Open source · Apache 2.0
Open-source BI engine for building trusted analytics with AI agents
anfra lets data and product teams build and customize their own data apps with coding agents, on shared metrics defined once in a semantic layer
Install anfra following https://anfra.ai/docs/local-dev/installation, connect my data warehouse and build me a first app.anfra
QueryFunnel, 5 steps, 1-year window
FilterOrder status: completed, shipped
Semantic layer
Warehouse
“anfra is basically dbt for BI apps. The semantic layer is text in git, a coding agent writes the dashboard, the CLI checks it.” Read more (opens in a new tab)
“After about 30 minutes of setup, we had key dashboards and apps with built‑in interactions, out of the box, to track our usage.”
“AI dashboards always felt like artifacts. anfra feels like production: the semantic layer, the SDK and the visuals all working in unison. Really awesome!”
Custom data apps
Design your own data apps
Any layout, any interaction, one prompt. Every number still comes from your governed metrics.
Build a cash flow statement I can switch between quarters and years, add metrics to, and break down by entity.
Build a funnel tool where I can add, remove and reorder steps and see who converted or dropped off.
Build me a metric explorer where I can pick any measure, filter by a leaderboard row and compare to the previous period.
Give me an exploratory canvas where I can place charts anywhere and click a mark to drill into a linked chart.
EMEA is down since the spring promo ended. Check DE first
MTMai TranInspect
Built by AI. Checkable by anyone
Your agent writes the app, but it doesn’t make up the numbers. Each one comes from a metric your team defined, and anyone using the app can check it
- What a number means, who owns it and how it’s worked out
- Its lineage, back to the metrics, fields and tables underneath
- The rows and queries behind it, and a trust level for the whole app
Revenue after refunds, less what the goods sold cost us. Built on three other metrics.
Owned by Finance team- From the semantic layer
ecommerce - A defined metric
profit - Filters on defined fields
orders.ordered_at - Endorsed
not yet
revenue - refunds - cogs| Month | Revenue | Refunds | COGS | Profit |
|---|---|---|---|---|
| Jul 2026 | $352,980 | $10,590 | $211,790 | $130,600 |
| Aug 2026 | $376,130 | $11,280 | $225,680 | $139,170 |
| Sep 2026 | $403,510 | $12,110 | $242,110 | $149,290 |
- 1. UntracedDoesn’t come from the semantic layer: raw SQL, or hard-coded
- 2. Ad hocFrom the semantic layer, but part of it is written in the app
- 3. GovernedA defined metric; every field and filter is defined in the models
- 4. EndorsedGoverned, and its owner has endorsed it
- What a number means, who owns it and how it’s worked out
- Its lineage, back to the metrics, fields and tables underneath
- The rows and queries behind it, and a trust level for the whole app
AI analytics via MCP
Your metrics, in the AI chat you already use
Claude, ChatGPT or any MCP client answers from your governed metrics.
- Nothing new to learn
- No extra AI bill
- Trace any metric to its definition and lineage
Why did revenue drop in January?
anfra · query revenue by month and region
Revenue was in January, down from December. NA fell the most, to , down : about a third of the total drop (Worked out by the assistant). Every region fell by a similar share, so it is the dip after the December peak, not one market.
Revenue, December 2025 to January 2026
Revenue by month
metric revenue { label: 'Revenue' type: 'number' description: 'What customers paid for the items they ordered' definition: @aql sum(order_items.quantity * order_items.unit_price) ;;}datasets/ecommerce.dataset.aml- Finance data team
- Last changed in a reviewed pull request, 12 days ago
explore { dimensions { month: orders.created_at | month() } measures { revenue } filters { orders.created_at >= @2025-12-01 and orders.created_at < @2026-02-01 }}SELECT DATE_TRUNC('month', o.created_at) AS month, SUM(oi.quantity * oi.unit_price) AS revenueFROM ecommerce.orders AS oJOIN ecommerce.order_items AS oi ON oi.order_id = o.idWHERE o.created_at >= '2025-12-01' AND o.created_at < '2026-02-01'GROUP BY 1ORDER BY 1- Table:
ecommerce.Table:orders ecommerce.order_items - Model:
order_items - Metric:
revenue
| Month | Revenue |
|---|---|
| Dec 2025 | $323,250 |
| Jan 2026 | $240,990 |
- Nothing new to learn
- No extra AI bill
- Trace any metric to its definition and lineage
PluginsComing soon
Extend your BI with plugins
Turn on features across every app, or build your own with your agent.
Build with agents
One prompt, from warehouse to governed dashboard
Ask for a dashboard. Your agent turns your warehouse into models and metrics, then builds on top of them.
Install the CLI and the agent skills.
curl -fsSL https://anfra.ai/install.sh | bash then anfra agent setup
Add your credentials to
.anfra/data_sources.yml.anfra init my-project
It models your tables and defines the metrics, as files you can review.
cd my-project && claude then Use the anfra-build-data-app skill. Build a revenue overview with a region filter.
Every chart uses those metrics.
apps/revenue-overview.html
anfra serveruns it locally.anfra publishshares it with your team on anfra Cloud.anfra serve then anfra publish
Your prompt
Install anfra, connect it to my warehouse, then build a revenue dashboard with a region filter and publish itRevenue overview
- Revenue
- $3.93M +18.2%
- Orders
- 65,410
- Avg. order
- $60.08
Revenue by region
Monthly trend
Install
Get started with anfra
Install anfra following https://anfra.ai/docs/local-dev/installation, connect my data warehouse and build me a first app.
What you need
- macOS or Linuxx64 or ARM. Anywhere else, use the Docker image
- A coding agentClaude Code, Cursor, Codex or any agent that can run commands
- A warehouse loginPostgreSQL, Redshift, BigQuery, Snowflake, Databricks, ClickHouse, MySQL or a DuckDB file
How it works
The backend your agent builds data apps on
Everything a data app needs under it, open source, on your own warehouse.
AMQL
Built on an open, programmable semantic layer
AMQL, the semantic layer behind Holistics. Define metrics once as code, and every question compiles to SQL on your warehouse.
Programmable, not configured
Metrics build on other metrics, and one AQL expression handles period comparisons, percent of total, running totals and level of detail
Open as in yours
AML and AQL are plain-text files in your git, and every query compiles to SQL you can read
One model, every caller
Apps through the JS SDK and agents over MCP ask for metrics by name and get the same numbers
metric revenue { definition: @aql sum(order_items.amount) ;;}metric revenue_last_year { definition: @aql revenue | relative_period( orders.created_at, interval(-1 year) ) ;;}metric revenue_growth { definition: @aql safe_divide( revenue - revenue_last_year, revenue_last_year ) ;;}WITH monthly AS ( SELECT date_trunc('month', o.created_at) AS month, SUM(oi.amount) AS revenue FROM orders o JOIN order_items oi ON oi.order_id = o.id GROUP BY 1)SELECT curr.month, curr.revenue, prev.revenue AS revenue_last_year, (curr.revenue - prev.revenue) / NULLIF(prev.revenue, 0) AS revenue_growthFROM monthly currLEFT JOIN monthly prev ON prev.month = curr.month - INTERVAL '1 year'ORDER BY curr.monthCompare AMQL with other semantic layers (opens in a new tab)Try it in the playground (opens in a new tab)
JS SDK
Wire charts to metrics, not SQL
The JS SDK puts your semantic layer in the page. Query metrics as JS objects, bind them to any chart, and wire up filters and click-to-filter in a few lines.
the page asks for revenue by category, by name; anfra compiles the data team’s definitions to SQL
const app = Anfra.createApp({ title: 'Revenue explorer' })const byCategory = app.createQuery('byCategory', { dataset: 'ecommerce', aql: `explore { dimensions { category: products.category } measures { revenue: revenue } }`,})const country = app.createFilter('country', { dataset: 'ecommerce', field: 'customers.country',})app.mapControl(country, byCategory, { field: 'customers.country' })app.subscribe(() => { if (byCategory.state !== 'success') return chart.setOption({ dataset: { source: byCategory.result.rows } })})await app.execute()SELECT "products"."category" AS "category", SUM(("order_items"."quantity" * "products"."price")) AS "revenue"FROM public.order_items "order_items" LEFT JOIN public.orders "orders" ON "order_items"."order_id" = "orders"."id" LEFT JOIN public.products "products" ON "order_items"."product_id" = "products"."id" LEFT JOIN public.customers "customers" ON "orders"."customer_id" = "customers"."id"WHERE "customers"."country" = 'Vietnam'GROUP BY 1- Services$91,500
- Software$68,234
- Hardware$23,747
- Parts$1,774
- No SQL in the pageThe page asks for revenue by name
- Filters travel as dataThe page sends a filter beside the query, as data, and anfra builds the SQL from it
- SandboxedEach app runs in a sandboxed frame and reaches data only through its host
- Your server decidesLog, change or refuse any call the page makes
Git workflow
BI as code, in your own repo
Every connection, model, metric and app is a plain file in git.
- Reviewed in pull requests
- Full history, easy rollback
- Your agent checks its work before it ships
# your warehouse; this file stays out of gitdata_sources: warehouse: type: postgresql connection: host: db.acme.internal port: 5432 user: anfra password: •••••••• dbname: analyticsModel orders { type: 'table' label: 'Orders' data_source_name: 'warehouse' table_name: 'public.orders' dimension id { label: 'ID' type: 'number' primary_key: true } dimension user_id { label: 'User ID' type: 'number' } dimension amount { label: 'Amount' type: 'number' } dimension created_at { label: 'Created at' type: 'datetime' }}Model users { type: 'table' label: 'Users' data_source_name: 'warehouse' table_name: 'public.users' dimension id { label: 'ID' type: 'number' primary_key: true } dimension region { label: 'Region' type: 'text' }}Dataset sales { data_source_name: 'warehouse' models: [orders, users] relationships: [ relationship(orders.user_id > users.id, true) ] metric revenue { label: 'Revenue' definition: @aql sum(orders.amount) ;; } metric order_count { label: 'Orders' definition: @aql count(orders.id) ;; } metric aov { label: 'Avg. order' definition: @aql revenue / order_count ;; }}<h1>Revenue overview</h1><div id="kpis"></div><div id="by-region"></div><script> const app = Anfra.createApp({ title: 'Revenue overview' }) const byRegion = app.createQuery('byRegion', { dataset: 'sales', aql: `explore { dimensions { region: users.region } measures { revenue: revenue } }`, })</script># Agent instructionsThis is an anfra project. Metrics live indatasets/; apps query them by name and neverwrite their own SQL.Before you finish a change, run:- anfra validate- anfra query, to check the numbers- anfra serve, to open the appDataset sales { data_source_name: 'warehouse' models: [orders, users] relationships: [ relationship(orders.user_id > users.id, true) ] metric revenue { label: 'Revenue' definition: @aql sum(orders.amount) ;; } metric order_count { label: 'Orders' definition: @aql count(orders.id) ;; } metric aov { label: 'Avg. order' definition: @aql revenue / order_count ;; }}anfra validatePassedReviewApproved by the data team- Reviewed in pull requests
- Full history, easy rollback
- Your agent checks its work before it ships
Use cases
What you can build with anfra
Your coding agent builds the apps, as many as your team needs, in your own code and on your own warehouse
Your own BI tool
The dashboards and apps your team asks for. The layout and the charts are yours, and the metrics are your data team’s.
See live demos (opens in a new tab)A backend for your AI data agent
The agent searches your datasets and queries metrics by name, so its answers come from your definitions, not SQL it guessed
Agent skills on GitHub (opens in a new tab)Embedded analytics
Apps inside your own product through the JS SDK, under your own login and look
Host example on GitHub (opens in a new tab)
Deployment
Run it your way
Start on your laptop, then run the same project on your own server, or publish it to anfra Cloud
anfra Cloud
anfra, hosted, with batteries included. The project you build locally publishes to it unchanged, and your team signs in to use it.
About anfra Cloud- Hosted for youNo server to run yourself
- Users and rolesAdmins decide who builds and who views. SSO is on the way.
- PermissionsOn the wayRow-level and viewer-level, so access to data follows each user
- Sharing and auditOn the waySharing links and public links, with an audit trail of who did what
Community
Build it with us
Ask questions, share what you’ve built and tell us what anfra should do next.
- ForumQuestions, answers and show-and-tell (opens in a new tab)
- GitHubReport a bug, ask for a feature or star the repo (opens in a new tab)
- Release notesWhat’s new in anfra and anfra Cloud
- Coming soonSlackChat with the team and other builders
FAQ
More answers in the README (opens in a new tab) and the docs (opens in a new tab)
anfra is the open-source framework you build BI with: the AMQL semantic layer, the query engine, the JS SDK and the MCP server. You run it yourself, on your machine or your own infrastructure.
anfra Cloud is anfra hosted for you, with what a whole team needs on top: sign-in, invitations and roles today, with SSO, row-level permissions, sharing and an audit trail on the way. It’s in beta in the US region, at us.anfra.ai, and projects you build locally run on it unchanged.
Claude can write the SQL, but each time it writes it again, so revenue in one app can differ from revenue in the next. With anfra, metrics are defined once in AMQL, and apps and agents ask for them by name, so anfra writes the same SQL for every caller.
You can also check a number: anfra shows the metric’s definition and the SQL that ran, instead of leaving you to read the code Claude generated. And the data is read live through your model whenever an app is opened, not saved as a snapshot.
anfra serve runs your apps on your machine while you and your agent build and check them, but it has no logins or per-user permissions. To give your team access, publish the project to anfra Cloud (in beta), where they sign in and each person has a role. Or run anfra behind your own SSO proxy, or embed the apps in your own product under your own login.
The Apache License 2.0. You can use it, change it and build it into your own products, commercial or not.
dbt transforms your data into tables. anfra reads those tables and defines the metrics on top, so the two work together.
Like Cube and dbt’s semantic layer, AMQL defines metrics as code. It is built for the analysis people ask for, such as metrics built from metrics, period comparisons, percent of total and level of detail, so agents query them instead of writing that SQL each time. Support for reading external semantic layers is planned.
PostgreSQL, Amazon Redshift, Google BigQuery, Snowflake, Databricks, ClickHouse and MySQL, plus DuckDB when you run anfra yourself. On anfra Cloud, you’ll use MotherDuck for DuckDB.
No. anfra doesn’t include an AI model. You bring your own agent, such as Claude Code, Cursor or Codex, and it works with anfra through the agent skills, the CLI and MCP.
Yes. anfra runs from a single binary or its Docker image, on your machine or your own server. anfra serve has no logins or per-user permissions, so keep it where everyone who can reach it may see the data, or put it behind your own SSO proxy. anfra Cloud (in beta) adds logins and roles, and a paid self-hosted edition with the same features is planned.