Skip to content

Externally hosted app quickstart

5 min read · Updated 2 Nov 2026

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.

  • 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. Browser baseUrl remains 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.
Terminal window
npm create vite@latest astrato-quickstart -- --template vanilla-ts
cd astrato-quickstart
npm install @astrato-io/sdk @astrato-io/visualizations

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.

vite.config.ts
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.

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:

  1. astrato_semantic_layer_list_questions — returns IDs and titles.
  2. astrato_semantic_layer_get_question with 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.

src/main.ts
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>
Terminal window
npm run dev

Open the printed localhost URL. You should see a bar chart with live data from your semantic layer, queried through Astrato’s governed engine.

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.