Docs

Astro

Install

src/layouts/BaseLayout.astro
---
const key = "{{PUBLISHABLE_KEY}}";
---
<html lang="en">
  <head>
    <script is:inline>window.x3=window.x3||function(){(window.x3q=window.x3q||[]).push(arguments)}</script>
    <script is:inline async src={`{{API_ORIGIN}}/v1/s/x3.full.js?k=${key}`} data-key={key}></script>
  </head>
  <body>
    <slot />
  </body>
</html>

is:inline keeps Astro from bundling the tags. With <ClientRouter /> (view transitions) the History API patch reports every transition as an spa pageview; the script runs once.

Verify

  1. npm run dev, open the site, follow a link.
  2. POST {{API_ORIGIN}}/v1/e → 204 in the network tab.
  3. The dashboard's install page shows the pageviews.

Proxy (optional)

With an SSR adapter (Node, Vercel, Netlify, Cloudflare) add an endpoint; static builds use a host-level proxy instead (Cloudflare Worker, Netlify).

npm install {{API_ORIGIN}}/v1/sdk/3x-engine-sdk-0.1.0.tgz
.env
X3_API_ORIGIN={{API_ORIGIN}}
{{PROXY_KEY_ENV}}=sk_3x_YOUR_PROXY_KEY
src/pages{{PROXY_PATH}}/[...path].ts
import type { APIRoute } from "astro";
import { createX3ProxyHandler } from "@3x-engine/sdk/next/proxy";

export const prerender = false;

const handler = createX3ProxyHandler({ apiOrigin: import.meta.env.X3_API_ORIGIN, proxyKey: import.meta.env.{{PROXY_KEY_ENV}} });

export const GET: APIRoute = ({ request }) => handler.GET(request);
export const POST: APIRoute = ({ request }) => handler.POST(request);
export const OPTIONS: APIRoute = ({ request }) => handler.OPTIONS(request);
src/layouts/BaseLayout.astro
<script is:inline async src={`{{PROXY_PATH}}/v1/s/x3.full.js?k=${key}`} data-key={key} data-host="{{PROXY_PATH}}"></script>

The handler only uses the Fetch API, so it runs on every Astro adapter despite the next in its import path.

src/components/ConsentBanner.astro
<div id="consent">
  <button data-consent="granted">Accept</button>
  <button data-consent="denied">Decline</button>
</div>
<script is:inline>
  document.querySelectorAll("#consent [data-consent]").forEach((button) =>
    button.addEventListener("click", () => window.x3("consent", button.dataset.consent)),
  );
</script>

Events

src/components/Cta.astro
<a href="/contact" data-x3-cta data-x3-section="hero">Talk to us</a>
<button data-x3-event="pricing_toggle" data-x3-prop-period="yearly">Yearly</button>

Script alternative: window.x3("track", "pricing_toggle", { period: "yearly" }).