cloudflarer wraps the Cloudflare
v4 REST API. Most users will reach for a handful of named endpoint
helpers, but anything not yet wrapped is reachable through the generic
cf_request()
function.
List endpoints return a data.frame by default (one row per record,
list-columns for nested fields). The data.frames carry the
tbl_df/tbl classes so callers with the tibble
package loaded get tibble-style printing for free. To get the raw nested
list back, pass as_df = FALSE.
Set a Cloudflare API token in ~/.Renviron and restart
R:
CLOUDFLARE_API_TOKEN=your-token-here
The Global API Key (legacy) also works through
CLOUDFLARE_EMAIL + CLOUDFLARE_API_KEY. See
vignette("authentication") for the full setup.
Verify the connection:
cf_user()
#> $id
#> [1] "user-1"
#>
#> $email
#> [1] "[email protected]"
#>
#> $first_name
#> [1] "Ada"
#>
#> $last_name
#> [1] "Lovelace"
cf_list_accounts()
#> # A data frame: 1 × 2
#> id name
#> * <chr> <chr>
#> 1 abc123 Example Org
cf_list_zones()
#> # A data frame: 2 × 3
#> id name status
#> * <chr> <chr> <chr>
#> 1 abc123 example.com active
#> 2 def456 example.org activeBoth helpers walk every page of results automatically. Pass
max_pages to stop early while exploring:
cf_list_dns_records() returns a data.frame;
cf_get_dns_record(), cf_create_dns_record(),
cf_update_dns_record(), and
cf_delete_dns_record() cover the CRUD lifecycle:
zones <- cf_list_zones()
zone_id <- zones$id[1]
records <- cf_list_dns_records(zone_id, type = "A")
head(records[, c("name", "content", "proxied")])
#> # A data frame: 2 × 3
#> name content proxied
#> * <chr> <chr> <lgl>
#> 1 example.com 192.0.2.1 TRUE
#> 2 shop.example.com 192.0.2.2 FALSE
cf_create_dns_record(
zone_id,
type = "A",
name = "blog.example.com",
content = "192.0.2.5",
proxied = TRUE
)
#> $id
#> [1] "rec-new"
#>
#> $type
#> [1] "A"
#>
#> $name
#> [1] "blog.example.com"
#>
#> $content
#> [1] "192.0.2.5"
#>
#> $proxied
#> [1] TRUE
cf_update_dns_record(zone_id, "rec-1", content = "192.0.2.99")
#> $id
#> [1] "rec-1"
#>
#> $type
#> [1] "A"
#>
#> $name
#> [1] "example.com"
#>
#> $content
#> [1] "192.0.2.99"
#>
#> $proxied
#> [1] TRUE
cf_delete_dns_record(zone_id, "rec-1")
#> $id
#> [1] "rec-1"Targeted purges are preferred over wipes:
settings <- cf_get_zone_settings(zone_id)
settings[settings$id %in% c("ssl", "cache_level", "security_level"),
c("id", "value")]
#> # A data frame: 3 × 2
#> id value
#> * <chr> <chr>
#> 1 ssl full
#> 2 cache_level aggressive
#> 3 security_level medium
cf_get_zone_setting(zone_id, "ssl")
#> $id
#> [1] "ssl"
#>
#> $value
#> [1] "full"
#>
#> $modified_on
#> [1] "2026-05-01T00:00:00Z"
#>
#> $editable
#> [1] TRUEEvery endpoint in the Cloudflare API
reference is reachable through cf_request(), which
builds an authenticated request for a path relative to
/client/v4. Pipe it through the httr2 request
verbs you need, perform it, and unwrap the envelope with
cf_resp():
cf_request("user/tokens/verify") |>
httr2::req_perform() |>
cf_resp()
#> $id
#> [1] "tok-1"
#>
#> $status
#> [1] "active"
cf_request("zones/abc123/dns_records") |>
httr2::req_method("POST") |>
httr2::req_body_json(list(
type = "A",
name = "www.example.com",
content = "192.0.2.1",
proxied = TRUE
)) |>
httr2::req_perform() |>
cf_resp()
#> $id
#> [1] "rec-new"
#>
#> $type
#> [1] "A"
#>
#> $name
#> [1] "www.example.com"
#>
#> $content
#> [1] "192.0.2.1"
#>
#> $proxied
#> [1] TRUEFor paginated list endpoints, pipe the request into
cf_collect() to get the raw list, then tidy it with
cf_records_to_df():
Both HTTP-level failures and Cloudflare envelope failures
(success: false) raise a condition of class
cloudflarer_error, so you can catch them with
tryCatch():