Customized domains are a typical SaaS characteristic. But each internet hosting platform exposes a special API for them. OpenCoreDev has revealed Area SDK, a TypeScript consumer that normalizes that work. Model 0.2.0 reached npm a day after the primary launch.
What’s Area SDK?
Area SDK covers Vercel, Cloudflare for SaaS, Railway, Render, and Netlify. You add a hostname, present the precise DNS information, then observe supplier state till it’s prepared.
It doesn’t register domains, host DNS, deploy apps, proxy visitors, or retailer tenant information. Your software retains tenant authorization and state. The supplier stays the supply of reality.
The bundle is server-side solely, so credentials by no means attain browser code. engines.node is >=20, and Bun is supported. It’s ESM-only and ships one runtime dependency, tldts.
The consumer floor
Inside that scope, the API is small. createDomainClient() returns a stateless consumer with seven members: add, get, refresh, checklist, confirm, take away, and waitUntilActive. Each technique accepts an AbortSignal. Add and take away are idempotent, so retries are secure.
Every adapter is a separate entry level: ./vercel, ./cloudflare, ./railway, ./render, ./netlify, and ./testing. Altering platforms means altering the import, not the workflow.
Standing is a union, not a boolean
As a result of DNS, possession, and TLS end at completely different instances, the SDK fashions them individually. DomainStatus carries eight values:
pending, pending_dns, pending_verification, pending_certificate, energetic, misconfigured, failed, unknown.
A Area additionally holds verification (pending | verified | failed | unknown), certificates (pending | energetic | expiring | failed | unknown), and an points array. Every challenge has a code, message, non-compulsory report, and a retryable flag.
Data are typed the identical method. DnsRecord has kind, title, worth, non-compulsory ttl, goal, required, standing, and non-compulsory description. goal is routing, possession, certificates, or different. standing is pending, legitimate, invalid, or unknown.
Consequently, a Vercel hostname surfaces three separate information:
| Kind | Title | Function |
|---|---|---|
| CNAME | app.buyer.com |
routing |
| TXT | _vercel.app.buyer.com |
possession |
| TXT | _acme-challenge.app.buyer.com |
certificates |
Supplier compatibility
Whereas the lifecycle is normalized, platform limits should not:
| Functionality | Vercel | Cloudflare SaaS | Railway | Render | Netlify |
|---|---|---|---|---|---|
| Checklist domains | ✓ | ✓ | ✓ | ✓ | ✓ |
| Express verification | ✓ | — | — | ✓ | — |
| Managed certificates | ✓ | ✓ | ✓ | ✓ | ✓ |
| Apex domains | ✓ | — | ✓ | ✓ | ✓ |
| Wildcard domains | — | — | — | ✓ | — |
| Computerized buyer DNS | — | — | — | — | — |
Cloudflare for SaaS requires a CNAME goal, so the adapter doesn’t declare apex assist. Render alone accepts wildcard hostnames. No adapter edits buyer DNS mechanically.
Somewhat than hard-coding these limits, learn consumer.capabilities at runtime. It exposes checklist, explicitVerification, managedCertificates, apexDomains, and wildcardDomains.
Working with the consumer
The next snippet type-checks and runs in opposition to 0.2.0:
import { createDomainClient } from "@opencoredev/domain-sdk";
import { vercel } from "@opencoredev/domain-sdk/vercel";
export const domains = createDomainClient({
supplier: vercel({
token: course of.env.VERCEL_TOKEN!,
projectId: course of.env.VERCEL_PROJECT_ID!,
}),
});
const area = await domains.add("app.buyer.com");
// Render each required report in your customer-facing DNS directions.
const required = area.information.filter((merchandise) => merchandise.required);
const energetic = await domains.waitUntilActive(area.hostname, {
timeoutMs: 300_000, // default
intervalMs: 5_000, // default, minimal 250
onStatus: (present) => console.log(present.standing),
});
console.log(energetic.certificates.standing, energetic.verification.standing);
Polling is sequential. It defaults to a 300,000 ms timeout and a 5,000 ms interval. Intervals beneath 250 ms are rejected, and every state is reported by means of onStatus. When a supplier returns retryAfter, the consumer waits at the least that lengthy earlier than retrying.
Testing and agent assist
For CI, an in-memory adapter by no means calls an actual supplier:
import { createDomainClient } from "@opencoredev/domain-sdk";
import { memoryProvider } from "@opencoredev/domain-sdk/testing";
const reminiscence = memoryProvider();
const domains = createDomainClient({ supplier: reminiscence });
await domains.add("app.buyer.com"); // standing: pending_dns
reminiscence.activate("app.buyer.com");
const newest = await domains.refresh("app.buyer.com");
console.log(newest.standing === "energetic"); // true
It additionally helps transition(), injected latencyMs, per-operation failures, and a calls log. createMockDomain, createMockDnsRecord, and createFailingProvider sit alongside it.
Individually, an agent ability installs the workflow into Codex, Claude Code, Cursor, and different appropriate brokers:
npx expertise add opencoredev/domain-sdk --skill domain-sdk
Use instances
- Multi-tenant SaaS on Vercel: A buyer maps
app.buyer.com. Your settings web page renders required CNAME and TXT information with stay standing. - Cloudflare for SaaS at scale: Customized Hostnames stay inside one zone. The adapter retains that scoping specific.
- Tenant subdomains you personal:
createSubdomainClientmaps labels underneath a base area.reservedLabelsblockswww,api, andadmin. - Supplier migration: Swap the adapter import and hold the identical lifecycle calls.
