npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jaggr2/cdk-cf-dns

v0.3.1

Published

Manage Cloudflare DNS records as CloudFormation resources from AWS CDK, so Route 53 hosted zones are no longer needed.

Readme

@jaggr2/cdk-cf-dns

Manage Cloudflare DNS records as CloudFormation resources from AWS CDK, so that Route 53 hosted zones (and their $0.50/zone/month charge) are no longer needed when Cloudflare is already authoritative for the domain.

The developer experience is modelled on aws-cdk-lib/aws-route53: reference a zone, add records, and let the stack manage them. A single shared Lambda custom resource performs the Cloudflare API calls — one Lambda per stack, no matter how many records you define.

const zone = CloudflareZone.fromZoneId(this, 'Zone', {
  zoneId: 'abc123...',
  apiToken: secretsmanager.Secret.fromSecretNameV2(this, 'CfToken', 'cloudflare/dns-token'),
  zoneName: 'example.com',
});

new CloudflareRecord(this, 'AppCname', {
  zone,
  recordName: 'app',            // relative -> app.example.com
  type: CloudflareRecordType.CNAME,
  content: distribution.distributionDomainName,
  proxied: true,
});

Install

npm install @jaggr2/cdk-cf-dns

aws-cdk-lib and constructs are peer dependencies — you must have them installed in your own project (any CDK v2 app already does):

npm install aws-cdk-lib constructs

You also need esbuild as a dev dependency, because the library's Lambda handler is bundled with NodejsFunction at synthesis time:

npm install --save-dev esbuild

The handler itself has zero runtime npm dependencies — it uses the AWS SDK v3 that is built into the Lambda runtime and Cloudflare's API over fetch.

Cloudflare API token setup

  1. Log in to the Cloudflare dashboard.
  2. Go to My Profile → API Tokens → Create Token.
  3. Use the Edit zone DNS template.
  4. Scope the token to the single zone you intend to manage (do not select "All zones" unless you really need it).
  5. Create the token, then store it in Secrets Manager so only its ARN ever reaches CloudFormation:
aws secretsmanager create-secret --name cloudflare/dns-token --secret-string 'cf_token_here'

The token grants DNS write access to that zone; scope it as narrowly as possible. The token itself never appears in your CDK template, an environment variable, or a log line — the Lambda resolves it from Secrets Manager at runtime.

Finding your Zone ID

Cloudflare dashboard → select the domain → Overview → scroll to the right rail → API section shows the Zone ID (a 32-character hex string).

Usage

CNAME to a CloudFront distribution

const zone = CloudflareZone.fromZoneId(this, 'Zone', {
  zoneId: 'abc123...',
  zoneName: 'example.com',
  apiToken: secretsmanager.Secret.fromSecretNameV2(this, 'CfToken', 'cloudflare/dns-token'),
});

new CloudflareCnameRecord(this, 'AppCname', {
  zone,
  recordName: 'app',                          // -> app.example.com
  content: distribution.distributionDomainName,
  proxied: true,                              // Cloudflare terminates TLS and caches
});

Apex A record

new CloudflareARecord(this, 'Apex', {
  zone,                                       // recordName omitted -> zone apex
  content: '1.2.3.4',
});

TXT record for domain verification

new CloudflareTxtRecord(this, 'Verify', {
  zone,
  recordName: '_amazonses.example.com',
  content: 'LONG_VERIFICATION_STRING_...',     // >255 chars is chunked automatically
});

Managed-by comment

Every record gets a Cloudflare comment identifying it as managed by this library, including the CloudFormation stack name and account id, so records can be traced back to their source stack:

managed by cdk-cf-dns (stack: MyStack, account: 123456789012)

This uses the comment field because Cloudflare only supports record tags on paid (Pro/Business/Enterprise) plans — comments are available on all plans. Pass your own comment to override it, or set managedByCdkComment: false to disable it.

MX records

new CloudflareMxRecord(this, 'Mx', {
  zone,
  content: 'mail.example.com',
  priority: 10,
});

Every record type also works through the generic CloudflareRecord with CloudflareRecordType, including SRV, CAA and URI (which take a data object instead of a plain content string). Thin subclasses (CloudflareARecord, CloudflareAaaaRecord, CloudflareCnameRecord, CloudflareTxtRecord, CloudflareMxRecord, CloudflareCaaRecord) narrow the type and validate their own required fields.

Proxied records and ACM

If you use ACM certificate DNS validation (for example with CloudFront), the _acme-challenge CNAMEs generated by CertificateValidation.fromDns() must be proxied: false — DNS-01 validation requires the raw CNAME to resolve, and a proxied record will never pass. Use a plain CNAME record for validation names:

new CloudflareCnameRecord(this, 'AcmeValidation', {
  zone,
  recordName: record.targetName,   // e.g. _acme-challenge.example.com
  content: record.domainName,      // the validation value
  proxied: false,                  // CRITICAL
  ttl: cdk.Duration.seconds(60),
});

Automated ACM DNS validation

CloudflareValidatedCertificate removes the manual copy-paste step during ACM DNS validation. It creates the certificate and, via a second shared custom resource, polls acm:DescribeCertificate until the validation CNAMEs are populated (they are absent for a few seconds after creation) and writes each one into Cloudflare with proxied: false and ttl: 60. A wildcard SAN and its apex share a single validation record.

const cert = new CloudflareValidatedCertificate(this, 'Cert', {
  domainName: 'example.com',
  subjectAlternativeNames: ['*.example.com'],
  zone,
});

new cloudfront.Distribution(this, 'Dist', {
  defaultBehavior: {
    origin: origin,
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
  },
  domainNames: ['example.com'],
  certificate: cert.certificate,
});

The validation records are left in Cloudflare after issuance — they are harmless. If the certificate is attached to CloudFront it must live in us-east-1.

Limitations

  • No zone creation. Zones must already exist in Cloudflare; this library only references them by Zone ID.
  • No zone-level settings. Record-only. SSL/TLS modes, page rules, workers, and other zone configuration are out of scope.
  • No page rules and no bulk import.
  • Out-of-band edits to records in the Cloudflare dashboard are only reconciled on the next stack update (an update re-applies the desired state; a delete of a record that only exists out-of-band is ignored).
  • Deleting a managed record requires adoptExisting: true if it was created outside the stack, otherwise the deployment fails with a helpful error.

API

See API.md for the generated reference.

License

MIT