By default every Pug SDK sends data to Pug Cloud (https://api.pugs.dev). When you self-host, you change one thing: point the SDK’s endpoint at your own pug server. Nothing else about the API changes - same methods, same events. Keys don’t carry over from Pug Cloud: your instance issues its own, so create a project in your dashboard and use its project ID and keys.
Use the public URL of your server (the TLS-terminated origin in front of port 3000, e.g. https://api.example.com), not the internal :3000 address.
The one setting, per SDK
It’s the same option, endpoint, on every SDK (Go spells it Endpoint, and the HTTP API just uses the request URL):
| SDK | Package | Option | Default |
|---|---|---|---|
| Browser | @pug-sh/browser |
endpoint |
https://api.pugs.dev |
| Android | sh.pug:android |
endpoint |
https://api.pugs.dev |
| Flutter | pug_flutter |
endpoint |
https://api.pugs.dev |
| Node (server) | @pug-sh/node |
endpoint |
https://api.pugs.dev |
| Go (server) | github.com/pug-sh/sdk-go |
Endpoint |
https://api.pugs.dev |
| Ruby (server) | pug-sh |
endpoint |
https://api.pugs.dev |
| HTTP API | - | request URL | https://api.pugs.dev |
Browser
import { init } from '@pug-sh/browser'
init('YOUR_PROJECT_ID', {
apiKey: 'pub_YOUR_PUBLIC_KEY',
endpoint: 'https://api.example.com', // your pug server
})For environment-aware config (e.g. Vite), fall back to localhost in dev:
init(import.meta.env.VITE_PUG_PROJECT_ID, {
apiKey: import.meta.env.VITE_PUG_PUBLIC_KEY,
endpoint: import.meta.env.DEV ? 'http://localhost:3000' : 'https://api.example.com',
})The one-tag CDN install takes it as data-endpoint="https://api.example.com" - see One-tag install.
Android
import sh.pug.android.Options
import sh.pug.android.Pug
Pug.init(this, "YOUR_PROJECT_ID", Options("pub_your_public_key") {
endpoint = "https://api.example.com" // your pug server
})Pug.init throws if the endpoint isn’t an http(s) URL. Android blocks cleartext
HTTP for apps targeting API 28 or higher, so an http:// endpoint - a local
server at http://10.0.2.2:3000 from the emulator, say - also needs cleartext
allowed, ideally in a debug-only manifest or network security config.
Flutter
import 'package:pug_flutter/pug_flutter.dart';
await Pug.init(
'YOUR_PROJECT_ID',
const PugOptions(
apiKey: 'pub_your_public_key',
endpoint: 'https://api.example.com', // your pug server
),
);Node (server-side)
The Node SDK takes the same endpoint option and authenticates with your private key (prv_...):
import { Pug } from '@pug-sh/node'
const pug = new Pug({
apiKey: process.env.PUG_PRIVATE_KEY!, // prv_... - server-side only
endpoint: 'https://api.example.com', // your pug server
})Go (server-side)
The Go SDK takes the same setting as Endpoint and authenticates with your private key (prv_...):
import pug "github.com/pug-sh/sdk-go"
client, err := pug.New(pug.Options{
APIKey: os.Getenv("PUG_PRIVATE_KEY"), // prv_... - server-side only
Endpoint: "https://api.example.com", // your pug server
})
if err != nil {
log.Fatal(err)
}pug.New returns an error for an Endpoint that isn’t an absolute http:// or https:// URL.
Ruby (server-side)
The Ruby SDK takes the same endpoint option and authenticates with your private key (prv_...):
require "pug"
pug = Pug::Client.new(
api_key: ENV.fetch("PUG_PRIVATE_KEY"), # prv_... - server-side only
endpoint: "https://api.example.com", # your pug server
)It doesn’t follow redirects, so use the URL your server answers on: an http:// origin that redirects to https:// fails every request as unimplemented, and dropped events go to on_error.
HTTP API
If you call the API directly, just swap the host in the request URL - the paths are unchanged. The key identifies the project, so the body carries only the events (HTTP API):
curl -X POST https://api.example.com/sdk.events.v1.EventsService/BatchCreate \
-H 'Content-Type: application/json' \
-H 'Connect-Protocol-Version: 1' \
-H 'x-api-key: pub_YOUR_PUBLIC_KEY' \
-d '{ "events": [ /* ... */ ] }'Notes
- No SDK-specific CORS setup is needed on the server. The events and profiles ingest services accept any origin (auth lives entirely in the
x-api-keyheader). Only the dashboard origin needs listing inPUG_CORS_ORIGINS- see Dashboard. - Key types are unchanged. Public keys (
pub_...) for the browser, Android and Flutter SDKs, private keys (prv_...) for the Node, Go and Ruby SDKs and server-to-server reads. See Authentication. - Verify it works: open your self-hosted dashboard’s Live view - an event fired from a repointed SDK should appear within ~15 seconds. If requests fail with a network error, confirm the endpoint is your public HTTPS origin and that TLS terminates in front of the server.
For the full initialization options (batching, sessions, auto-capture, consent), see the SDKs reference.
Further reading
- Dashboard: the dashboard’s own
VITE_API_BASE_URLendpoint setting - Deployment: stand up the server your SDKs point at
- Development: point SDKs at a local
pug dev