Integration Guides

Phoenix

Add Rybbit analytics to your Phoenix app

Phoenix renders every page inside the root layout, lib/my_app_web/components/layouts/root.html.heex, which owns the <html> and <head> tags, so the snippet goes there. Read the site ID from application config set in config/runtime.exs, so it comes from an environment variable at boot and stays out of the template.

Get your tracking snippet

In your Rybbit dashboard, open Site Settings → Tracking Script and copy your snippet. It looks like this:

<script src="https://app.rybbit.io/api/script.js?siteId=YOUR_SITE_ID" defer></script>

YOUR_SITE_ID is the numeric ID of your site. If you self-host Rybbit, app.rybbit.io is the domain of your own instance.

Add the snippet to Phoenix

Configure the site ID

config/runtime.exs runs when the app boots, including inside a release, so it is the right place to read environment variables. Add a :rybbit key for your app (replace :my_app with your OTP app name):

config :my_app, :rybbit,
  instance_url: System.get_env("RYBBIT_INSTANCE_URL", "https://app.rybbit.io"),
  site_id: System.get_env("RYBBIT_SITE_ID")

Then set the variables where the app runs:

export RYBBIT_INSTANCE_URL=https://app.rybbit.io
export RYBBIT_SITE_ID=YOUR_SITE_ID

Expose the config to the layout

Templates in lib/my_app_web/components/layouts/ are embedded into the MyAppWeb.Layouts module, so a function defined there is callable from root.html.heex. Add one that returns the config:

defmodule MyAppWeb.Layouts do
  use MyAppWeb, :html

  embed_templates "layouts/*"

  def rybbit_config, do: Application.get_env(:my_app, :rybbit, [])
end

Add the tag to the root layout

Open lib/my_app_web/components/layouts/root.html.heex and add the tag inside <head>, after the existing app.css and app.js tags:

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="csrf-token" content={get_csrf_token()} />
  <.live_title suffix=" · Phoenix Framework">{assigns[:page_title] || "MyApp"}</.live_title>
  <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
  <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}></script>

  <script
    :if={rybbit_config()[:site_id]}
    src={"#{rybbit_config()[:instance_url]}/api/script.js?siteId=#{rybbit_config()[:site_id]}"}
    defer
  ></script>
</head>

The :if attribute skips the tag when RYBBIT_SITE_ID is unset, so development and test environments are not tracked unless you set the variable there.

LiveView navigation

<.link navigate>, <.link patch>, push_navigate/2 and push_patch/2 change the URL with history.pushState (or replaceState when replace: true) and keep the root layout, including its <head>, in place. The Rybbit script hooks history.pushState and history.replaceState and listens for popstate and hashchange, so each live navigation is tracked as a pageview while SPA Navigation is enabled in your site settings (the default). No phx: event listener is needed. A regular <.link href> does a full page load, which the script also tracks.

Verify installation

Open your live site in a new tab and click through a few pages. Within a few seconds the pageviews appear in the Rybbit dashboard.

If nothing shows up:

  • View the page source and search for script.js?siteId= to confirm the snippet is on the page.
  • Open the browser Network tab and check that script.js returns 200 and that POST requests go to /api/track.
  • Disable ad blockers, or set up a proxy so the script loads from your own domain.
  • See the script troubleshooting guide for other common causes.

Troubleshooting

  • Tag missing: config/runtime.exs is evaluated at boot, so restart the app or release after changing RYBBIT_SITE_ID. Check with Application.get_env(:my_app, :rybbit) in iex -S mix.
  • Content Security Policy: if you set a content-security-policy header in a plug, add your Rybbit host to script-src and connect-src, or proxy the script through your own domain.
  • Self-hosted instance: set RYBBIT_INSTANCE_URL to your own domain without a trailing slash; the layout appends the script path to it.

Next steps

On this page