Installation
Install Domain SDK and start managing customer domains from your server.
1. Install the package
bun add @opencoredev/domain-sdkDomain SDK is server-only. Keep it in API routes, server actions, workers, or another trusted backend.
Install the agent skill
Give Codex, Claude Code, Cursor, and other compatible coding agents the Domain SDK workflow and safety rules:
npx skills add opencoredev/domain-sdk --skill domain-sdkThe skills CLI asks which detected agents should receive the skill. Add -g to make it available across all of your projects.
2. Choose your provider
Use the platform that currently receives traffic for your application. Each adapter is a separate import and uses credentials scoped to one project, service, site, or zone.
Vercel
Project domains and explicit verification
Cloudflare for SaaS
Custom Hostnames inside one SaaS zone
Railway
One project, environment, and service
Render
One web service or static site
Netlify
Domain aliases on one existing site
3. Create the client
Put the client in a server-only module. Pick the tab that matches your provider; its provider page lists the required credentials and platform-specific behavior.
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!,
}),
});import { createDomainClient } from "@opencoredev/domain-sdk";
import { cloudflareSaaS } from "@opencoredev/domain-sdk/cloudflare";
export const domains = createDomainClient({
provider: cloudflareSaaS({
apiToken: process.env.CLOUDFLARE_API_TOKEN!,
zoneId: process.env.CLOUDFLARE_ZONE_ID!,
cnameTarget: process.env.CLOUDFLARE_CNAME_TARGET!,
}),
});import { createDomainClient } from "@opencoredev/domain-sdk";
import { railway } from "@opencoredev/domain-sdk/railway";
export const domains = createDomainClient({
provider: railway({
token: process.env.RAILWAY_TOKEN!,
projectId: process.env.RAILWAY_PROJECT_ID!,
environmentId: process.env.RAILWAY_ENVIRONMENT_ID!,
serviceId: process.env.RAILWAY_SERVICE_ID!,
}),
});import { createDomainClient } from "@opencoredev/domain-sdk";
import { render } from "@opencoredev/domain-sdk/render";
export const domains = createDomainClient({
provider: render({
apiKey: process.env.RENDER_API_KEY!,
serviceId: process.env.RENDER_SERVICE_ID!,
}),
});import { createDomainClient } from "@opencoredev/domain-sdk";
import { netlify } from "@opencoredev/domain-sdk/netlify";
export const domains = createDomainClient({
provider: netlify({
accessToken: process.env.NETLIFY_ACCESS_TOKEN!,
siteId: process.env.NETLIFY_SITE_ID!,
}),
});Never expose provider credentials through NEXT_PUBLIC_, VITE_, or another browser-accessible environment variable.
4. Add and refresh a domain
import { domains } from "./domains";
const domain = await domains.add("app.customer.com");
// Show every required record in your customer-facing DNS instructions.
const requiredRecords = domain.records.filter((record) => record.required);
const latest = await domains.refresh(domain.hostname);
if (latest.status === "active") {
// The configured provider now reports the domain as ready.
}Store the hostname's tenant ownership in your own database. Domain SDK is stateless and the configured provider remains the source of truth for readiness.
Read next: Compare providers.
