# track

sdk.track() records a game event with up to 20 properties; the SDK batches them and they appear in your statistics.

## `track(event, props?): void`

`event` is `snake_case`, at most 40 characters (`^[a-z][a-z0-9_]{0,39}$`). `props` holds at most 20
properties with string, number or boolean values, each at most 200 characters once written. The call
returns nothing and never throws after `init()`.

<CodePanel title="Record an event">
  <Fragment slot="javascript">
    ```js
    sdk.track('level_complete', { level: 12, moves: 48, perfect: true });
    sdk.track('shop_open');
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    import type { TrackProps } from '@filudi/game-sdk';

    const props: TrackProps = { level: 12, moves: 48, perfect: true };
    sdk.track('level_complete', props);
    sdk.track('shop_open');
    ```

  </Fragment>
</CodePanel>

## Where events go

The SDK batches: 50 events or 5 s, whichever comes first, then `POST /v1/sdk/events` on filudi.com with
the game token. On `pagehide` and when the page becomes hidden it flushes with a `keepalive` request so the
last batch leaves with the page. Each item carries what the SDK knows: `gameId`, `gameVersion` (the
`version` you passed to `init()`), `host`, `locale`, a `sessionId` per page load and a timestamp.

On portals, events go nowhere by default. `init({ events: 'always', eventsUrl })` sends them to the URL you
give, the Filudi API's `/v1/sdk/events`; no token exists there, so the API counts them as anonymous for the
game's site.

<CodePanel title="Events from portals too">
  <Fragment slot="javascript">
    ```js
    await sdk.init({
      gameId: 'gloop',
      version: '1.4.0',
      events: 'always',
      eventsUrl: 'https://api.filudi.com/v1/sdk/events',
    });
    ```

  </Fragment>
  <Fragment slot="typescript">
    ```ts
    await sdk.init({
      gameId: 'gloop',
      version: '1.4.0',
      events: 'always',
      eventsUrl: 'https://api.filudi.com/v1/sdk/events',
    });
    ```

  </Fragment>
</CodePanel>

## What to track

Events are for what the console cannot see on its own. Sessions, saves and scores are already counted
from the SDK calls that make them. Track the decisions inside your game: a level cleared, a difficulty
chosen, a shop opened, a tutorial skipped. Keep the set small and stable across versions; a renamed event
starts a new line in the statistics.

`frame_loaded` is reserved: the hub sends it when your bundle calls `ready()`, and the API refuses it from
a game. Every other name is yours.

## Unavailable cases

`track()` never reports one. An event that breaks the limits is dropped with a debug log line
(`init({ debug: true })` or `?filudi_debug=1`). On the mock, events are kept in memory unless
`mock.eventsUrl` points somewhere. What the console shows for your events is in
[events and statistics](/docs/games/events-and-statistics/).
