Externally hosted app quickstart
This quickstart builds a small externally hosted app: a single page with one governed chart
backed by a business question. Your project owns its hosting, deployment and trusted server-side
Astrato access. It does not use the Astrato CLI or @astrato-io/app-runtime.
Choose this path when you want to add Astrato data or charts to an app that you host and operate. See Astrato terminology for a comparison with Workbooks and Data Apps.
If the app is intended for Astrato-managed hosting, stop here and use the Data App quickstart instead. The two routes use different runtime and authentication boundaries and must not be mixed. Data App publishing is in preview and requires an enabled environment and publication permissions; see Manage Data Apps.
For every free-form App build, establish useful saved Business Questions and semantic/chart capabilities before implementing the views. Reuse matching questions and create missing ones during exploration through the editor or MCP authoring workflow. The application runtime itself does not author the model.
To add inserts, updates, stored procedures or SELECT readback through an authenticated platform transport, use Writeback and SQL. Its endpoint scopes and datasource permissions apply separately from chart queries.
What you need
Section titled “What you need”- Your tenant ID for the Astrato environment. Ask your Astrato administrator if you do not know it.
- A semantic layer ID. You can find one through MCP as described below.
- An Astrato host. This guide uses
https://app.astrato.io; if your organization provides a private or custom deployment origin, use it as the development proxy target and for trusted server-side Astrato calls. BrowserbaseUrlremains your app’s same-origin URL. - A Personal Access Token — see Authentication.
- Node.js and npm. The example uses Vite, but any bundler with a dev proxy works.
- The Astrato SDK and visualization packages used in the installation step.
1. Create the app and install
Section titled “1. Create the app and install”npm create vite@latest astrato-quickstart -- --template vanilla-tscd astrato-quickstartnpm install @astrato-io/sdk @astrato-io/visualizations2. Configure the dev proxy
Section titled “2. Configure the dev proxy”Browsers can only reach the Astrato API same-origin (see the same-origin rule). A dev proxy forwards your page’s API calls to your Astrato host server-side, where that restriction does not apply.
import { defineConfig, loadEnv } from 'vite';
export default defineConfig(({ mode }) => { const token = loadEnv(mode, '.', '').ASTRATO_TOKEN; if (!token) throw new Error('Set ASTRATO_TOKEN in .env.local');
return { server: { proxy: { '/api': { target: 'https://app.astrato.io', changeOrigin: true, headers: { Authorization: `Bearer ${token}` }, }, }, }, };});Put your token in .env.local. ASTRATO_TOKEN is an example local credential name, not an
Astrato or framework requirement. Never commit or ship the file: the proxy reads it server-side
and adds the bearer header without exposing the token to browser code.
Keep this development server bound to localhost. Anyone who can reach the proxy can make Astrato API requests with the configured token, so do not expose it to an untrusted network.
This PAT proxy is a development setup, not a production multi-user authentication design. An externally hosted production app must provide its own trusted backend/session boundary and keep Astrato credentials out of browser assets. A static page on another origin cannot call Astrato APIs directly, even with a PAT. If the intended app must remain frontend-only, use the Data App route instead. Publishing requires an enabled environment and publication permissions.
Create .env.local privately rather than passing the token in a shell command, which may save it
in shell history:
ASTRATO_TOKEN=<your-personal-access-token>Ensure .env.local is ignored by source control before adding the token.
3. Pick a business question
Section titled “3. Pick a business question”Start with astrato_get_context. If it returns a default semantic layer, use that layer. Otherwise,
call astrato_semantic_layer_list and choose one of the returned semanticLayerId values. Pass the
same ID to each subsequent tool that accepts it. The list returns at most 50 entries per page;
follow pagination.nextOffset while pagination.hasMore is true when you need later pages.
Once you have selected a layer, choose a question through the Nash MCP gateway:
astrato_semantic_layer_list_questions— returns IDs and titles.astrato_semantic_layer_get_questionwith an ID — returns the fields used below.
The question supplies the dimension and measure fields used by the chart. Copy their fieldKey
values and the measure aggregation into the constants in the next section.
If MCP is unavailable, you can still choose fields directly from the semantic layer. Continue
through the client and metadata setup in the next section. After
createVisualizationAvailableFields runs, inspect the normalized catalog:
console.log(availableFields);Copy the fieldKey for one dimension-compatible entry and one measure-compatible entry exactly as
returned. Do not construct field keys from display names. For a raw measure field (kind: 'field'),
choose one of its reported aggregations. For a calculation (kind: 'calculation'), leave the
aggregation undefined. Do not chart a field of kind: 'measure': a definition refuses an aggregation
on it, and without one the chart compiles but reports an error when it mounts.
4. The page
Section titled “4. The page”import { createAstratoClient } from '@astrato-io/sdk';import { createVisualizationRuntime, registerAstratoElements, registry, createDefaultVSpec,} from '@astrato-io/visualizations';import { compileVisualizationDefinition, createVisualizationAvailableFields,} from '@astrato-io/visualizations/definitions';import '@astrato-io/visualizations/styles.css';
const tenantId = 'your-tenant-id';const semanticLayerId = 'your-semantic-layer-id';
// From the business question's fields — or any fields in your semantic layer.const dimensionFieldKey = '[6 Productcategory].[Product Category]';const measureFieldKey = '[1 Sales].[Salesamount]';const measureAggregation: string | undefined = 'SUM'; // undefined for a calculation defined in the semantic layer
const client = createAstratoClient({ baseUrl: window.location.origin, // the dev proxy forwards and authenticates /api requests tenantId, visualizationEngine: true,});
// 1. Load field metadata. The remaining steps require it.const fieldMetadata = await client.services.engine.fields.getFieldMetadata?.({ semanticLayerId, tenantId,});if (!fieldMetadata) { throw new Error('Field metadata unavailable — was the client created with visualizationEngine: true?');}const availableFields = createVisualizationAvailableFields(fieldMetadata);
// 2. Compile a chart definition into a vSpec.const compilation = await compileVisualizationDefinition({ definition: { version: 1, type: 'bar', data: { dimensions: [{ ref: 'dimension', fieldKey: dimensionFieldKey }], measures: [ { ref: 'measure', fieldKey: measureFieldKey, ...(measureAggregation ? { aggregation: measureAggregation } : {}), }, ], }, }, availableFields, baseVSpec: createDefaultVSpec('bar'),});if (!compilation.success) { throw new Error(JSON.stringify(compilation.issues, null, 2));}
// 3. Create the runtime and the chart instance.const runtime = createVisualizationRuntime({ client, registry });runtime.createInstance({ id: 'quickstart-chart', type: 'bar', vSpec: compilation.vSpec, dataBinding: { tenantId, semanticLayerId, fieldMetadata, queryContext: { objectId: 'quickstart-chart', tenantId }, },});
// 4. Register the web component. The chart mounts itself.registerAstratoElements({ runtime });<!-- index.html, inside <body> --><astrato-visualization instance-id="quickstart-chart"></astrato-visualization><style> astrato-visualization { display: block; width: 100%; height: 400px; }</style><script type="module" src="/src/main.ts"></script>5. Run it
Section titled “5. Run it”npm run devOpen the printed localhost URL. You should see a bar chart with live data from your semantic layer, queried through Astrato’s governed engine.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause and fix |
|---|---|
| HTTP 401 or 403 on every request | Token missing, expired, revoked, or lacks access — see Authentication. |
unknown_field_key compile issue |
The fieldKey is not in this semantic layer. Copy keys exactly from metadata or the business question. |
Astrato SDK engine service is not configured. Provide an engine service. |
Pass visualizationEngine: true to createAstratoClient. |
| Chart compiles but reports an error when it mounts | The measure may be a field of kind: 'measure'. Use a calculation, or a raw field with an aggregation. |
| Chart area stays empty, warning about container size | The element has zero size. Keep the display: block and explicit height. |
Where to go next
Section titled “Where to go next”- Link two charts with brushing and filters: Interactivity guide.
- Change or restyle the chart with operations: Visualizations reference.
- Inspect or change the data model safely: Semantic layer workflows.
- Query data without a chart: SDK reference.