OpenCoreDev Releases Domain SDK 0.2.0: One TypeScript API to Add, Verify, and Remove Customer Domains Across Five Platforms
Custom domains are a standard SaaS feature. Yet every hosting platform exposes a different API for them. OpenCoreDev has published Domain SDK, a TypeScript client that normalizes that work. Version 0.2.0 reached npm a day after the first release.
What is Domain SDK?
Domain SDK covers Vercel, Cloudflare for SaaS, Railway, Render, and Netlify. You add a hostname, show the exact DNS records, then track provider state until it is ready.
It does not register domains, host DNS, deploy apps, proxy traffic, or store tenant data. Your application keeps tenant authorization and state. The provider stays the source of truth.
The package is server-side only, so credentials never reach browser code. engines.node is >=20, and Bun is supported. It is ESM-only and ships one runtime dependency, tldts.
The client surface
Within that scope, the API is small. createDomainClient() returns a stateless client with seven members: add, get, refresh, list, verify, remove, and waitUntilActive. Every method accepts an AbortSignal. Add and remove are idempotent, so retries are safe.
Each adapter is a separate entry point: ./vercel, ./cloudflare, ./railway, ./render, ./netlify, and ./testing. Changing platforms means changing the import, not the workflow.
Status is a union, not a boolean
Because DNS, ownership, and TLS finish at different times, the SDK models them separately. DomainStatus carries eight values:
pending, pending_dns, pending_verification, pending_certificate, active, misconfigured, failed, unknown.
A Domain also holds verification (pending | verified | failed | unknown), certificate (pending | active | expiring | failed | unknown), and an issues array. Each issue has a code, message, optional record, and a retryable flag.
Records are typed the same way. DnsRecord has type, name, value, optional ttl, purpose, required, status, and optional description. purpose is routing, ownership, certificate, or other. status is pending, valid, invalid, or unknown.
Consequently, a Vercel hostname surfaces three separate records:
| Type | Name | Purpose |
|---|---|---|
| CNAME | app.customer.com |
routing |
| TXT | _vercel.app.customer.com |
ownership |
| TXT | _acme-challenge.app.customer.com |
certificate |
Provider compatibility
While the lifecycle is normalized, platform limits are not:
| Capability | Vercel | Cloudflare SaaS | Railway | Render | Netlify |
|---|---|---|---|---|---|
| List domains | ✓ | ✓ | ✓ | ✓ | ✓ |
| Explicit verification | ✓ | — | — | ✓ | — |
| Managed certificates | ✓ | ✓ | ✓ | ✓ | ✓ |
| Apex domains | ✓ | — | ✓ | ✓ | ✓ |
| Wildcard domains | — | — | — | ✓ | — |
| Automatic customer DNS | — | — | — | — | — |
Cloudflare for SaaS requires a CNAME target, so the adapter does not claim apex support. Render alone accepts wildcard hostnames. No adapter edits customer DNS automatically.
Rather than hard-coding those limits, read client.capabilities at runtime. It exposes list, explicitVerification, managedCertificates, apexDomains, and wildcardDomains.
Working with the client
The following snippet type-checks and runs against 0.2.0:
import { createDomainClient } from "@opencoredev/domain-sdk";
import { vercel } from "@opencoredev/domain-sdk/vercel";
export const domains = createDomainClient({
provider: vercel({
token: process.env.VERCEL_TOKEN!,
projectId: process.env.VERCEL_PROJECT_ID!,
}),
});
const domain = await domains.add("app.customer.com");
// Render every required record in your customer-facing DNS instructions.
const required = domain.records.filter((item) => item.required);
const active = await domains.waitUntilActive(domain.hostname, {
timeoutMs: 300_000, // default
intervalMs: 5_000, // default, minimum 250
onStatus: (current) => console.log(current.status),
});
console.log(active.certificate.status, active.verification.status);
Polling is sequential. It defaults to a 300,000 ms timeout and a 5,000 ms interval. Intervals below 250 ms are rejected, and each state is reported through onStatus. When a provider returns retryAfter, the client waits at least that long before retrying.
Testing and agent support
For CI, an in-memory adapter never calls a real provider:
import { createDomainClient } from "@opencoredev/domain-sdk";
import { memoryProvider } from "@opencoredev/domain-sdk/testing";
const memory = memoryProvider();
const domains = createDomainClient({ provider: memory });
await domains.add("app.customer.com"); // status: pending_dns
memory.activate("app.customer.com");
const latest = await domains.refresh("app.customer.com");
console.log(latest.status === "active"); // true
It also supports transition(), injected latencyMs, per-operation failures, and a calls log. createMockDomain, createMockDnsRecord, and createFailingProvider sit alongside it.
Separately, an agent skill installs the workflow into Codex, Claude Code, Cursor, and other compatible agents:
npx skills add opencoredev/domain-sdk --skill domain-sdk
Use cases
- Multi-tenant SaaS on Vercel: A customer maps
app.customer.com. Your settings page renders required CNAME and TXT records with live status. - Cloudflare for SaaS at scale: Custom Hostnames live inside one zone. The adapter keeps that scoping explicit.
- Tenant subdomains you own:
createSubdomainClientmaps labels under a base domain.reservedLabelsblockswww,api, andadmin. - Provider migration: Swap the adapter import and keep the same lifecycle calls.
Interactive Explainer
Key Takeaways
- Domain SDK normalizes the custom-domain lifecycle across five platforms behind one TypeScript client.
DomainStatushas eight values, with separate verification, certificate, and issue fields.- Capabilities differ by platform; read
client.capabilitiesinstead of hard-coding limits. - Idempotent add and remove,
AbortSignalsupport, and fourteen typed error codes make retries safe. - At 0.2.0 the API is young, and its types need
moduleResolution: "bundler"as of now.
Check out the GitHub Repo. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us
The post OpenCoreDev Releases Domain SDK 0.2.0: One TypeScript API to Add, Verify, and Remove Customer Domains Across Five Platforms appeared first on MarkTechPost.
MarkTechPost
