Instrument and configure the Node.js SDK

이 페이지는 아직 귀하의 언어로 번역되지 않았습니다. 현재 작업 중이므로 곧 다시 확인해 주십시오.

After your first event with the Node.js SDK (@amplitude/analytics-node), use these tasks to track events and properties, identify users, change events with plugins, tune how the SDK uploads, and flush events from short-lived processes. To install the SDK, go to Get started. For every signature and option, go to the Node.js SDK reference. For changes between versions, go to the Node.js SDK release notes.

The examples import named functions, which act on a default instance. Each instance from createInstance() has the same methods.

Track events

Amplitude's HTTP V2 API receives the events, so every event needs an event_type and a user_id or device_id. For field rules, go to the HTTP V2 API.

Track a custom event

Call track() with an event name, optional event properties, and the event options that identify the user:

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

track("Button Clicked", { buttonColor: "primary" }, { user_id: "user-12345" });

Pass the event as an object

Pass an object to set any event field directly, such as groups or user_properties:

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

track({
  event_type: "Report Exported",
  event_properties: { format: "csv" },
  groups: { orgId: "15" },
  user_id: "user-12345",
});

If you also pass event properties as the second argument, they replace the object's event_properties. The two sets don't merge.

Set event fields for one call

The third argument takes any event field, such as device_id, time, insert_id, ip, country, or app_version. For the full list, go to EventOptions.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

track(
  "Order Shipped",
  { orderId: "ord-1001" },
  {
    user_id: "user-12345",
    device_id: "device-abcdef",
    time: Date.now() - 60_000, // when the event happened, in milliseconds
    insert_id: "order-shipped-ord-1001", // Amplitude deduplicates on this
    ip: "203.0.113.7",
  },
);

The SDK sets time and insert_id when you don't. It always overwrites event_id and library.

Set user properties

Build an Identify object, chain operations on it, and send it with identify(). The SDK sends an $identify event that updates the user's properties without counting as an action.

js
import { init, identify, Identify } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

const props = new Identify()
  .set("plan", "pro")
  .setOnce("initial_plan", "starter")
  .add("login_count", 1)
  .append("visited_regions", "us-west")
  .unset("trial_ends_at");

identify(props, { user_id: "user-12345" });
json
{
  "event_type": "$identify",
  "user_id": "user-12345",
  "user_properties": {
    "$set": { "plan": "pro" },
    "$setOnce": { "initial_plan": "starter" },
    "$add": { "login_count": 1 },
    "$append": { "visited_regions": "us-west" },
    "$unset": { "trial_ends_at": "-" }
  }
}

Each property takes one operation per Identify object. The SDK silently skips a second operation on the same property, so new Identify().set("plan", "pro").setOnce("plan", "free") sends only the set. After clearAll(), the SDK skips every other operation on that object. The SDK drops values that aren't valid properties, such as null, undefined, functions, and nested arrays, without a log message.

User property changes apply to events that arrive after the $identify event. The user's profile shows the change right away, but charts reflect it only on later events. For more, go to User properties and events.

Assign a user to a group

Call setGroup() with a group type and one or more group names. For example, a user in organization 15:

js
import { init, setGroup } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

setGroup("orgId", "15", { user_id: "user-12345" });

// A user can belong to more than one group of a type.
setGroup("sport", ["tennis", "soccer"], { user_id: "user-12345" });

setGroup() sends an $identify event that sets the group type as a user property, such as orgId: "15", and replaces any earlier value of that property. In charts, you can then count distinct groups, such as organizations, that performed an event.

To associate one event with a group without changing the user's groups, set groups on that event. Go to Pass the event as an object.

Set group properties

Call groupIdentify() with a group type, a group name, and an Identify object. The changes apply to the group, not the user, and affect only later events.

js
import { init, groupIdentify, Identify } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

const groupProps = new Identify().set("plan", "enterprise").set("seats", 250);

groupIdentify("orgId", "15", groupProps, { user_id: "user-12345" });

Track revenue

Build a Revenue object and send it with revenue(). The SDK sends a revenue_amount event that Amplitude's revenue charts read.

js
import { init, revenue, Revenue } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

const purchase = new Revenue()
  .setProductId("com.example.pro-annual")
  .setPrice(3.99)
  .setQuantity(3)
  .setRevenueType("purchase")
  .setEventProperties({ coupon: "SPRING" });

revenue(purchase, { user_id: "user-12345" });
json
{
  "event_type": "revenue_amount",
  "user_id": "user-12345",
  "event_properties": {
    "coupon": "SPRING",
    "$productId": "com.example.pro-annual",
    "$quantity": 3,
    "$price": 3.99,
    "$revenueType": "purchase"
  }
}

Amplitude computes revenue as price times quantity, unless you call setRevenue(). The SDK doesn't require a price: a Revenue object without setPrice() sends $price: 0. setQuantity() ignores values of 0 or less and keeps the default of 1.

Send events to more than one project

Create one instance per Amplitude project with createInstance(). Each instance has its own API key, options, queue, and plugins.

js
import { createInstance } from "@amplitude/analytics-node";

const billing = createInstance();
billing.init("PROJECT_A_API_KEY");

const product = createInstance();
product.init("PROJECT_B_API_KEY", { flushQueueSize: 50 });

billing.track("Invoice Paid", { amount: 42 }, { user_id: "user-12345" });
product.track("Invoice Paid", { amount: 42 }, { user_id: "user-12345" });

The named functions, such as track(), act on a separate default instance. The instanceName option labels an instance but doesn't link instances together.

Identify users

How the SDK identifies users

The Node.js SDK sends events on behalf of many users, so it doesn't store a user ID, device ID, or session. Pass user_id, device_id, or both with every call, in the last argument. Amplitude rejects an event that has neither, with 400 and Request missing required field. The SDK doesn't check this before it sends the event.

If your server handles requests from a browser or app that also runs an Amplitude SDK, pass that client's device_id and session_id to the server. Set them on server events so that Amplitude joins server events to the same user and session.

Allow shorter IDs

Amplitude rejects user IDs and device IDs shorter than five characters, with Invalid id length for user_id or device_id. To accept shorter IDs, set minIdLength. The SDK sends the value to Amplitude with each upload. It doesn't check ID length itself.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY", { minIdLength: 3 });

track("Button Clicked", undefined, { user_id: "u42" });

Opt out of tracking

setOptOut(true) stops the instance from sending events. It applies to the whole instance, not to one user, and the setting lasts only for the life of the process.

js
import { init, setOptOut, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

setOptOut(true);
track("Button Clicked", undefined, { user_id: "user-12345" }); // not sent

setOptOut(false); // later events are sent again

While opt-out is on, track() and the other tracking methods resolve with 0 and Event skipped due to optOut config. To skip events for individual users who opted out, check your own consent record before you call track(), or drop their events in a plugin.

Plugins

Plugins change events before the SDK sends them, or send them to other destinations. A plugin is an object with a type, a name, and an execute() function. Import TypeScript types for plugins from the Types namespace of @amplitude/analytics-node.

How plugins run

For each event, the SDK runs plugins in three stages:

  1. before plugins: Run in the order you added them. The built-in context plugin is a before plugin. It sets time and insert_id when they're missing, and always sets event_id and library.
  2. enrichment plugins: Run in the order you added them. A plugin without a type is an enrichment plugin.
  3. destination plugins: Run all at once, each with its own copy of the event. The built-in amplitude destination uploads to Amplitude.

A before or enrichment plugin returns the event, changed or not, or null to drop it. Plugins you add before init() finishes run ahead of the context plugin, so those plugins don't see time, insert_id, or library yet. To see those fields, add the plugin after await init(...).promise, or use an enrichment plugin.

track() resolves with the result of the first destination plugin the instance registered. Add your own destinations after init() resolves, so the result reflects Amplitude's response.

Add or remove a plugin

js
import { init, add, remove, track } from "@amplitude/analytics-node";

await init("AMPLITUDE_API_KEY").promise;

const tagEnvironment = {
  name: "tag-environment",
  type: "enrichment",
  execute: async (event) => {
    event.event_properties = {
      ...event.event_properties,
      environment: "production",
    };
    return event;
  },
};

add(tagEnvironment);
track("Button Clicked", undefined, { user_id: "user-12345" });

// Later: remove the plugin by name.
remove("tag-environment");

Each plugin's name must be unique on the instance. The SDK skips a plugin whose name is already registered and logs Plugin with name tag-environment already exists, skipping registration.

Change events before other plugins

A before plugin runs ahead of every enrichment and destination plugin. This example plugin lowercases user_id, so later plugins and Amplitude see the normalized value:

ts
import { add, init, track, Types } from "@amplitude/analytics-node";

const normalizeUserId: Types.BeforePlugin = {
  name: "normalize-user-id",
  type: "before",
  execute: async (event: Types.Event) => {
    if (event.user_id) {
      event.user_id = event.user_id.toLowerCase();
    }
    return event;
  },
};

add(normalizeUserId);
init("AMPLITUDE_API_KEY");

track("Button Clicked", undefined, { user_id: "User-12345" });

Enrich events

An enrichment plugin is a class or object with an execute() method. The optional setup(config, client) runs once, when you add the plugin or when init() finishes, whichever is later. Use it to read the configuration or prepare state.

ts
import { add, init, track, Types } from "@amplitude/analytics-node";

class ServiceInfoPlugin implements Types.EnrichmentPlugin {
  name = "service-info";
  type = "enrichment" as const;
  private region = "unknown";

  async setup(config: Types.IConfig) {
    this.region = config.serverZone === "EU" ? "eu" : "us";
  }

  async execute(event: Types.Event) {
    event.event_properties = {
      ...event.event_properties,
      service: "billing-worker",
      amplitude_region: this.region,
    };
    return event;
  }
}

await init("AMPLITUDE_API_KEY").promise;
add(new ServiceInfoPlugin());

track("Job Completed", { jobId: "job-77" }, { user_id: "user-12345" });

Drop events

Return null from a before or enrichment plugin to drop an event. The dropped call resolves with code 0 and an empty message.

js
import { init, add, track } from "@amplitude/analytics-node";

await init("AMPLITUDE_API_KEY").promise;

add({
  name: "drop-health-checks",
  type: "enrichment",
  execute: async (event) =>
    event.event_type === "Health Check" ? null : event,
});

track("Health Check", undefined, { user_id: "user-12345" }); // dropped
track("Button Clicked", undefined, { user_id: "user-12345" }); // sent

Send events to another destination

A destination plugin receives a copy of every event and must return a result with event, code, and message. This example plugin posts each event to your own endpoint with the global fetch():

ts
import { add, init, track, Types } from "@amplitude/analytics-node";

class WebhookDestination implements Types.DestinationPlugin {
  name = "webhook-destination";
  type = "destination" as const;

  constructor(private readonly url: string) {}

  async execute(event: Types.Event): Promise<Types.Result> {
    try {
      const response = await fetch(this.url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(event),
      });
      return { event, code: response.status, message: response.statusText };
    } catch (error) {
      return { event, code: 0, message: String(error) };
    }
  }
}

await init("AMPLITUDE_API_KEY").promise;
add(new WebhookDestination("https://collector.example.com/events"));

track("Button Clicked", undefined, { user_id: "user-12345" });

Data delivery

How the SDK uploads events

The SDK queues events in memory and uploads them in the background:

  • Timing: When an event enters an empty queue, the SDK schedules an upload flushIntervalMillis later (10 seconds by default). The pending upload keeps the Node.js process alive until it runs.
  • Batch size: Each request holds up to flushQueueSize events (200 by default). The SDK sends larger queues as several requests, one after another. Reaching flushQueueSize doesn't start an upload early.
  • Storage: The queue lives only in memory. The SDK loses events that haven't uploaded when the process exits or crashes, unless you set a storageProvider.
  • Compression: The SDK sends plain JSON and doesn't compress request bodies.

Scheduled uploads retry failures. flushMaxRetries (12 by default) is the total number of attempts for an event, including the first.

An upload you start with flush() works differently: it makes one attempt per batch and doesn't retry. Events in a retry backoff stay queued for the next scheduled upload. If a scheduled upload is still in progress, flush() does nothing and resolves.

Tune upload performance

Raise flushQueueSize to send fewer, larger requests. Lower flushIntervalMillis to send events sooner.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY", {
  flushQueueSize: 500, // events per request
  flushIntervalMillis: 2000, // upload 2 seconds after the first queued event
  flushMaxRetries: 5, // attempts per event, including the first
});

track("Button Clicked", undefined, { user_id: "user-12345" });

0 isn't a valid value for flushQueueSize or flushMaxRetries: the SDK uses the default instead.

Choose a data region

If your Amplitude project is in the EU data center, set serverZone to "EU". The SDK then sends to https://api.eu.amplitude.com/2/httpapi. Use the API key from your EU project.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY", { serverZone: "EU" });

track("Button Clicked", undefined, { user_id: "user-12345" });

Send large batches to the Batch API

For high-volume backfills, set useBatch to true. The SDK then sends to the Batch Event Upload API at https://api2.amplitude.com/batch, or https://api.eu.amplitude.com/batch with serverZone: "EU". Batching, intervals, and retries stay the same.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY", { useBatch: true, flushQueueSize: 1000 });

track("Record Imported", { source: "crm" }, { user_id: "user-12345" });

Send events through a proxy

Set serverUrl to your proxy's URL. A custom serverUrl replaces the URL that serverZone and useBatch select. Point serverUrl at the endpoint you want, and have the proxy forward requests to Amplitude unchanged.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY", {
  serverUrl: "https://analytics-proxy.example.com/2/httpapi",
});

track("Button Clicked", undefined, { user_id: "user-12345" });

The SDK makes its own HTTPS requests with Node.js's https module and doesn't read HTTP_PROXY or HTTPS_PROXY itself. To send through a forward proxy that requires those settings, use a custom HTTP client.

Set a request timeout

The SDK aborts an upload that takes longer than requestTimeoutMillis (10 seconds by default) and retries it like a 408 response. The timeout is a deadline for the whole request, not an idle timer, so a slow response that keeps sending data still times out. Requires 1.5.70 or later. Earlier versions have no request timeout.

js
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY", { requestTimeoutMillis: 5000 });

track("Button Clicked", undefined, { user_id: "user-12345" });

requestTimeoutMillis applies only to the built-in HTTP client. With transportProvider, your client handles timeouts.

Use a custom HTTP client

Set transportProvider to replace the built-in HTTP client, for example to add headers or send through a proxy agent. Extend BaseTransport from @amplitude/analytics-core, which the SDK installs as a dependency, and convert the server's JSON response with buildResponse(). If your package manager doesn't hoist dependencies, add @amplitude/analytics-core to your own package.json.

ts
import { init, track } from "@amplitude/analytics-node";
import {
  BaseTransport,
  type Payload,
  type Response,
} from "@amplitude/analytics-core";

class HeaderTransport extends BaseTransport {
  async send(serverUrl: string, payload: Payload): Promise<Response | null> {
    try {
      const response = await fetch(serverUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Request-Source": "billing-worker",
        },
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(10_000),
      });
      return this.buildResponse(await response.json());
    } catch {
      return null; // the SDK drops the events without retrying
    }
  }
}

init("AMPLITUDE_API_KEY", { transportProvider: new HeaderTransport() });

track("Button Clicked", undefined, { user_id: "user-12345" });

Return null only when the request never reached a server. To make the SDK retry, return this.buildResponse({ code: 408 }) instead.

Keep unsent events across restarts

By default, the SDK keeps its queue only in memory. To keep unsent events when the process restarts, set storageProvider to an object that implements the Storage interface. The SDK saves the queue under the key AMP_unsent_ plus the first 10 characters of your API key, and resends saved events after init().

js
import { readFile, writeFile, rm } from "node:fs/promises";
import { init, track } from "@amplitude/analytics-node";

const fileStorage = {
  file: (key) => `/tmp/${key}.json`,
  isEnabled: async () => true,
  getRaw: async (key) =>
    readFile(fileStorage.file(key), "utf8").catch(() => undefined),
  get: async (key) =>
    JSON.parse((await fileStorage.getRaw(key)) ?? "null") ?? undefined,
  set: async (key, value) =>
    writeFile(fileStorage.file(key), JSON.stringify(value)),
  remove: async (key) => rm(fileStorage.file(key), { force: true }),
  reset: async () => {},
};

// Await init(): with an asynchronous storage, events tracked before init() finishes are discarded.
await init("AMPLITUDE_API_KEY", { storageProvider: fileStorage }).promise;

track("Button Clicked", undefined, { user_id: "user-12345" });

Logging

The SDK logs warnings and errors to the console by default. To route its messages to your own logger, set loggerProvider to an object with the methods of the Logger interface, and set logLevel to the lowest level you want:

js
import { init, track, Types } from "@amplitude/analytics-node";

let level = Types.LogLevel.None;
const appLogger = {
  disable: () => (level = Types.LogLevel.None),
  enable: (logLevel) => (level = logLevel),
  log: (...args) =>
    level >= Types.LogLevel.Verbose && console.info("[amplitude]", ...args),
  warn: (...args) =>
    level >= Types.LogLevel.Warn && console.warn("[amplitude]", ...args),
  error: (...args) =>
    level >= Types.LogLevel.Error && console.error("[amplitude]", ...args),
  debug: (...args) =>
    level >= Types.LogLevel.Debug && console.debug("[amplitude]", ...args),
};

init("AMPLITUDE_API_KEY", {
  loggerProvider: appLogger,
  logLevel: Types.LogLevel.Warn,
});

track("Button Clicked", undefined, { user_id: "user-12345" });

The SDK calls enable(logLevel) during init(). The logger filters by level, not the SDK, so apply the level in your methods, as the example does. For what each level includes, go to Turn on debug logging.

Use the SDK in request handlers

Initialize the SDK once when your server starts, then call track() in route handlers without awaiting it. The SDK uploads in the background.

js
import express from "express";
import { init, track } from "@amplitude/analytics-node";

init("AMPLITUDE_API_KEY");

const app = express();
app.use(express.json());

app.post("/orders", async (req, res) => {
  const order = await createOrder(req.body);

  // Don't await: the upload happens in the background.
  track("Order Created", { orderId: order.id }, { user_id: req.user.id });

  res.status(201).json(order);
});

app.listen(3000);

Awaiting track(...).promise in a handler delays the response until the next scheduled upload finishes: up to flushIntervalMillis (10 seconds by default), plus network time and any retries.

If a handler must know the upload outcome before it responds, call flush() and then await the event's promise. Initialization finished long before the request arrived, so flush() starts an upload right away:

js
app.post("/orders", async (req, res) => {
  const order = await createOrder(req.body);

  const pending = track(
    "Order Created",
    { orderId: order.id },
    { user_id: req.user.id },
  );
  await flush().promise;
  const result = await pending.promise; // result.code and result.message

  res.status(201).json({ order, analytics: result.code });
});

flush() makes one attempt and doesn't retry, so a failure is final for the flushed events.

Serverless and short-lived processes

Serverless functions, CLI scripts, and job workers can end before the SDK's next upload. Await init() once, then await flush().promise before the function returns or the script exits.

js
import { init, track, flush } from "@amplitude/analytics-node";

// Runs once per container or process.
const ready = init("AMPLITUDE_API_KEY").promise;

export async function handler(job) {
  await ready;

  track("Job Completed", { jobId: job.id }, { user_id: job.userId });

  await flush().promise;
  return { statusCode: 200 };
}

To try the handler locally, call it once:

js
await handler({ id: "job-77", userId: "user-12345" });

Two rules keep events from being lost:

  • Await init() before flush(). If flush() runs before init() finishes, the queued events resolve with 100 and Event not tracked, no destination plugins on the instance, and the SDK never sends them.
  • Await the flush. A frozen or ended process can't finish the upload. Because flush() doesn't retry, check the event results if delivery matters, and track again on failure.

이 내용이 도움이 되었나요?

For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.

이 페이지에서