Skip to content
>GLB_
Go back

PostHog across subdomains and projects: identity, multiple instances and client rate limits

· Updated

Once a site grows into a family of subdomains (marketing site, docs, events portal, blog), PostHog setup raises three questions: does every subdomain need the snippet, how do you keep one user as one person across them, and when (if ever) should events go to more than one PostHog project. A fourth question shows up in production: what the client_ingestion_warning event means and whether it is losing data. This guide answers all four.

Every frontend must load PostHog

PostHog only captures events from pages where its JavaScript snippet or SDK actually runs:

This isn’t specific to PostHog; it’s how client-side analytics works.

If the main domain and a subdomain are deployed independently, both need PostHog initialization. If they share a base template or layout, initializing once in that shared layer may be enough. But that is an architectural shortcut, not an analytics feature: every page that should be tracked must load the client.

In most cases, use one PostHog project for every site that is part of the same user journey: main website, help center, events platform, member portal. One project lets you answer cross-site questions:

Splitting each subdomain into its own project makes these questions harder. Only do it if there’s a strong governance or data-isolation reason (see when multiple projects make sense).

Example: geeklogbook.com and blog.geeklogbook.com

This site runs exactly this setup. geeklogbook.com and blog.geeklogbook.com are separate deployments, and both initialize PostHog with the same project key. The blog (an Astro site) also tags its events with a site property and sends pageviews itself, because client-side page transitions don’t trigger full page loads:

posthog.init('YOUR_PROJECT_API_KEY', {
  api_host: 'https://us.i.posthog.com',
  defaults: '2026-01-30',
  person_profiles: 'identified_only',
  capture_pageview: false,
  loaded: function(posthog) {
    posthog.register({ site: 'blog' });
  }
});
document.addEventListener('astro:page-load', function() {
  posthog.capture('$pageview');
});

Neither site sets cross_subdomain_cookie explicitly. Setting it (next section) removes any dependence on the SDK default.

Keep one identity across subdomains

A user who starts on one subdomain and moves to another can show up as two people if the identity cookie is scoped to each subdomain. In posthog-js the relevant setting is cross_subdomain_cookie:

<script>
  posthog.init('YOUR_PROJECT_API_KEY', {
    api_host: 'https://us.i.posthog.com',
    cross_subdomain_cookie: true,
    persistence: 'localStorage+cookie'
  })
</script>

With this, PostHog stores identification data at the parent-domain level, which keeps the same identity across related subdomains. In many setups that is enough.

Tell subdomains apart in reports

In one shared project you still need to filter by site. The simplest way is to register the hostname as a super property, sent with every event:

posthog.register({
  hostname: window.location.hostname
})

Dashboards, funnels and event streams can then filter on values like www.example.com, events.example.com or docs.example.com.

A business-level property is more readable when hostnames are technical, or when several domains map to one category:

posthog.register({ site_section: 'events' })

When journeys still break

Even with shared cookies, cross-site journeys can look wrong:

The usual causes are cookie policies, browser behavior, inconsistent initialization, or complex navigation between sites. Check:

Validate by navigating from one subdomain to another and watching the live events view.

When multiple projects make sense

Some architectures do need one website to send events to several PostHog projects:

Don’t use multiple projects just to get more properties or attributes. One project with richer event properties is the right design for that.

Named instances

posthog-js supports several SDK instances on one page. The third argument of posthog.init names the instance, and each instance sends to its own project with its own API key:

// Default instance: project A
posthog.init('PROJECT_A_API_KEY', {
  api_host: 'https://us.i.posthog.com',
  autocapture: false,
  capture_pageview: false
});

// Named instance: project B
posthog.init('PROJECT_B_API_KEY', {
  api_host: 'https://us.i.posthog.com',
  autocapture: false,
  capture_pageview: false
}, 'project_b');

posthog.capture('signup_completed');            // goes to project A only
posthog.project_b.capture('signup_completed');  // goes to project B only

Events are not copied between instances. Each call goes only to the instance you call it on, so you must send an event to each instance explicitly.

Rules for multiple instances

  1. Disable automatic capture. With autocapture and automatic pageviews on, every instance sends its own pageviews and clicks, which duplicates or skews data across projects. Send only the events you define.
  2. Use the same distinct ID. To recognize the same user in both projects, identify them with the same ID on every instance (posthog.identify(userId) and posthog.project_b.identify(userId)). Otherwise the same person appears as different users in each project.
  3. Duplicate critical events on the backend. For business-critical events (payments, signups, conversions), send the event once from the frontend to your backend, and have the backend send it to project A and project B through PostHog’s capture API. That gives you retries, validation, logging, idempotency and better governance than two browser calls.

client_ingestion_warning: the client-side rate limit

With the default posthog-js configuration you may see this in the browser console:

posthog-js client rate limited.
Config is set to 10 events per second and 100 events burst limit.

and this event in the PostHog UI:

client_ingestion_warning

It means the SDK’s client-side rate limiter fired. The SDK limits how fast a browser can send events, by default:

Sending more than 10 events per second, or more than 100 in a burst, triggers it (JS SDK configuration).

Are you losing data?

Possibly. When the limiter is active:

  1. Some events may be delayed.
  2. Some events may be dropped.
  3. If the page unloads before the queue flushes, events may be lost for good.

If the warnings come from a single distinctId, the issue is probably one user session or an automated interaction, not a systemic failure.

Why it happens

Normal user interaction rarely exceeds 10 events per second. Typical causes:

Find the cause

Turn on debug mode in the browser console:

posthog.debug(true)

Then use the app and watch event frequency, repeated patterns and capture loops. In PostHog, inspect the event timeline for the affected distinctId.

Raising the limit

You can override the defaults:

posthog.init('YOUR_PROJECT_KEY', {
  api_host: 'https://us.i.posthog.com',
  rate_limiting: {
    events_per_second: 20,
    events_burst_limit: 200
  }
})

Don’t start here. If the warning comes from a logic error, a higher threshold only hides it. Find the excessive event generation, fix the frontend code, and only then consider changing the limits.

Note that PostHog’s ingestion warnings documentation doesn’t list client_ingestion_warning by name; the behavior is documented under SDK configuration. A GitHub issue references this exact warning: PostHog/posthog#22213.

Gotchas


Share this post:

Related Posts


Previous Post
Tracking S3 file changes with ETags and LastModified
Next Post
Working with JSON in Python and pandas: json.load, JSON Lines and read_json