Infrastructure as Code (IaC)
Uploading and managing a Worker is easy with Wrangler, but sometimes you need to do it more programmatically. You might do this with IaC ("Infrastructure as Code") tools or by calling the Cloudflare API ↗ directly. Use cases for the API include build and deploy scripts, CI/CD pipelines, custom dev tools, or testing. We provide API SDK libraries for common languages that make interacting with the API easier, such as cloudflare-typescript ↗ or cloudflare-python ↗. For IaC, a common tool is HashiCorp's Terraform where you can use the Cloudflare Provider to create and manage Workers resources.
Here are examples of deploying a Worker with common tools and languages. In particular, they highlight how to upload script content and metadata which is different with each approach. Reference the Upload Worker Module API docs here ↗.
All of these examples need an account id and API token (not Global API key) to work.
In this example, you need a local file named my-hello-world-script.mjs
with script content similar to the above examples. Replace account_id
with your own. Learn more about the Cloudflare Terraform Provider here, and see an example with all the Workers script resource settings here ↗.
terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" version = "~> 5" } }}
resource "cloudflare_workers_script" "my-hello-world-script" { account_id = "<replace_me>" script_name = "my-hello-world-script" main_module = "my-hello-world-script.mjs" content = trimspace(file("my-hello-world-script.mjs")) bindings = [{ name = "MESSAGE" type = "plain_text" text = "Hello World!" }]}
This example uses the cloudflare-typescript ↗ library which provides convenient access to the Cloudflare REST API from server-side JavaScript or TypeScript.
#!/usr/bin/env -S npm run tsn -T
/* * Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ * (Not Global API Key!) * * Find your account id: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/ * * Set these environment variables: * - CLOUDFLARE_API_TOKEN * - CLOUDFLARE_ACCOUNT_ID */
import Cloudflare from "cloudflare";import { toFile } from "cloudflare/index";
const apiToken = process.env["CLOUDFLARE_API_TOKEN"] ?? "";if (!apiToken) { throw new Error("Please set envar CLOUDFLARE_ACCOUNT_ID");}
const accountID = process.env["CLOUDFLARE_ACCOUNT_ID"] ?? "";if (!accountID) { throw new Error("Please set envar CLOUDFLARE_API_TOKEN");}
const client = new Cloudflare({ apiToken: apiToken,});
async function main() { const scriptName = "my-hello-world-script"; const scriptFileName = `${scriptName}.mjs`;
// Workers Scripts prefer Module Syntax // https://blog.cloudflare.com/workers-javascript-modules/ const scriptContent = ` export default { async fetch(request, env, ctx) { return new Response(env.MESSAGE, { status: 200 }); } }; `;
try { // https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/ const script = await client.workers.scripts.update(scriptName, { account_id: accountID, // https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/ metadata: { main_module: scriptFileName, bindings: [ { type: "plain_text", name: "MESSAGE", text: "Hello World!", }, ], }, files: { // Add main_module file [scriptFileName]: await toFile( Buffer.from(scriptContent), scriptFileName, { type: "application/javascript+module", }, ), // Can add other files, such as more modules or source maps // [sourceMapFileName]: await toFile(Buffer.from(sourceMapContent), sourceMapFileName, { // type: 'application/source-map', // }), }, }); console.log("Script Upload success!"); console.log(JSON.stringify(script, null, 2)); } catch (error) { console.error("Script Upload failure!"); console.error(error); }}
main();
#!/usr/bin/env -S npm run tsn -T
/* * Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ * (Not Global API Key!) * * Find your account id: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/ * * Set these environment variables: * - CLOUDFLARE_API_TOKEN * - CLOUDFLARE_ACCOUNT_ID */
import Cloudflare from 'cloudflare';import { toFile } from 'cloudflare/index';
const apiToken = process.env['CLOUDFLARE_API_TOKEN'] ?? '';if (!apiToken) { throw new Error('Please set envar CLOUDFLARE_ACCOUNT_ID');}
const accountID = process.env['CLOUDFLARE_ACCOUNT_ID'] ?? '';if (!accountID) { throw new Error('Please set envar CLOUDFLARE_API_TOKEN');}
const client = new Cloudflare({ apiToken: apiToken,});
async function main() { const scriptName = 'my-hello-world-script'; const scriptFileName = `${scriptName}.mjs`;
// Workers Scripts prefer Module Syntax // https://blog.cloudflare.com/workers-javascript-modules/ const scriptContent = ` export default { async fetch(request, env, ctx) { return new Response(env.MESSAGE, { status: 200 }); } }; `;
try { // https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/ const script = await client.workers.scripts.update( scriptName, { account_id: accountID, // https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/ metadata: { main_module: scriptFileName, bindings: [ { type: 'plain_text', name: 'MESSAGE', text: 'Hello World!', }, ], }, files: { // Add main_module file [scriptFileName]: await toFile(Buffer.from(scriptContent), scriptFileName, { type: 'application/javascript+module', }), // Can add other files, such as more modules or source maps // [sourceMapFileName]: await toFile(Buffer.from(sourceMapContent), sourceMapFileName, { // type: 'application/source-map', // }), }, }, ); console.log('Script Upload success!'); console.log(JSON.stringify(script, null, 2)); } catch (error) { console.error('Script Upload failure!'); console.error(error); }}
main();
This example uses the cloudflare-python ↗ library.
12 collapsed lines
"""Workers Script Upload Example
Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/(Not Global API Key!)
Find your account id: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
Set these environment variables:- CLOUDFLARE_API_TOKEN- CLOUDFLARE_ACCOUNT_ID"""
import jsonimport os
from cloudflare import Cloudflarefrom cloudflare import BadRequestError
API_TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN")if API_TOKEN is None: raise Exception("Please set envar CLOUDFLARE_API_TOKEN")
ACCOUNT_ID = os.environ.get("CLOUDFLARE_ACCOUNT_ID")if ACCOUNT_ID is None: raise Exception("Please set envar CLOUDFLARE_ACCOUNT_ID")
client = Cloudflare(api_token=API_TOKEN)
def main() -> None: script_name = "my-hello-world-script" script_file_name = f"{script_name}.mjs"
# Workers Scripts prefer Module Syntax # https://blog.cloudflare.com/workers-javascript-modules/ script_content = """ export default { async fetch(request, env, ctx) { return new Response(env.MESSAGE, { status: 200 }); } }; """
try: # https://developers.cloudflare.com/api/resources/workers/subresources/scripts/methods/update/ script = client.workers.scripts.update( script_name, account_id=ACCOUNT_ID, # https://developers.cloudflare.com/workers/configuration/multipart-upload-metadata/ metadata={ "main_module": script_file_name, "bindings": [ { "type": "plain_text", "name": "MESSAGE", "text": "Hello World!", } ], }, files={ # Add main_module file script_file_name: (script_file_name, bytes(script_content, "utf-8"), "application/javascript+module") # Can add other files, such as more modules or source maps # source_map_file_name: (source_map_file_name, bytes(source_map_content, "utf-8"), "application/source-map") }, ) print('Script Upload success!') print(json.dumps(script, indent=2)) except BadRequestError as err: print('Script Upload failure!') print(err)
if __name__ == "__main__": main()
Open a terminal or create a shell script to upload a Worker easily with curl. For this example, replace <account_id>
and <api_token>
with your own. What's notable about interacting with the Workers Script Upload API directly is that it uses multipart/form-data ↗ for uploading metadata, multiple JavaScript modules, source maps, and more. This is abstracted away in Terraform and the API libraries.
curl https://api.cloudflare.com/client/v4/accounts/<account_id>/workers/scripts/my-hello-world-script \ -X PUT \ -H 'Authorization: Bearer <api_token>' \ -F 'metadata={ "main_module": "my-hello-world-script.mjs", "bindings": [ { "type": "plain_text", "name": "MESSAGE", "text": "Hello World!" } ], "compatibility_date": "2025-03-11" };type=application/json' \ -F 'my-hello-world-script.mjs=@-;filename=my-hello-world-script.mjs;type=application/javascript+module' <<EOFexport default { async fetch(request, env, ctx) { return new Response(env.MESSAGE, { status: 200 }); }};EOF
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Products
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- 2025 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark