Skip to content

One tag, one SDK, one project.

Four things to wire up, none of them longer than a few lines. Everything reports into the same project and lands on the same dashboard.

Sources

one project
web
downloads
app
errors
logs
feedback

Setup

Four lines of wiring, then it is reporting.

There is no pipeline to stand up and no warehouse to point at. The tag and the SDK talk to one endpoint, and the endpoint writes to one database.

  1. 01 web

    Add the tag to the site

    One script tag on the marketing site. Vanilla TypeScript, under 3KB gzipped, no dependencies and no second request. It writes nothing and sends nothing until consent is given.

    <script defer
      src="https://…/t.js"
      data-key="fr_web_…"></script>
  2. 02 app

    Initialise the SDK in the app

    One call at start-up. From there the same client carries product events, exceptions, logs and feedback, so there is one thing to configure rather than four.

    firstrun.init({
      key: "fr_app_…",
      release: "2.7.0",
    })
  3. 03 app

    Send whatever you want to count

    A typed envelope with your own name and properties. Exceptions and log lines go through the same path, so an event queued on a laptop with no network survives the restart and arrives later.

    firstrun.track("project.created", {
      template: "blank",
    })
  4. 04 app

    Drop in the feedback widget

    A reporter that attaches the running release, the platform and the last exception before it was opened, so what lands is a ticket rather than the start of a conversation about which build they are on.

    firstrun.feedback.open()

Recording

How a thing that happened becomes a row.

The rules below are enforced by tests rather than by convention, which is the only reason they are worth printing on a marketing page.

  • Client time is the real time

    A laptop is offline, the OS kills the process, the queue replays three days later. The event still lands in the day it happened. Nothing is bucketed on arrival time.

  • One envelope for everything

    A product event, an exception and a log line are the same shape with a different type. That is why one SDK call configures all of them, and why the retry queue does not care which is which.

  • The release travels with the event

    Every record carries the version it came from, so adoption, retention and crash rate are all cut by the same field rather than reconstructed from timestamps afterwards.

  • Ingestion fails closed

    A plan that runs out or a key that is wrong stops ingestion and says so. It does not sample quietly, and it never deletes what has already landed.

Privacy

Small enough to hold, on purpose.

The less a tag has to collect, the fewer promises there are to keep. This one collects little enough that self-hosting removes the last of them.

  • Nothing before consent

    The web tag holds no storage and sends no request until consent is given. Refusing it is a no-op, not a degraded mode with a cookie in it.

  • IPs are hashed with a salt you own

    A raw address is never written. Self-hosted, the salt lives in your environment and never leaves it, so the hashes are not reversible by us, because we do not have it.

  • No fingerprinting

    No device fingerprint, no cross-site identifier, no session replay. A visitor id is a first-party value scoped to your own domain and nothing else.

  • No third parties, anywhere

    No CDN, no font host, no tag manager. The page you are reading fetches its typeface from its own origin for exactly the same reason.


Read the part that does it.