New:Socket for Asana Is Now Available.Learn more
Get Started

@dropthis/node

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dropthis/node

Official Node.js SDK for Dropthis.

Source
npmnpm
Version
0.14.0
Version published
Weekly downloads
51
168.42%
Maintainers
1
Weekly downloads
 
Created
Source

@dropthis/node

Official Node.js SDK for dropthis -- the publish layer between AI and the internet. One API call in, one URL out.

Install

npm install @dropthis/node

Quick start

import { Dropthis } from "@dropthis/node";

const dropthis = new Dropthis({ apiKey: "sk_..." });
const { data, error } = await dropthis.drops.publish("<h1>Hello</h1>");

console.log(data.url); // https://abc123.dropthis.app
console.log(data.id);  // drop_… — keep this; it's how you update or delete later

Keep the drop_… id. publish() never takes an id — every call creates a NEW drop. To change something already published, pass the id from the publish response to drops.updateContent() (the files at the URL) or drops.updateSettings() (title, visibility, expiry, …). Lost the id? Recover it from the URL with drops.resolve().

Usage

Publish an HTML string

const { data } = await dropthis.drops.publish("<h1>Launch page</h1>");

Publish a file

const { data } = await dropthis.drops.publish("./report.html");

Publish a directory

const { data } = await dropthis.drops.publish("./dist");

Publish with options

const { data } = await dropthis.drops.publish("./dist", {
  title: "Q4 Report",
  visibility: "unlisted",
  noindex: true,
  expiresAt: "2026-12-31T00:00:00Z",
});

Update the content of an existing drop

const created = await dropthis.drops.publish("./dist", { title: "v1" });

const updated = await dropthis.drops.updateContent(created.data.id, "./dist-v2", {
  ifRevision: created.data.revision,
});

Update settings only

await dropthis.drops.updateSettings("drop_abc123", { title: "New title" });

Resolve a URL back to its drop

Lost the drop_… id? Resolve the drop's URL (or bare slug) back to the drop. The slug is parsed client-side from the first hostname label of a dropthis hostname (<slug>.dropthis.app, or <slug>. + your configured baseUrl host), then matched against your own drops via GET /drops?slug=. Custom-domain URLs are not resolvable yet — they are rejected client-side with an invalid_drop_url error, without hitting the API.

const { data } = await dropthis.drops.resolve("https://my-report.dropthis.app/");

if (data) {
  console.log(data.id); // drop_… — use this for updateContent/updateSettings/delete
} else {
  // none of your drops has that slug
}

Read back what a drop is serving

drops.getContent() is the owner-side read-back (it works regardless of any viewer password). By default it returns a JSON manifest of the current deployment's files; pass path to download one file's exact stored bytes.

// Manifest of the current deployment
const manifest = await dropthis.drops.getContent("drop_abc123");
console.log(manifest.data.files); // [{ path, contentType, sizeBytes }, …]

// One file's bytes (and a text() helper)
const file = await dropthis.drops.getContent("drop_abc123", { path: "index.html" });
console.log(file.data.contentType); // "text/html"
console.log(file.data.text());      // "<h1>…"

// A historical (even superseded) deployment — this is also the rollback path:
// download the old version's files and republish them with updateContent().
const old = await dropthis.drops.getContent("drop_abc123", {
  deploymentId: "dep_xyz789",
  path: "index.html",
});

Safe concurrent edits with ifRevision

Every drop response carries a revision. Pass it back as ifRevision on updateContent() / updateSettings() to make the update conditional: if someone else changed the drop in between, the API answers 409 instead of clobbering, and the error exposes the server's currentRevision so you can re-read and retry.

// 1. Read
const drop = await dropthis.drops.get("drop_abc123");

// 2. Edit locally (e.g. via getContent read-back), then
// 3. Update conditionally
const result = await dropthis.drops.updateContent(drop.data.id, "./dist-v2", {
  ifRevision: drop.data.revision,
});

if (result.error?.statusCode === 409) {
  console.log("Drop changed underneath us; server is at revision", result.error.currentRevision);
  // re-read with drops.get(), merge, retry with the fresh revision
}

Supported inputs

The drops.publish() and drops.updateContent() methods accept:

  • HTML/text string -- "<h1>Hello</h1>" (auto-detected as inline content)
  • File path -- "./report.html" (local file)
  • Directory -- "./dist" (local directory, bundled)
  • Array of paths -- ["./dist", "./extra.css"] (multi-path bundle)
  • URL object -- new URL("https://example.com/page") (source fetch)
  • Bytes -- new Uint8Array(...) (raw bytes)
  • Explicit content -- { kind: "content", content: "...", contentType?: "text/html", path?: "page.html" }
  • Source URL -- { kind: "source_url", sourceUrl: "https://example.com/page" }
  • File bundle -- { kind: "files", files: [{ path, content?, contentBase64?, bytes?, contentType? }], entry? }

Drop settings (title, visibility, password, noindex, expiresAt, metadata) go in the second options argument, not in the input object.

All inputs are uploaded through staged presigned URLs — one signed PUT per file, up to 5 files in parallel. The SDK handles this transparently.

Explicit input examples

// Inline content with explicit MIME type
await dropthis.drops.publish({
  kind: "content",
  content: "<h1>Hello</h1>",
  contentType: "text/html",
});

// Fetch and re-publish a remote URL
await dropthis.drops.publish({
  kind: "source_url",
  sourceUrl: "https://example.com/report",
});

// Multi-file bundle with explicit entry point
await dropthis.drops.publish(
  {
    kind: "files",
    files: [
      { path: "index.html", content: "<h1>Hello</h1>" },
      { path: "style.css", content: "body { margin: 0; }" },
    ],
    entry: "index.html",
  },
  { title: "My Site" },
);

Prepare (validate without sending)

prepare() resolves and validates the input locally, returning the prepared request object without making any API calls. It throws PublishInputError on invalid input (e.g. missing file).

import { Dropthis, PublishInputError } from "@dropthis/node";

try {
  const prepared = await dropthis.prepare("./dist");
  console.log("Ready to publish:", prepared.kind);
} catch (e) {
  if (e instanceof PublishInputError) {
    console.error("Bad input:", e.message);
  }
}

Error handling

All methods return DropthisResult<T> -- either { data: T, error: null, headers } or { data: null, error, headers }. API errors never throw; check error before using data.

const result = await dropthis.drops.get("drop_abc123");

if (result.error) {
  console.error(result.error.code, result.error.message);
  // Also available: error.statusCode, error.requestId, error.suggestion,
  //                 error.retryable, error.param, error.currentRevision
} else {
  console.log(result.data);
}

Local input validation errors (e.g. file_not_found) are also returned as { error: { code: "file_not_found", ... } } rather than thrown -- except for prepare(), which throws PublishInputError.

Configuration

const dropthis = new Dropthis({
  apiKey: "sk_...",        // Required. Defaults to DROPTHIS_API_KEY env var.
  baseUrl: "https://...",  // Override API base URL.
  timeoutMs: 30_000,       // Request timeout in milliseconds (default: 30s).
  uploadTimeoutMs: 120_000, // Timeout for signed-PUT file uploads (default: 120s).
  fetch: customFetch,      // Custom fetch implementation.
});

You can also pass just the API key as a string:

const dropthis = new Dropthis("sk_...");

Resources

drops

await dropthis.drops.list({ limit: 20 });
await dropthis.drops.list({ domain: "reports.example.com" }); // only drops mounted on a custom domain
await dropthis.drops.get("drop_abc123");
await dropthis.drops.resolve("https://my-report.dropthis.app/"); // URL/slug → drop (or null)
await dropthis.drops.getContent("drop_abc123");                  // manifest of served files
await dropthis.drops.getContent("drop_abc123", { path: "index.html" }); // one file's bytes
await dropthis.drops.updateSettings("drop_abc123", { title: "Updated" });
await dropthis.drops.delete("drop_abc123");

List results support auto-pagination:

const page = await dropthis.drops.list();
const allDrops = await page.data.autoPagingToArray({ limit: 100 });

// Or iterate
for await (const drop of page.data) {
  console.log(drop.url);
}

To change a drop's content, use client.drops.updateContent(dropId, newInput). drops.updateSettings() is for settings only (title, visibility, password, noindex, expiresAt, metadata).

deployments

await dropthis.deployments.list("drop_abc123");
await dropthis.deployments.get("drop_abc123", "dep_xyz789");

uploads

Low-level upload session management. Most users should use publish() instead.

await dropthis.uploads.create({
  schemaVersion: 1,
  files: [{ path: "index.html", contentType: "text/html", sizeBytes: 1024 }],
});
await dropthis.uploads.get("upl_abc123");
await dropthis.uploads.complete("upl_abc123"); // no body — server verifies against the manifest
await dropthis.uploads.cancel("upl_abc123");

auth

await dropthis.auth.requestEmailOtp({ email: "you@example.com" });
await dropthis.auth.verifyEmailOtp({ email: "you@example.com", code: "123456" });
await dropthis.auth.logout(); // 204 No Content — data is null

apiKeys

await dropthis.apiKeys.create({ label: "CI" });
await dropthis.apiKeys.list();
await dropthis.apiKeys.delete("key_abc123"); // 204 No Content — data is null

account

const { data } = await dropthis.account.get();
// data.limits carries your plan's limits — use them to size a publish first:
// { name, maxSizeBytes, defaultTtlSeconds, maxStorageBytes }

await dropthis.account.update({ displayName: "Jane Doe" });
await dropthis.account.delete();

domains

Custom domains let you serve drops on your own hostname instead of the shared pool. There are two modes: path (many drops at hostname/{slug}/) and dedicated (one drop at the hostname root).

// 1. Connect the domain — returns DNS instructions (status: "pending_dns")
const { data: domain } = await dropthis.domains.connect({
  hostname: "drops.example.com",
  mode: "path",
});

// 2. Create the CNAME at your DNS provider:
//    drops.example.com  CNAME  edge.dropthis.app
const dnsRecord = domain.dns[0];
// dnsRecord.name  → "drops.example.com"
// dnsRecord.value → "edge.dropthis.app"

// 3. Verify — call repeatedly until status is "live"
const { data: verified } = await dropthis.domains.verify("drops.example.com");
// Use verified.dns[0].retryAfter (seconds) as the polling interval while status !== "live"
// verified.status → "live"

// 4. Publish to the custom domain (path mode: specify a vanity slug, or omit for a random one)
const { data: drop } = await dropthis.drops.publish("<h1>Hello</h1>", {
  domain: "drops.example.com",
  slug: "summer-sale",
});
// drop.url → "https://drops.example.com/summer-sale/"

Other domain operations:

await dropthis.domains.list();
await dropthis.domains.get("drops.example.com");

// Repoint a dedicated domain to a different drop
await dropthis.domains.update("bio.example.com", { dropId: "drop_abc123" });

// Set a path-mode domain as the account's publish default
await dropthis.domains.update("drops.example.com", { default: true });

// Delete (remove your DNS CNAME after this to prevent re-claim by another account)
await dropthis.domains.delete("drops.example.com");

Pricing tiers

  • Free ($0) — drops expire after 7 days, 5 MB per drop, dropthis badge.
  • Personal ($5/mo) — drops stay live while subscribed (no expiry), no badge, 100 MB per drop, 2 GB account storage.
  • Pro ($19/mo) — everything in Personal plus password protection, custom domains, and analytics.

account.get().data.limits reflects your active tier. Note: password protection ships with Pro and is not enabled on any tier yet — the API rejects the password option until then.

Cloudflare Workers (edge)

Use the fs-free entry point for Cloudflare Workers and other edge runtimes. It does not import node:fs, node:path, or node:crypto.

import { DropthisEdge } from "@dropthis/node/edge";

const dropthis = new DropthisEdge({ apiKey: env.DROPTHIS_API_KEY });
const { data, error } = await dropthis.drops.publish("<h1>Hello from the edge</h1>");

DropthisEdge accepts the in-memory subset of PublishInput: inline strings, Uint8Array, URL, and the explicit { kind: "content" }, { kind: "source_url" }, and { kind: "files" } forms. Local file paths and string[] path arrays are not supported (no filesystem on the edge).

DropthisEdge exposes the drop lifecycle through drops.publish(input, options?), drops.updateContent(dropId, input, options?), drops.updateSettings, drops.get, drops.list, drops.resolve, drops.getContent, and drops.delete, plus the deployments, account, apiKeys, and domains resource accessors — the same surface as the Node client.

Types

Key types exported from the package:

import type {
  AccountLimits,
  AccountResponse,
  DropthisClientOptions,
  DropthisResult,
  DropthisErrorResponse,
  DropResponse,
  DropDeploymentResponse,
  DropOptions,
  DeploymentContentManifest,
  DeploymentContentFile,
  DropContentFile,
  GetContentOptions,
  PrepareOptions,
  RequestControls,
  PublishOptions,
  PublishInput,
  PublishFileInput,
  ListPage,
  CreateUploadSessionRequest,
  CreateUploadSessionResponse,
} from "@dropthis/node";

Agent skills

For AI coding agents (Cursor, Claude Code, Windsurf, etc.), install the dropthis-skills package:

npx skills add dropthis-dev/dropthis-skills

FAQs

Package last updated on 12 Jun 2026

Related posts