npm Packages
Install any npm package into your HeadlessOps integration's execution environment. Declare dependencies in integration.yaml using the packages field, or via the requirements field through the REST API or MCP. Packages are cached for zero-overhead reinstalls on repeat runs.
npm Packages
HeadlessOps supports installing third-party npm packages directly into your integration's execution environment. Dependencies are declared in the packages field of integration.yaml — no separate package.json file is needed or read.
Declaring Requirements
Via integration.yaml (Recommended)
The simplest and most portable way to declare dependencies is directly in integration.yaml. Every time you push files to the platform, the packages array is automatically parsed and applied — keeping your YAML the single source of truth for both logic and dependencies:
name: data-pipeline
description: "Pulls records, processes with lodash, stores results"
packages:
- zod@^3.23
- dayjs@2.0
- lodash@^4.17.21When you push this YAML (via integration_files_push, integration_files_push_run, or deploy), the packages array is synced to the integration's requirements field automatically. No additional API call is required.
Via MCP
Use the integrations_update MCP tool to set package requirements directly, without modifying the YAML:
{
"tool": "integrations_update",
"arguments": {
"name": "my-integration",
"requirements": ["zod@^3.23", "dayjs@2.0", "lodash"]
}
}Via REST API
curl -X PUT https://app.headlessops.ai/api/integrations/my-integration \
-H "Authorization: Bearer iter_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"requirements": ["zod@^3.23", "dayjs@2.0", "lodash"]
}'Requirements take effect on the next run after the update is applied.
Note: If both the
packagesfield inintegration.yamland therequirementsfield are set via API, the YAML push always wins — thepackagesarray overwrites the storedrequirementson every push. Use the YAML field for everything that should be version-controlled, and the API only for ad-hoc overrides.
Supported Specifier Formats
| Format | Example | Behaviour |
|---|---|---|
| Latest | lodash | Installs the newest available version from npm |
| Pinned | dayjs@2.0.0 | Exact version lock — recommended for production |
| Range | zod@^3.23 | Semver-compatible version range |
| Tag | lodash@latest | Specific dist-tag |
You can freely mix pinned and unpinned specifiers in the same list. For example: ["zod@^3.23", "dayjs@2.0.0", "lodash"] is valid.
Package Caching
Dependencies are installed into a content-addressed environment identified by a hash of the sorted, normalised requirements list:
/pip-cache/venvs/{hash-of-sorted-requirements}/How caching works:
- If an identical requirements list was installed before, the cached modules are reused — no reinstall overhead.
- Adding, removing, or changing any single requirement creates a new environment with a clean install.
- Old environments are retained until the platform's cache pruning process removes them.
First-time installs typically complete in 2–15 seconds depending on package size and dependency depth. Subsequent runs with the same requirements add zero startup overhead.
Security Restrictions
The following specifier formats are blocked to prevent security vulnerabilities:
| Blocked Format | Example | Reason |
|---|---|---|
| Git URLs | git+https://github.com/org/repo | Arbitrary code execution risk |
| Direct URLs / tarballs | https://files.example.com/pkg.tgz | Unverified package source |
| Local paths | file:../local-pkg | Filesystem access |
| Shell metacharacters | lodash; rm -rf / | Command injection prevention |
Attempting to use a blocked specifier returns a 400 Bad Request error with a description of the violation.
Only packages available on the public npm registry (https://registry.npmjs.org) are supported. Private registries are not currently available.
Using Installed Packages in Steps
Import packages normally at the top of your step file. HeadlessOps activates the integration's environment before executing any step:
import _ from "lodash";
import dayjs from "dayjs";
import { z } from "zod";
export async function run(input: Record<string, any>, ctx: StepContext) {
// lodash, dayjs, and zod are available
const sorted = _.sortBy(input.items, "priority");
ctx.log("sorted items", { count: sorted.length });
return { sorted };
}Example: HTTP Client with Schema Validation
This example uses the built-in ctx.http for async HTTP requests and zod for response validation:
import { z } from "zod";
const ContactSchema = z.object({
id: z.string(),
email: z.string().email(),
name: z.string(),
});
export async function run(input: Record<string, any>, ctx: StepContext) {
const apiKey = ctx.credentials.CRM_API_KEY;
const response = await ctx.http.get(
`https://api.crm.example.com/contacts/${input.contact_id}`,
{
headers: { Authorization: `Bearer ${apiKey}` },
timeout: 25000,
}
);
if (!response.ok) {
throw new Error(`CRM API returned ${response.status}`);
}
const raw = await response.json();
const contact = ContactSchema.parse(raw);
ctx.log("fetched contact", { id: contact.id, email: contact.email });
return contact;
}Declare the dependencies in integration.yaml:
packages:
- zod@^3.23Frequently Asked Questions
Can I use a package.json file?
No. The platform does not read package.json from the integration package. Declare all dependencies using the packages field in integration.yaml, or via the requirements field through the REST API or MCP.
Are private npm registries supported? Not currently. Only packages available on the public npm registry are supported.
Can I use packages with native extensions (e.g. sharp, bcrypt)? Packages that require native compilation must be available as pre-built binaries for the platform's Node.js runtime on Linux.
What Node.js version does the runtime use? The platform uses Node.js 24. Ensure your packages are compatible before declaring them as requirements.
How long does installation take? First-time installs typically complete in 2–15 seconds. Subsequent runs with identical requirements reuse the cached environment and add zero overhead.
What happens if I push integration.yaml with a packages field but also set requirements via the API?
The YAML push always wins. Every time you push files containing a packages field, it overwrites the stored requirements. For version-controlled integrations, keep dependencies in the YAML; use the API only for temporary overrides.
Related
- integration.yaml Schema — full YAML manifest reference including the
packagesfield - TypeScript ctx API — available context methods in step scripts
- JavaScript Runtime Behavior — handler signatures,
process.env, built-in modules, and globals available inside the sandbox - MCP Tools Reference — full list of MCP tools including
integrations_update - Integrations Overview — introduction to the integration model