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:
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:
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.
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.
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" });
{
"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": "-" }
}
}
| Operation | Effect |
|---|---|
set(property, value) | Sets the value. |
setOnce(property, value) | Sets the value only if the property has no value yet. |
add(property, number) | Adds the number to the current value. A property with no value starts at 0. |
append(property, value) | Adds the value or values to the end of the list. A property with no value starts as an empty list. |
prepend(property, value) | Adds the value or values to the start of the list. A property with no value starts as an empty list. |
postInsert(property, value) | Adds the value to the end of the list if the list doesn't contain it yet. |
preInsert(property, value) | Adds the value to the start of the list if the list doesn't contain it yet. |
remove(property, value) | Removes the value from the list, if it's there. |
unset(property) | Removes the property. |
clearAll() | Removes all of the user's properties. |
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:
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.
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.
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" });
{
"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.
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.
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.
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:
beforeplugins: Run in the order you added them. The built-incontextplugin is abeforeplugin. It setstimeandinsert_idwhen they're missing, and always setsevent_idandlibrary.enrichmentplugins: Run in the order you added them. A plugin without atypeis an enrichment plugin.destinationplugins: Run all at once, each with its own copy of the event. The built-inamplitudedestination 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
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:
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.
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.
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():
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
flushIntervalMillislater (10 seconds by default). The pending upload keeps the Node.js process alive until it runs. - Batch size: Each request holds up to
flushQueueSizeevents (200 by default). The SDK sends larger queues as several requests, one after another. ReachingflushQueueSizedoesn'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.
| Response | What the SDK does |
|---|---|
2xx | Resolves each event with the status and Event tracked successfully. |
400 for some events | Drops the events Amplitude names as invalid, and retries the rest on the next upload. |
400 for a missing or invalid API key | Drops every event in the request. |
413 | Halves flushQueueSize for the rest of the instance's life and retries. Drops a single event that's too large. |
429 | Drops events for users or devices over their daily quota. Waits 30 seconds before it retries throttled events. |
408 (request timeout), 5xx, or a broken response | Retries after a backoff that grows by one second per attempt. |
| No response (DNS, connection refused) | Drops the events with Unexpected error occurred, without retrying. |
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.
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.
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.
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.
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.
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.
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().
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:
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.
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:
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.
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:
await handler({ id: "job-77", userId: "user-12345" });
Two rules keep events from being lost:
- Await
init()beforeflush(). Ifflush()runs beforeinit()finishes, the queued events resolve with100andEvent 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.