anfra docs

Build a data app

Write a data app as one HTML file, run it with anfra serve, and work within its sandbox

A data app is one HTML file in your repo's apps/ folder. It declares its queries, controls and cross-filters through the Anfra global, and renders the results with its own HTML, CSS and JavaScript. anfra supplies the data and the state. Every pixel is yours.

Build it with a coding agent

The anfra-build-data-app skill teaches a coding agent the steps on this page: learn the datasets, agree on a design, write and test the queries, then write and check the app. Install it with anfra agent setup, or npx skills add holistics/anfra-skills -a <agent-id> -g -y for agents without plugin support.

The file

  • Put the file anywhere under apps/. Subfolders group apps in the list.
  • The list shows each app by its <title>.
  • apps/sales/overview.html opens at /apps/sales/overview.

Most apps follow the same order, as in the example app:

  1. Create the app with Anfra.createApp(). See Environment.
  2. Declare every query and control, then connect them with control mappings and cross-filters.
  3. app.subscribe(render), where render draws each query from its state and result.
  4. app.execute().
  5. Wire inputs and clicks to set controls or the selection, then call app.execute() again.

Look up dataset and field names with anfra show, and run each query with anfra query before you put it in the page.

Run it

anfra status   # is a server already running for this repo, and where?
anfra serve    # if not, start one

Open http://127.0.0.1:7878/apps/, or the address anfra serve prints if that port is taken. See anfra serve.

While it runs:

  • The open app reloads when its file changes. When an .aml file changes, the datasets reload and the app remounts.
  • The header shows the repo's problems, such as AML that does not compile or a data source that is not configured.
  • The Inspect panel has two tabs. Structure shows the parts you marked as a tree, with any markup it could not read. Data shows each query's state, executed AQL and SQL, and error. See Debug information.
  • The toolbar and the Inspect panel save a PNG or PDF of the whole page, or of any block or container. See Render.

Read the console of the app's frame, not just the page's. A ValidationError from a declaring line surfaces there.

anfra serve listens on 127.0.0.1 by default, so the URL opens only on your machine. It has no authentication, so think before serving it beyond loopback with --addr.

The sandbox

The app runs in a sandboxed frame (sandbox="allow-scripts", with an opaque origin), under a content policy that limits what it may load and where it may send requests. The app has the reader's query results in hand, so the policy keeps its code from sending them anywhere unexpected. Write for it:

  • Data comes only from the SDK. The frame can't call anfra's API itself, and fetch, XHR and WebSocket to any other server are blocked.

  • The app is one HTML file. Nothing it loads may be another file in your repo, so no relative src or href such as ./chart.js or logo.png. Put your own scripts and styles inline, and images inline as SVG or a data: URI.

  • Load libraries, styles and fonts only from jsDelivr or cdnjs, by their https:// URL with a pinned version. Anything from another origin is blocked, including esm.sh and unpkg. Google Fonts is allowed too, but its CSS can't carry an integrity hash, so prefer the @fontsource packages on jsDelivr (see below). Load an npm package from jsDelivr (https://cdn.jsdelivr.net/npm/<package>@<version>/<file>), and an ES module from its +esm build (https://cdn.jsdelivr.net/npm/<package>@<version>/+esm). Images may come from any https:// server.

  • Give every CDN script and stylesheet an integrity hash, so a library changed at its CDN fails to load rather than running beside your reader's data:

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"
            integrity="sha384-…" crossorigin="anonymous"></script>

    jsDelivr shows each file's hash on its page. For another file, run curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A and put sha384- before the output. Load fonts from @fontsource on jsDelivr rather than Google Fonts' CSS, which differs per browser and so can't be hashed: anfra validate warns about a Google Fonts stylesheet for that reason.

  • No storage. localStorage, sessionStorage and cookies throw. Keep state in variables.

  • No forms, popups, navigation, nested frames or <base>. Use buttons and change listeners, never <form>.

anfra validate warns about a file the app references from the repo, and about a CDN script or stylesheet without an integrity hash.

Allowing another origin

To load from another origin, such as your company's own CDN, list it in your repo's .anfra/config.yml:

apps:
  allow_origins:
    - https://cdn.acme.com

Each entry is an https:// origin with nothing after it. anfra serve adds these origins for scripts, styles, fonts and requests. They aren't published: anfra Cloud keeps its own list, which an Admin edits in the organization's settings, and anfra publish warns about any app origin the organization doesn't allow.

Make the numbers trustworthy

  • Mark the parts of the page with data-anfra-block and data-anfra-container, so the Inspect panel can show them and the host can render them.
  • Render all four states of each query: loading, error, a result, and an empty result in words. See Results.
  • On each query's first success, log result.columns, result.rows[0] and result.debug?.executedAql to confirm the row keys, value types and the AQL that ran.
  • Put the definitions, dates and rounding a reader needs on the page.
  • If a number has no field or metric, add it to the semantic layer rather than computing it in JavaScript.
  • Add interactions: add a filter, a date drill and a cross-filter, step by step.
  • Example app: a complete file to start from.
  • Queries: the rules for a query's AQL.
  • Errors: what each error means and what to do.
  • Plugins: export, lineage and rendering, where the host installs them.

On this page