# Data API Overview Source: https://docs.dune.com/api-reference/api-overview Access Dune's onchain data programmatically via REST endpoints. The Data API provides programmatic access to execute queries, manage data, track usage, and integrate Dune's onchain analytics into your applications, workflows, and environments. For complete endpoint documentation, authentication guides, and code examples, visit the **[API Reference](/api-reference/overview/introduction)** tab. ## Available Endpoints Execute saved queries and retrieve results in various formats Create, update, and manage your Dune queries programmatically Upload and manage custom datasets as queryable tables Create and refresh materialized views for improved performance Execute and monitor query pipelines with coordinated workflows Monitor API usage and credit consumption ## Full API Reference Complete endpoint documentation with authentication, rate limits, SDKs, and detailed code examples → # Analyze Onchain Data Source: https://docs.dune.com/api-reference/apis/quickstart-analyze Execute SQL queries and build custom analytics pipelines with Dune's query execution engine ## Overview Perfect for data teams, researchers, and analysts who need to programmatically analyze onchain data, build custom reports, and create automated analytics pipelines. ## What You'll Learn * Execute SQL queries via API * Retrieve and process results * Handle query parameters * Build analytics pipelines * Filter and transform data ## Prerequisites * A Dune account ([create one free](https://dune.com/auth/register)) * An API key ([get your API key](https://dune.com/apis?tab=keys)) * Basic familiarity with SQL ## Quick Start ### 1. Execute a Simple Query Let's start by executing a query to analyze recent DEX trading volume: ```python Python theme={null} theme={null} from dune_client.client import DuneClient import pandas as pd # Initialize client dune = DuneClient(api_key="YOUR_API_KEY") # Execute SQL query sql = """ SELECT blockchain, DATE_TRUNC('hour', block_time) as hour, SUM(amount_usd) as volume_usd, COUNT(*) as trade_count FROM dex.trades WHERE block_time > now() - interval '24' hour GROUP BY 1, 2 ORDER BY 2 DESC """ results = dune.run_sql(query_sql=sql) # Convert to pandas DataFrame for analysis df = pd.DataFrame(results.result.rows) print(df.head()) ``` ```bash cURL theme={null} theme={null} # Step 1: Execute query curl -X POST "https://api.dune.com/api/v1/sql/execute" \ -H "Content-Type: application/json" \ -H "X-Dune-Api-Key: YOUR_API_KEY" \ -d '{ "sql": "SELECT blockchain, DATE_TRUNC('\''hour'\'', block_time) as hour, SUM(amount_usd) as volume_usd, COUNT(*) as trade_count FROM dex.trades WHERE block_time > now() - interval '\''24'\'' hour GROUP BY 1, 2 ORDER BY 2 DESC", "performance": "medium" }' # Response includes execution_id # {"execution_id":"01JA...","state":"QUERY_STATE_EXECUTING"} # Step 2: Get results (after query completes) curl "https://api.dune.com/api/v1/execution/{execution_id}/results" \ -H "X-Dune-Api-Key: YOUR_API_KEY" ``` The `run_sql()` function in our Python SDK automatically handles query execution and polling for completion. With cURL, you'll need to poll the execution status endpoint and then fetch results manually. ### 2. Use Parameterized Queries For reusable analytics, create queries with parameters: ```python Python theme={null} theme={null} from dune_client.client import DuneClient from dune_client.query import QueryBase from datetime import datetime, timedelta dune = DuneClient(api_key="YOUR_API_KEY") # Execute a saved query with parameters query_id = 3493826 # Example: Popular DEX analysis query query = QueryBase(query_id=query_id) results = dune.run_query(query=query) # Process results for row in results.result.rows: protocol = row.get('protocol') or row.get('project', 'Unknown') volume = row.get('volume') or row.get('volume_usd', 0) print(f"{protocol}: ${volume:,.2f}") ``` ```typescript TypeScript theme={null} theme={null} import { DuneClient, QueryParameter } from '@duneanalytics/client-sdk'; const client = new DuneClient(process.env.DUNE_API_KEY ?? ''); // Execute a saved query with parameters const queryId = 3493826; const params = { query_parameters: [ QueryParameter.text("blockchain", "ethereum"), QueryParameter.number("min_volume", 1000), QueryParameter.number("days", 7), ] }; client .runQuery(queryId, params) .then((executionResult) => { // Process results executionResult.result?.rows.forEach(row => { console.log(`${row.protocol}: $${row.volume.toLocaleString()}`); }); }); ``` ### 3. Build an Analytics Pipeline Create a pipeline that runs multiple queries and combines results: ```python theme={null} theme={null} from dune_client.client import DuneClient import pandas as pd from datetime import datetime dune = DuneClient(api_key="YOUR_API_KEY") def daily_dex_analysis(): """Run daily DEX analytics pipeline""" # 1. Get overall volume volume_query = """ SELECT SUM(amount_usd) as total_volume, COUNT(DISTINCT taker) as unique_traders FROM dex.trades WHERE block_time > now() - interval '24' hour """ volume_data = dune.run_sql(query_sql=volume_query) # 2. Get top protocols protocol_query = """ SELECT project as protocol, SUM(amount_usd) as volume, COUNT(*) as trades FROM dex.trades WHERE block_time > now() - interval '24' hour GROUP BY 1 ORDER BY 2 DESC LIMIT 10 """ protocol_data = dune.run_sql(query_sql=protocol_query) # 3. Get chain breakdown chain_query = """ SELECT blockchain, SUM(amount_usd) as volume FROM dex.trades WHERE block_time > now() - interval '24' hour GROUP BY 1 ORDER BY 2 DESC """ chain_data = dune.run_sql(query_sql=chain_query) # Combine and process results report = { 'timestamp': datetime.now(), 'total_volume': volume_data.result.rows[0]['total_volume'], 'unique_traders': volume_data.result.rows[0]['unique_traders'], 'top_protocols': pd.DataFrame(protocol_data.result.rows), 'chain_breakdown': pd.DataFrame(chain_data.result.rows) } return report # Run pipeline report = daily_dex_analysis() print(f"24h Volume: ${report['total_volume']:,.2f}") print(f"Unique Traders: {report['unique_traders']:,}") print("\nTop Protocols:") print(report['top_protocols']) ``` ### 4. Filter and Transform Results Use Dune's powerful filtering to refine results: ```python Python theme={null} theme={null} from dune_client.client import DuneClient dune = DuneClient(api_key="YOUR_API_KEY") # Execute query with SQL-based filtering sql = """ SELECT blockchain, project, SUM(amount_usd) as volume_usd FROM dex.trades WHERE block_time > now() - interval '24' hour AND blockchain = 'ethereum' AND amount_usd > 1000000 GROUP BY 1, 2 ORDER BY 3 DESC """ results = dune.run_sql(query_sql=sql) # Results are already filtered server-side for row in results.result.rows: print(row) ``` ```bash cURL theme={null} theme={null} # Execute query, then filter results curl "https://api.dune.com/api/v1/execution/{execution_id}/results?filters=blockchain%20%3D%20%27ethereum%27%20AND%20volume_usd%20%3E%201000000" \ -H "X-Dune-Api-Key: YOUR_API_KEY" ``` Server-side filtering with Dune's SQL-like WHERE clause syntax is unique to Dune's API! It saves bandwidth and processing time compared to filtering client-side. ## Common Patterns ### Scheduled Analytics Run analytics on a schedule (e.g., with cron, Airflow, or cloud scheduler): ```python theme={null} theme={null} import schedule import time from dune_client.client import DuneClient dune = DuneClient(api_key="YOUR_API_KEY") def hourly_report(): """Generate hourly DEX report""" sql = """ SELECT project, SUM(amount_usd) as hourly_volume FROM dex.trades WHERE block_time > now() - interval '1' hour GROUP BY 1 ORDER BY 2 DESC """ results = dune.run_sql(query_sql=sql) # Send to your dashboard/database/Slack send_to_dashboard(results.result.rows) # Run every hour schedule.every().hour.at(":00").do(hourly_report) while True: schedule.run_pending() time.sleep(60) ``` ### Onchain Monitoring Monitor specific metrics and trigger alerts: ```python theme={null} theme={null} from dune_client.client import DuneClient import time dune = DuneClient(api_key="YOUR_API_KEY") def monitor_large_trades(threshold=1000000): """Alert on large trades""" sql = f""" SELECT * FROM dex.trades WHERE block_time > now() - interval '5' minute AND amount_usd > {threshold} ORDER BY block_time DESC """ results = dune.run_sql(query_sql=sql) if results.result.rows: for trade in results.result.rows: send_alert(f"Large trade: ${trade['amount_usd']:,.2f} on {trade['blockchain']}") # Run continuously while True: monitor_large_trades() time.sleep(300) # Check every 5 minutes ``` ## Next Steps Full API reference for query execution Advanced filtering techniques Create and manage materialized views Learn DuneSQL functions and operators ## Example Use Cases * **Research Reports:** Generate daily/weekly reports on DeFi protocols, NFT markets, or chain activity * **Automated Dashboards:** Feed data to custom dashboards or BI tools * **Alert Systems:** Monitor metrics and trigger alerts when thresholds are met * **Comparative Analysis:** Compare metrics across chains, protocols, or time periods # Build Custom Dashboards & Reports Source: https://docs.dune.com/api-reference/apis/quickstart-dashboards Fetch query results, manage executions, and create dynamic visualizations in your own applications ## Overview Perfect for product teams embedding analytics into their applications, creating white-label solutions, or building custom reporting tools on top of Dune's data. ## What You'll Learn * Fetch and display query results * Build interactive visualizations * Manage query executions * Create parameterized dashboards * Handle real-time updates * Export data in multiple formats ## Prerequisites * A Dune account ([create one free](https://dune.com/auth/register)) * An API key ([get your API key](https://dune.com/apis?tab=keys)) * Basic knowledge of your frontend framework (React, Vue, etc.) ## Installation Install the required SDK for your language: ```bash TypeScript theme={null} npm install @duneanalytics/client-sdk ``` ```bash Python theme={null} pip install dune-client ``` ## Quick Start ### 1. Fetch Query Results Start by fetching results from an existing Dune query: ```javascript React theme={null} import { useEffect, useState } from 'react'; import { DuneClient } from '@duneanalytics/client-sdk'; function DashboardWidget() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const dune = new DuneClient(process.env.DUNE_API_KEY); useEffect(() => { async function fetchData() { try { // Execute query and get results const executionResult = await dune.runQuery({ queryId: 3493826 }); setData(executionResult.result?.rows); } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } } fetchData(); }, []); if (loading) return
Loading...
; return (

DEX Volume (24h)

{data?.map((row, i) => ( ))}
Protocol Volume
{row.protocol} ${row.volume.toLocaleString()}
); } export default DashboardWidget; ``` ```python Python (Flask) theme={null} from flask import Flask, jsonify, render_template from dune_client.client import DuneClient from dune_client.query import QueryBase app = Flask(__name__) # DuneClient will read the DUNE_API_KEY environment variable dune = DuneClient() @app.route('/api/dashboard/dex-volume') def get_dex_volume(): """API endpoint for DEX volume data""" query = QueryBase(query_id=3493826) results = dune.run_query(query) return jsonify({ 'data': results.get_rows(), 'metadata': { 'execution_id': results.execution_id, 'row_count': len(results.get_rows()) } }) @app.route('/dashboard') def dashboard(): """Render dashboard page""" return render_template('dashboard.html') if __name__ == '__main__': app.run(debug=True) ``` ```python Python (Streamlit) theme={null} import streamlit as st from dune_client.client import DuneClient from dune_client.query import QueryBase import pandas as pd import plotly.express as px # DuneClient will read the DUNE_API_KEY environment variable dune = DuneClient() st.set_page_config(page_title="DEX Analytics", layout="wide") # Dashboard title st.title("📊 DEX Analytics Dashboard") # Fetch data @st.cache_data(ttl=300) # Cache for 5 minutes def load_data(): query = QueryBase(query_id=3493826) results = dune.run_query(query) return pd.DataFrame(results.get_rows()) df = load_data() # Display metrics col1, col2, col3 = st.columns(3) with col1: st.metric("Total Volume", f"${df['volume'].sum():,.0f}") with col2: st.metric("Total Trades", f"{df['trade_count'].sum():,}") with col3: st.metric("Protocols", len(df)) # Visualization fig = px.bar(df, x='protocol', y='volume', title='Volume by Protocol') st.plotly_chart(fig, use_container_width=True) # Data table st.dataframe(df, use_container_width=True) ```
### 2. Build Interactive Visualizations Create charts using popular visualization libraries: ```python Plotly theme={null} from dune_client.client import DuneClient import plotly.graph_objects as go import pandas as pd dune = DuneClient(api_key="YOUR_API_KEY") # Fetch data sql = """ SELECT DATE_TRUNC('hour', block_time) as time, blockchain, SUM(amount_usd) as volume FROM dex.trades WHERE block_time > now() - interval '24' hour GROUP BY 1, 2 ORDER BY 1 """ results = dune.run_sql(query_sql=sql) df = pd.DataFrame(results.get_rows()) # Create interactive chart fig = go.Figure() for blockchain in df['blockchain'].unique(): chain_data = df[df['blockchain'] == blockchain] fig.add_trace(go.Scatter( x=chain_data['time'], y=chain_data['volume'], name=blockchain, mode='lines' )) fig.update_layout( title='DEX Volume by Chain', xaxis_title='Time', yaxis_title='Volume (USD)', hovermode='x unified' ) fig.show() ``` **Important Limitations:** * **TypeScript SDK:** The TypeScript SDK (`@duneanalytics/client-sdk`) only supports saved queries, not direct SQL execution. For JavaScript/TypeScript applications, create a saved query on Dune and use `dune.runQuery({ queryId })`. * **Python SDK:** Direct SQL execution with `dune.run_sql()` requires a Dune Plus subscription. For most users, use saved queries with `query = QueryBase(query_id=123)` and `dune.run_query(query)`. The example above shows SQL execution using the Python SDK (Plus subscription required). ### 3. Parameterized Dashboards Create dashboards with user-configurable filters using saved queries: ```typescript theme={null} import { useState, useEffect } from 'react'; import { DuneClient, QueryParameter } from '@duneanalytics/client-sdk'; interface DashboardFilters { blockchain: string; protocol: string; timeRange: string; } function ParameterizedDashboard() { const [filters, setFilters] = useState({ blockchain: 'ethereum', protocol: 'all', timeRange: '24h' }); const [data, setData] = useState(null); const dune = new DuneClient(process.env.DUNE_API_KEY!); useEffect(() => { async function fetchData() { // Use a saved query with parameters instead of SQL const queryId = 3493826; // Your parameterized query ID const executionResult = await dune.runQuery({ queryId, query_parameters: [ QueryParameter.text("blockchain", filters.blockchain), QueryParameter.text("time_range", filters.timeRange), ] }); setData(executionResult.result?.rows); } fetchData(); }, [filters]); return (
{data && }
); } ``` ### 4. Real-time Dashboard Updates Implement automatic data refreshes: ```javascript theme={null} import { useEffect, useState, useCallback } from 'react'; import { DuneClient } from '@duneanalytics/client-sdk'; function RealtimeDashboard() { const [data, setData] = useState(null); const [lastUpdate, setLastUpdate] = useState(null); const [autoRefresh, setAutoRefresh] = useState(true); const dune = new DuneClient(process.env.DUNE_API_KEY); const fetchData = useCallback(async () => { try { const executionResult = await dune.runQuery({ queryId: 3493826 }); setData(executionResult.result?.rows); setLastUpdate(new Date()); } catch (error) { console.error('Failed to fetch data:', error); } }, []); useEffect(() => { // Initial fetch fetchData(); // Set up auto-refresh let interval; if (autoRefresh) { interval = setInterval(fetchData, 60000); // Refresh every minute } return () => { if (interval) clearInterval(interval); }; }, [fetchData, autoRefresh]); return (
{lastUpdate && ( Last updated: {lastUpdate.toLocaleTimeString()} )}
{data && }
); } ``` ## Advanced Patterns ### Multi-Query Dashboard Combine data from multiple queries: ```python theme={null} from dune_client.client import DuneClient from dune_client.query import QueryBase from concurrent.futures import ThreadPoolExecutor dune = DuneClient(api_key="YOUR_API_KEY") def build_comprehensive_dashboard(): """Fetch data from multiple queries in parallel""" query_ids = { 'volume': 3493826, 'users': 3493827, 'protocols': 3493828, 'chains': 3493829 } # Use ThreadPoolExecutor for parallel execution with ThreadPoolExecutor(max_workers=4) as executor: futures = { name: executor.submit( lambda qid: dune.run_query(QueryBase(query_id=qid)), query_id ) for name, query_id in query_ids.items() } # Build dashboard data structure dashboard = { name: future.result().get_rows() for name, future in futures.items() } return dashboard # Run function (no async/await needed) dashboard_data = build_comprehensive_dashboard() ``` The Python Dune SDK does not support async/await. For parallel query execution, use `ThreadPoolExecutor` as shown above to execute multiple queries concurrently. ### Export Data Provide data export functionality: ```typescript theme={null} import { DuneClient } from '@duneanalytics/client-sdk'; class DataExporter { private dune: DuneClient; constructor(apiKey: string) { this.dune = new DuneClient(apiKey); } async exportToCSV(queryId: number, filename: string) { // Use Dune's CSV endpoint const response = await fetch( `https://api.dune.com/api/v1/query/${queryId}/results/csv`, { headers: { 'X-Dune-Api-Key': process.env.DUNE_API_KEY! } } ); const csv = await response.text(); // Trigger download const blob = new Blob([csv], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); } async exportToJSON(queryId: number) { const executionResult = await this.dune.runQuery({ queryId }); const json = JSON.stringify(executionResult.result?.rows, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'data.json'; a.click(); } } // Usage function ExportButton({ queryId }: { queryId: number }) { const exporter = new DataExporter(process.env.DUNE_API_KEY!); return (
); } ``` ### Caching Strategy Implement smart caching for better performance: ```python theme={null} from flask import Flask, jsonify from flask_caching import Cache from dune_client.client import DuneClient from dune_client.query import QueryBase import os app = Flask(__name__) cache = Cache(app, config={'CACHE_TYPE': 'simple'}) # DuneClient will read the DUNE_API_KEY environment variable dune = DuneClient() @app.route('/api/data/') @cache.cached(timeout=300) # Cache for 5 minutes def get_query_data(query_id): """Cached endpoint for query results""" query = QueryBase(query_id=query_id) results = dune.run_query(query) return jsonify({ 'data': results.get_rows(), 'cached': False, # First request 'execution_id': results.execution_id }) @app.route('/api/data//refresh') def refresh_data(query_id): """Force refresh cached data""" cache.delete(f'view//api/data/{query_id}') query = QueryBase(query_id=query_id) results = dune.run_query(query) return jsonify({ 'data': results.get_rows(), 'cached': False, 'refreshed': True }) ``` ## Dashboard Templates ### Analytics Dashboard ```javascript theme={null} // Complete analytics dashboard example using saved queries import { useEffect, useState } from 'react'; import { DuneClient } from '@duneanalytics/client-sdk'; import { LineChart, BarChart, PieChart } from 'recharts'; function AnalyticsDashboard() { const [metrics, setMetrics] = useState({ totalVolume: 0, activeUsers: 0, avgTransactionSize: 0, topProtocols: [] }); const dune = new DuneClient(process.env.DUNE_API_KEY); useEffect(() => { async function loadDashboard() { // Use saved query IDs for each metric const volumeQueryId = 123456; // Your volume query ID const usersQueryId = 123457; // Your users query ID const protocolsQueryId = 123458; // Your protocols query ID const [volume, users, protocols] = await Promise.all([ dune.runQuery({ queryId: volumeQueryId }), dune.runQuery({ queryId: usersQueryId }), dune.runQuery({ queryId: protocolsQueryId }) ]); setMetrics({ totalVolume: volume.result?.rows[0].total, activeUsers: users.result?.rows[0].total, avgTransactionSize: volume.result?.rows[0].total / users.result?.rows[0].total, topProtocols: protocols.result?.rows }); } loadDashboard(); // Refresh every 5 minutes const interval = setInterval(loadDashboard, 300000); return () => clearInterval(interval); }, []); return (

DEX Analytics

); } ``` ## Next Steps Full API reference for query execution Filter and transform data efficiently Export data in CSV format Get notified when queries complete ## Example Use Cases * **Internal Analytics:** Build custom dashboards for your team * **White-label Solutions:** Embed Dune-powered analytics in your product * **Client Reporting:** Generate automated reports for clients * **Portfolio Trackers:** Create personalized portfolio dashboards * **Protocol Dashboards:** Build public dashboards for your protocol # CI/CD & Workflows Source: https://docs.dune.com/api-reference/connectors/dbt/cicd-workflows Set up development workflows and GitHub Actions for dbt on Dune The dbt template includes pre-configured GitHub Actions workflows for both CI and production deployments. **Credit Usage in CI/CD** All dbt executions triggered by CI/CD workflows consume Dune credits. This includes pull request workflows, deploy workflows, and scheduled runs. The template repository ships with automated triggers **disabled by default** so you can enable them on your own terms. See [Pricing & Best Practices](/api-reference/connectors/dbt/pricing-best-practices) for optimization guidance. ## Understanding Credit Costs in CI/CD When you run dbt models through CI/CD, each execution consumes credits from your Dune plan. Here are a few things to keep in mind: * **Each push or PR can trigger a pipeline run.** If your GitHub Actions run on every commit, frequent pushes during development will each use credits. * **All executions draw from the same pool.** Whether a query is triggered locally, from a CI runner, or a scheduled job, it counts the same way. * **Concurrent executions add up.** Multiple CI jobs running simultaneously (e.g., several open PRs) each consume credits independently. * **Timed-out queries still use credits.** If a query runs for 30 minutes before timing out, credits are consumed for the compute used during that time. **Tips for managing costs:** * Start with `workflow_dispatch` (manual trigger only) until you've validated your models * Use `--select state:modified` to only run changed models in CI * Set appropriate query timeouts to keep costs predictable * Monitor your credit usage in the Dune dashboard after enabling automated workflows ## Development Workflow ### Local Development 1. **Create a feature branch**: ```bash theme={null} git checkout -b feature/new-transformation ``` 2. **Develop models locally**: ```bash theme={null} # Run specific model uv run dbt run --select my_model # Run with full refresh (ignore incremental logic) uv run dbt run --select my_model --full-refresh # Run tests for specific model uv run dbt test --select my_model ``` 3. **Query your tables on Dune**: * Remember to use the `dune.` catalog prefix: ```sql theme={null} SELECT * FROM dune.my_team__tmp_alice.my_model ``` ### Pull Request Workflow 1. **Push changes and open PR**: ```bash theme={null} git add . git commit -m "Add new transformation model" git push origin feature/new-transformation ``` 2. **Automated CI runs**: * CI enforces that branch is up-to-date with main * Runs modified models with `--full-refresh` in isolated schema `{team}__tmp_pr{number}` * Runs tests on modified models * Tests incremental run logic **Tip:** The pull request workflow is disabled by default in the template. To enable it, uncomment the `on:` trigger block in `.github/workflows/dbt_ci.yml`. Each PR sync event (new commits pushed to a PR branch) will trigger a run and consume credits. 3. **Team review**: * Review transformation logic in GitHub * Check CI results * Approve and merge when ready ### Production Deployment The [production workflow](https://github.com/duneanalytics/dune-dbt-template/blob/main/.github/workflows/dbt_prod.yml) includes an hourly schedule (0 \* \* \* \*), but it's commented out by default. You can enable it by uncommenting the corresponding lines when you're ready to run production jobs automatically. **Tip:** The deploy and scheduled workflows are disabled by default. Only `workflow_dispatch` (manual trigger) is enabled out of the box. Uncomment the `push` and `schedule` triggers in the respective workflow files when you're ready to automate. 1. **State comparison**: Uses manifest from previous run to detect changes 2. **Full refresh modified models**: Any changed models run with `--full-refresh` 3. **Incremental run**: All models run with normal incremental logic 4. **Testing**: All models are tested 5. **Notification**: Email sent on failure ## CI/CD with GitHub Actions The template includes two GitHub Actions workflows: ### CI Workflow (`.github/workflows/ci.yml`) Runs on every pull request: ```yaml theme={null} - Enforces branch is up-to-date with main - Sets DEV_SCHEMA_SUFFIX to pr{number} - Runs modified models with --full-refresh - Tests modified models - Runs incremental logic test - Tests incremental models ``` **Required GitHub Secrets**: * `DUNE_API_KEY` **Required GitHub Variables**: * `DUNE_TEAM_NAME` ### Production Workflow (`.github/workflows/prod.yml`) Runs hourly on main branch: ```yaml theme={null} - Downloads previous manifest (for state comparison) - Full refreshes any modified models - Tests modified models - Runs all models (incremental logic) - Tests all models - Uploads manifest for next run - Sends email notification on failure ``` ## Troubleshooting ### Connection Issues **Problem**: `dbt debug` fails with connection error. **Solution**: * Verify `DUNE_API_KEY` and `DUNE_TEAM_NAME` are set correctly * Check that you have Data Transformations enabled for your team * Ensure `transformations: true` is in session properties ### Models Not Appearing in Dune **Problem**: Can't find tables in Data Explorer or queries. **Solution**: * Check the Connectors section in Data Explorer under "My Data" * Remember to use `dune.` catalog prefix in queries * Verify the table was created in the correct schema ### Incremental Models Not Working **Problem**: Incremental models always do full refresh. **Solution**: * Check that `is_incremental()` macro is used correctly * Verify the `unique_key` configuration matches your table structure * Ensure the target table exists before running incrementally ### CI/CD Failures **Problem**: GitHub Actions failing. **Solution**: * Verify secrets and variables are set correctly in GitHub * Check that branch is up-to-date with main * Review workflow logs for specific errors ## Limitations ### Metadata Discovery Limited support for some metadata discovery queries like `SHOW TABLES` or `SHOW SCHEMAS` in certain contexts. This may affect autocomplete in some BI tools. **Workaround**: Use the [Data Explorer](/web-app/query-editor/data-explorer) or query `information_schema` directly. ### Result Set Size Large result sets may timeout. Consider: * Paginating with `LIMIT` and `OFFSET` * Narrowing filters to reduce data volume * Breaking complex queries into smaller parts ### Read-After-Write Consistency Tables and views are available for querying immediately after creation, but catalog caching may cause brief delays (typically \< 60 seconds) before appearing in some listing operations. ### Rate Limits Rate limits for Data Transformations align with the Dune Data API: * Requests are subject to the same rate limiting as API executions * Large query operations run on the [Large Query Engine tier](/query-engine/query-executions#large-engine-size) * See [Rate Limits](/api-reference/overview/rate-limits) for detailed information # dbt to Datashare Source: https://docs.dune.com/api-reference/connectors/dbt/datashares Sync dbt-managed tables from Dune into your configured datashare target Use dbt to publish transformation outputs from your private Dune namespace into a configured Datashare target such as Snowflake, BigQuery, or S3. This is useful when you want to keep the transformation logic on Dune, but consume the resulting tables inside your own warehouse. dbt to Datashare is an enterprise workflow that requires both **Data Transformations** and **Datashare** to be enabled for your team. Datashare syncs are billed based on bytes transferred and byte-months of storage for the synced table. ## How It Works 1. dbt builds a `table` or `incremental` model in your Dune namespace. 2. A dbt post-hook runs `ALTER TABLE ... EXECUTE datashare(...)` on that table. 3. Dune registers or updates the datashare sync and sends the data to your configured target. 4. You monitor the sync in `dune.datashare.table_syncs` and `dune.datashare.table_sync_runs`. ## Prerequisites * Enterprise account with Data Transformations enabled * Datashare configured for your team by Dune * Dune API key with write access * A dbt project using the Dune Trino connector ## Recommended Starting Point The fastest way to get started is the public template repo: Includes the datashare macro, a prod-only post-hook, and an opt-in example model. ## Add The dbt Post-Hook Add the datashare post-hook to your `dbt_project.yml`: ```yaml theme={null} models: your_project: +post-hook: - sql: "{{ datashare_trigger_sync() }}" transaction: true ``` **The hook must be restricted to your production target.** Without a target guard, a local `dbt run` or a CI job writes to a temporary schema (`__tmp_`) and registers **that** schema as a real datashare, shipping it to your destination warehouse. Because dev schemas are ephemeral, the resulting destination table and view can outlive the source table that created them. If you are adding datashare to an existing dbt project rather than starting from the template, make sure your `datashare_trigger_sync()` macro opens with this check: ```jinja theme={null} {% macro datashare_trigger_sync() %} {%- if target.name != 'prod' -%} {{ log('Skipping datashare sync for ' ~ this.schema ~ '.' ~ this.identifier ~ ': datashare post-hook only runs on the prod target.', info=True) }} {{ return('') }} {%- endif -%} {#- ... build and return the ALTER TABLE ... EXECUTE datashare(...) statement -#} {%- endmacro %} ``` When the guard is working you will see one `Skipping datashare sync for ...: datashare post-hook only runs on the prod target` line per datashare-enabled model on every non-prod run. ## Check Table Size Before Syncing Datashare syncs are billed by reported bytes written during the sync and by byte-months of storage at your target warehouse, so it is worth checking how large a source table is before you enable a sync. You can read the Dune source table's on-disk size from the `$size` metadata table, but you first need to know the **physical** table name that Dune is scanning, which may differ from the logical name you query. You can give a list of tables to the [Dune MCP](/docs/agents/mcp#dune-mcp) and it can calculate their sizes. Alternatively, you can use the following two-step workflow from any Trino-compatible client (the Dune SQL Editor, a notebook, or `dbt run-operation`). These queries are read-only and do not require the `transformations=true` session property. The `$size` suffix must be attached to the table name and wrapped in double quotes (for example, `schema."table$size"`). ### Step 1: Find The Physical Table Name With `EXPLAIN` Run `EXPLAIN` against the table you want to size up: ```sql theme={null} EXPLAIN SELECT * FROM team.schema.table; ``` In the query plan, look for the `TableScan` or `ScanProject` node. The `table = ...` value is the physical table name you need for step 2. The catalog prefix before the colon (for example `dune:` or `delta_prod:`) is part of the connector and should be dropped — only the `schema.table` portion is used in the size query. ### Step 2: Query The `$size` Metadata Table Plug the physical `schema.table` name from step 1 into the size query, wrapping `table$size` in double quotes: ```sql theme={null} SELECT size_bytes, size_bytes / 1000.0 / 1000 / 1000 AS source_size_gb, size_bytes / 1000.0 / 1000 / 1000 / 1000 AS source_size_tb FROM dune.."$size"; ``` This returns the Dune source table's at-rest size using SI units (`1 GB = 1,000,000,000 bytes`). Treat it as a planning estimate only: it is not the destination warehouse table size, and it is not the exact billing meter. Destination storage can differ because each warehouse uses different formats, compression, layout, and metadata. Datashare sync billing is based on bytes written during synchronization, as reported by Dune's sync system. This can differ from both the Dune source table at-rest size and the destination warehouse at-rest size. ### Example 1: A dbt-Created Table For the dbt model `dune.dune.lineage_query_2`: ```sql theme={null} EXPLAIN SELECT * FROM dune.dune.lineage_query_2; ``` The `TableScan` line in the plan reads: ``` TableScan[table = dune:dune.lineage_query_2] ``` Here the physical name matches the logical name, so the size query is: ```sql theme={null} SELECT size_bytes, size_bytes / 1000.0 / 1000 / 1000 AS source_size_gb, size_bytes / 1000.0 / 1000 / 1000 / 1000 AS source_size_tb FROM dune.dune."lineage_query_2$size"; ``` ### Example 2: A Dune-Managed Table With A Suffix For a Dune-managed table such as `base.transactions`: ```sql theme={null} EXPLAIN SELECT * FROM base.transactions; ``` The plan shows a `ScanProject` node referencing a different physical name: ``` ScanProject[table = delta_prod:base.transactions_0002] ``` Notice the `_0002` suffix on the physical table — that is the name you must use in the size query (drop the `delta_prod:` catalog prefix): ```sql theme={null} SELECT size_bytes, size_bytes / 1000.0 / 1000 / 1000 AS source_size_gb, size_bytes / 1000.0 / 1000 / 1000 / 1000 AS source_size_tb FROM base."transactions_0002$size"; ``` After confirming the source-size estimate is acceptable, run a one-off sync with `dry_run: true` (see [Manual Syncs](#manual-syncs)) to preview the generated SQL before enabling the post-hook in production. ## Configure A Model Enable datashare in `meta.datashare` on a `table` or `incremental` model: ```sql theme={null} {% set time_start = "current_date - interval '1' day" if is_incremental() else "current_date - interval '2' day" %} {% set time_end = "current_date + interval '1' day" %} {{ config( materialized = 'incremental', incremental_strategy = 'merge', unique_key = ['block_number', 'block_date'], meta = { "datashare": { "enabled": true, "time_column": "block_date", "time_start": time_start, "time_end": time_end, "target_type": "snowflake", "target_region": "us" } } ) }} select ... ``` ## Configuration Fields | Field | Required | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | Yes | Must be `true` to trigger a sync. | | `time_column` | Yes | Column used by the sync window. | | `time_start` | Yes | SQL expression for the start of the window. | | `time_end` | No | SQL expression for the end of the window. Defaults to `now()`. | | `unique_key_columns` | No | Unique row identity columns. Falls back to the model `unique_key`. | | `target_type` | Yes | Datashare target platform where the synced table is delivered. One of `snowflake`, `bigquery`, or `s3`. Must match a target configured for your team. | | `target_region` | Yes | Region of the target warehouse to sync into (for example, `us` or `eu`). Must match a region configured for your team's datashare target. | Keep `time_column`, `time_start`, and `time_end` at the same granularity. For example, if `time_column` is a `date`, use date-based expressions rather than hour-based timestamp windows. ## Full Refresh Behavior The sync uses `full_refresh = true` when: * the model is materialized as a `table` * the incremental model is running for the first time * the model is run with `--full-refresh` Normal incremental runs use `full_refresh = false`. ## Manual Syncs You can trigger a sync manually with `dbt run-operation`: ```bash theme={null} uv run dbt run-operation datashare_trigger_sync_operation --target prod --args ' model_selector: my_model dry_run: true ' ``` This is useful for previewing the generated SQL or running one-off syncs outside a normal `dbt run`. **Always pass `--target prod`.** This is a separate entry point from the post-hook and does not inherit its guard. It reads the source schema from the dbt manifest, so on the default `dev` target it registers your temporary schema as a real datashare. `dry_run: true` is safe on any target, since it only prints SQL. ## Monitor Syncs ```sql theme={null} SELECT * FROM dune.datashare.table_syncs WHERE source_schema = 'your_team'; SELECT * FROM dune.datashare.table_sync_runs WHERE source_schema = 'your_team' ORDER BY created_at DESC; ``` Use `table_syncs` for the current registration state and `table_sync_runs` for execution history. ## Remove A Table From Datashare ```sql theme={null} ALTER TABLE dune.your_team.my_table EXECUTE delete_datashare ``` ## Reference * [Supported SQL Operations](/api-reference/connectors/sql-operations) * [dbt Connector Overview](/api-reference/connectors/dbt/overview) * [Datashare Overview](/datashare/datashare) # Getting Started Source: https://docs.dune.com/api-reference/connectors/dbt/getting-started Set up your dbt project and connect to Dune The fastest way to get started is using our [dbt template repository](https://github.com/duneanalytics/dune-dbt-template), which includes pre-configured profiles, sample models, and CI/CD workflows. ## Prerequisites * **Dune Enterprise account** with Data Transformations enabled * **Dune API key** ([generate one here](https://dune.com/settings/api)) * **Team name** on Dune (defines your namespace) * **dbt installed locally** (we recommend using `uv` for dependency management) ## 1. Use the Template Repository We provide a complete dbt project template to get started quickly: **GitHub Template**: [github.com/duneanalytics/dune-dbt-template](https://github.com/duneanalytics/dune-dbt-template) The template includes: * Pre-configured dbt profiles for dev and prod environments * Sample models demonstrating all model types * GitHub Actions workflows for CI/CD * Cursor AI rules for dbt best practices on Dune * Example project structure following dbt conventions **CI Workflows Are Disabled by Default** The template repository ships with GitHub Actions CI workflows **disabled by default**. Since each dbt execution consumes Dune credits, we recommend: 1. Running models locally first to get familiar with your pipeline 2. Reviewing the [Pricing & Best Practices](/api-reference/connectors/dbt/pricing-best-practices) guide 3. Enabling workflows when you're ready by uncommenting triggers in the workflow files (see the [template README](https://github.com/duneanalytics/dune-dbt-template#-note-) for instructions) **To use the template:** ```bash theme={null} # Create a new repository from the template # (Use GitHub's "Use this template" button) # Clone your new repository git clone https://github.com/your-org/your-dbt-project.git cd your-dbt-project # Install dependencies uv sync # Set up environment variables (see next section) ``` ## 2. Configure Environment Variables Set these required environment variables: ```bash theme={null} # Required export DUNE_API_KEY="your_api_key_here" export DUNE_TEAM_NAME="your_team_name" # Optional - for personal dev environments export DEV_SCHEMA_SUFFIX="alice" ``` **Persistence options:** ```bash theme={null} # Option 1: Add to shell profile (recommended for local dev) echo 'export DUNE_API_KEY="your_key"' >> ~/.zshrc echo 'export DUNE_TEAM_NAME="your_team"' >> ~/.zshrc source ~/.zshrc # Option 2: Use a .env file (remember to add to .gitignore!) # Option 3: Set in CI/CD secrets for production deployments ``` ## 3. Configure dbt Profile Your `profiles.yml` should look like this: ```yaml theme={null} dune: outputs: dev: type: trino method: jwt user: "{{ env_var('DUNE_TEAM_NAME') }}" jwt_token: "{{ env_var('DUNE_API_KEY') }}" host: trino.api.dune.com port: 443 database: dune schema: "{{ env_var('DUNE_TEAM_NAME') }}__tmp_{{ env_var('DEV_SCHEMA_SUFFIX', '') }}" http_scheme: https session_properties: transformations: true prod: type: trino method: jwt user: "{{ env_var('DUNE_TEAM_NAME') }}" jwt_token: "{{ env_var('DUNE_API_KEY') }}" host: trino.api.dune.com port: 443 database: dune schema: "{{ env_var('DUNE_TEAM_NAME') }}" http_scheme: https session_properties: transformations: true target: dev ``` The `transformations: true` session property is **required**. This tells Dune that you're running data transformation operations that need write access. ## 4. Test Your Connection ```bash theme={null} # Install dbt dependencies uv run dbt deps # Test connection uv run dbt debug # Run your first model uv run dbt run # Run tests uv run dbt test ``` ## Project Structure The template repository follows standard dbt conventions: ``` your-dbt-project/ ├── models/ │ ├── templates/ # Example models for each strategy │ │ ├── dbt_template_view_model.sql │ │ ├── dbt_template_table_model.sql │ │ ├── dbt_template_merge_incremental_model.sql │ │ ├── dbt_template_delete_insert_incremental_model.sql │ │ └── dbt_template_append_incremental_model.sql │ └── your_models/ # Your transformation models ├── macros/ │ └── dune_dbt_overrides/ │ └── get_custom_schema.sql # Schema naming logic ├── tests/ # Custom data tests ├── seeds/ # CSV seed files ├── snapshots/ # Snapshot definitions ├── analyses/ # Ad-hoc analyses ├── .github/workflows/ # CI/CD workflows (disabled by default—see note above) ├── profiles.yml # dbt connection profile ├── dbt_project.yml # Project configuration └── README.md ``` ## Schema Organization Schemas are automatically organized based on your dbt target: | Target | DEV\_SCHEMA\_SUFFIX | Schema Name | Use Case | | ------ | ------------------- | ------------------- | --------------------------- | | `dev` | Not set | `{team}__tmp_` | Local development (default) | | `dev` | Set to `alice` | `{team}__tmp_alice` | Personal dev space | | `dev` | Set to `pr123` | `{team}__tmp_pr123` | CI/CD per PR | | `prod` | (any) | `{team}` | Production tables | This is controlled by the `get_custom_schema.sql` macro in the template. ## How It Works ### Namespace Isolation All tables and views you create are organized into your team's namespace: * **Production schema**: `{your_team}` - For production tables * **Development schemas**: `{your_team}__tmp_*` - For development and testing This ensures complete isolation between teams and between development/production environments. ### Write Operations Execute SQL statements to create and manage your data: 1. **Create tables and views** in your namespace 2. **Insert, update, or merge** data using standard SQL 3. **Drop tables** when no longer needed 4. **Optimize and vacuum** tables for optimal performance when querying these tables All operations are authenticated via your Dune API key and restricted to your team's namespace. ### Data Access **What You Can Read:** * **All public Dune datasets**: Full access to blockchain data across all supported chains * **Your uploaded data**: Private datasets you've uploaded to Dune * **Your transformation outputs**: Tables and views created in your namespace * **Materialized views**: Views that are materialized as tables in your namespace via the APP **What You Can Write:** * **Your team namespace**: `{team_name}` for production tables * **Development namespaces**: `{team_name}__tmp_*` for dev and testing * **Private by default**: All created tables are private unless explicitly made public **Access Control:** * Write operations are restricted to your team's namespaces only * Cannot write to public schemas or other teams' namespaces * Schema naming rules enforced: no `__tmp_` in team handles ## Querying dbt Models on Dune When querying your dbt models in the Dune app or via the API, you **must** use the `dune.` catalog prefix. **Pattern**: `dune.{schema}.{table}` ```sql theme={null} -- ❌ Won't work (dbt logs show this but it won't work on Dune) SELECT * FROM my_team.my_model -- ✅ Correct SELECT * FROM dune.my_team.my_model SELECT * FROM dune.my_team__tmp_alice.dev_model ``` dbt logs omit the catalog name for readability, so remember to add `dune.` when using queries in the Dune app. ## Where Your Data Appears Tables and views created through dbt appear in the **Data Explorer** under: **My Data → Connectors** Data Transformations in Data Explorer under Connectors You can: * Browse your transformation datasets * View table schemas and metadata * Delete datasets directly from the UI * Search and reference them in queries ## Next Steps Learn about merge, delete+insert, and append strategies Set up GitHub Actions and development workflows # Incremental Models Source: https://docs.dune.com/api-reference/connectors/dbt/incremental-models Efficiently update large tables with merge, delete+insert, and append strategies dbt supports multiple strategies for incremental models. The template includes examples of each strategy to help you get started. ## 1. Merge Strategy (Recommended) **When to use**: When you need to update existing rows and insert new ones. **Example**: ```sql theme={null} {{ config( materialized='incremental', unique_key='user_address', incremental_strategy='merge' ) }} SELECT user_address, COUNT(*) as trade_count, SUM(volume_usd) as total_volume, MAX(block_time) as last_trade_time FROM {{ source('ethereum', 'dex_trades') }} WHERE block_time >= date_trunc('day', now() - interval '1' day) {% if is_incremental() %} AND block_time >= (SELECT MAX(last_trade_time) FROM {{ this }}) {% endif %} GROUP BY 1 ``` ## 2. Delete+Insert Strategy **When to use**: When recomputing entire partitions (e.g., daily aggregations). **Example**: ```sql theme={null} {{ config( materialized='incremental', unique_key='date', incremental_strategy='delete+insert' ) }} SELECT date_trunc('day', block_time) as date, protocol, COUNT(*) as transaction_count, SUM(amount_usd) as volume FROM {{ source('ethereum', 'decoded_events') }} WHERE block_time >= date_trunc('day', now() - interval '7' day) {% if is_incremental() %} AND date_trunc('day', block_time) >= date_trunc('day', now() - interval '1' day) {% endif %} GROUP BY 1, 2 ``` ## 3. Append Strategy **When to use**: For immutable event logs that only need new rows appended. **Example**: ```sql theme={null} {{ config( materialized='incremental', unique_key='tx_hash', incremental_strategy='append' ) }} SELECT tx_hash, block_time, block_number, "from" as from_address, "to" as to_address, value FROM {{ source('ethereum', 'transactions') }} WHERE block_time >= date_trunc('hour', now() - interval '1' hour) {% if is_incremental() %} AND block_time >= (SELECT MAX(block_time) FROM {{ this }}) {% endif %} ``` ## Strategy Comparison | Strategy | Best For | How It Works | | ----------------- | -------------------------------------- | ------------------------------------------------------ | | **Merge** | Updating existing rows + inserting new | Matches on `unique_key`, updates existing, inserts new | | **Delete+Insert** | Recomputing partitions | Deletes matching rows, then inserts new data | | **Append** | Immutable event logs | Only inserts new rows, no updates or deletes | ## Table Maintenance Maintenance operations consume credits based on compute and data written. These operations are necessary to keep your tables performant and to reclaim storage space. ### Manual Maintenance Run OPTIMIZE and VACUUM to improve performance and reduce storage costs: ```bash theme={null} # Optimize a specific table uv run dbt run-operation optimize_table --args '{table_name: "my_model"}' # Vacuum a specific table uv run dbt run-operation vacuum_table --args '{table_name: "my_model"}' ``` ### Automated Maintenance with dbt post-hooks Add post-hooks to your model configuration: ```sql theme={null} {{ config( materialized='incremental', post_hook=[ "ALTER TABLE {{ this }} EXECUTE OPTIMIZE", "ALTER TABLE {{ this }} EXECUTE VACUUM" ] ) }} SELECT ... ``` ### Project level post-hooks Add post-hooks to your project configuration: ```yaml theme={null} # dbt_project.yml post-hooks: - "ALTER TABLE {{ this }} EXECUTE OPTIMIZE" - "ALTER TABLE {{ this }} EXECUTE VACUUM" ``` The [template repository](https://github.com/duneanalytics/dune-dbt-template) includes a default post-hook that runs OPTIMIZE and VACUUM on all tables. ## Dropping Tables dbt doesn't have a built-in way to drop tables. Options: ### Option 1: Use dbt's --full-refresh flag then remove the model ```bash theme={null} # This will drop and recreate uv run dbt run --select my_model --full-refresh # Then delete the model file and run again rm models/my_model.sql uv run dbt run ``` ### Option 2: Connect with a SQL client Use any Trino-compatible client (Hex, Jupyter, DBeaver) to execute: ```sql theme={null} DROP TABLE IF EXISTS dune.your_team.old_model; ``` See the [SQL Operations Reference](/api-reference/connectors/sql-operations) for details. ## Examples Complete examples are available in the template repository: * **View Model**: Lightweight, always fresh data * **Table Model**: Static snapshots for specific points in time * **Merge Incremental**: Update existing rows, insert new ones * **Delete+Insert Incremental**: Recompute partitions efficiently * **Append Incremental**: Add-only with deduplication See all example models in our official dbt template repository # dbt Connector Overview Source: https://docs.dune.com/api-reference/connectors/dbt/overview Run production-grade dbt projects directly on Dune with full incremental model support The dbt Connector enables you to run production-grade dbt projects directly against Dune's data warehouse using the [dbt-trino adapter](https://docs.getdbt.com/docs/core/connect-data-platform/trino-setup). Build, test, and deploy transformation pipelines with full support for incremental models, testing frameworks, and CI/CD orchestration. This connector provides **write access to DuneSQL** via a Trino API endpoint, enabling you to create, update, and manage tables in your private namespace while reading from Dune's comprehensive blockchain datasets. Enterprise teams can also use dbt to publish transformed tables from Dune into a configured Datashare target. See [dbt to Datashare](/api-reference/connectors/dbt/datashares). Get started quickly with our official dbt template repository, featuring example models for all model strategies and CI/CD workflows. Sync dbt-managed tables from your Dune namespace into Snowflake or BigQuery. The dbt Connector is currently only available to Enterprise customers with Data Transformations enabled. Interested in having the dbt Connector enabled for your team? [Contact our team](https://dune.com/enterprise)—please mention the dbt Connector when you reach out. **Credits & CI/CD** Each dbt run consumes Dune credits, including runs triggered by CI/CD pipelines. If you're using GitHub Actions or similar tools, we recommend reviewing [Pricing & Best Practices](/api-reference/connectors/dbt/pricing-best-practices) for optimization tips and [CI/CD & Workflows](/api-reference/connectors/dbt/cicd-workflows) for guidance on configuring triggers. ## Video Tutorial Watch this step-by-step guide to get started with the dbt Connector on Dune: ``` Instead of sharing static screenshots, embedded visualizations stay up to date as the underlying query re-runs. Simply paste the iframe snippet into your website's HTML. Enable **Share in dark mode** in the share modal for dark-themed embeds. ### Social Sharing Use the social icons in the share modal to share directly to LinkedIn, Telegram, X, or Threads. ## Embeds with Parameters Embed links support [parameterized queries](/web-app/query-editor/parameters). The generated embed URL does not include parameters by default — you can append them manually to the URL: ``` https://dune.com/embeds/118220/238460/aa002dd3-f9e2-4d63-86c8-b765569306c6NFT?address=0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7&rolling_n_trades=500 ``` This lets viewers interact with different parameter values directly from the embedded chart on your site. ## Collaborate via Teams For ongoing collaboration, [create a team](/web-app/teams) and invite colleagues as viewers or editors. Team members can share credits and work on shared queries and dashboards together. # Teams and Roles Source: https://docs.dune.com/web-app/teams Create and manage team workspaces, invite members, assign roles, and collaborate on shared queries and dashboards. Team accounts on Dune provide a shared workspace for collaboration. Teams can be created without a paid plan, but [Plus and Enterprise subscription features](https://dune.com/pricing) are available only to teams, not individual accounts. **Enterprise organizations** — Companies with multiple Enterprise teams can use an **organization**: one subscription and administrative layer for many teams. Billing, consolidated usage, and some security settings are managed at the organization level; teams still own queries, dashboards, and API keys. See [Organizations](/web-app/organizations). **Key benefits:** * **Collaborate on shared content.** Multiple team members work on the same queries and dashboards. * **Shared credits and paid features.** All team members can spend credits and access private content. * **Team profile.** Showcase all of your team's work in one place. * **Role-based access.** Assign viewers, editors, or admins to control permissions. ## Create a Team 1. Navigate to the **global context switcher** at the top of the page. 2. Select **"Create new team"** from the dropdown. 3. Complete the team creation process. Every team must have a unique name that has not been used before.