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 * * ### Workers for Platforms ### * * For uploading a User Worker to a dispatch namespace: * https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/ * * Define a "dispatchNamespaceName" variable and change the entire "const script = " line to the following: * "const script = await client.workersForPlatforms.dispatch.namespaces.scripts.update(dispatchNamespaceName, scriptName, {" */
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 * * ### Workers for Platforms ### * * For uploading a User Worker to a dispatch namespace: * https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/ * * Define a "dispatchNamespaceName" variable and change the entire "const script = " line to the following: * "const script = await client.workersForPlatforms.dispatch.namespaces.scripts.update(dispatchNamespaceName, scriptName, {" */
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.
"""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
### Workers for Platforms ###
For uploading a User Worker to a dispatch namespace:https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/
Change the entire "script = " line to the following:"script = client.workers_for_platforms.dispatch.namespaces.scripts.update("
Then, define a "dispatch_namespace_name" variable and add a"dispatch_namespace=dispatch_namespace_name" keyword argument to the "update" method."""
import os
from cloudflare import Cloudflare, BadRequestError
API_TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN")if API_TOKEN is None: raise RuntimeError("Please set envar CLOUDFLARE_API_TOKEN")
ACCOUNT_ID = os.environ.get("CLOUDFLARE_ACCOUNT_ID")if ACCOUNT_ID is None: raise RuntimeError("Please set envar CLOUDFLARE_ACCOUNT_ID")
client = Cloudflare(api_token=API_TOKEN)
def main() -> None: """Workers Script Upload Example"""
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, # type: ignore # 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(script.to_json(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
With Workers for Platforms, you can upload User Workers in a dispatch namespace. You can use the exact same example above for that if you simply change the url to:
curl https://api.cloudflare.com/client/v4/accounts/<account_id>/workers/dispatch/namespaces/<dispatch_namespace>/scripts/my-hello-world-script
For this to work, you will first need to configure Workers for Platforms, create a dispatch namespace, and replace <dispatch_namespace>
with your own.
Python Workers (open beta) have their own special text/x-python
content type and python_workers
compatibility flag for uploading.
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.py", "bindings": [ { "type": "plain_text", "name": "MESSAGE", "text": "Hello World!" } ], "compatibility_date": "2025-03-11", "compatibility_flags": [ "python_workers" ] };type=application/json' \ -F 'my-hello-world-script.py=@-;filename=my-hello-world-script.py;type=text/x-python' <<EOFfrom workers import Response
def on_fetch(request, env): return Response(env.MESSAGE)EOF
You can upload Python Workers in any of our language SDKs, even the non-Python ones. Just modify the above SDK examples with the text/x-python
content type, change the script file extension from .mjs
to .py
, change the script content to a Python Worker format, and add the python_workers
compatibility date.
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
-