Direct Uploads·续

console.log(⚙️ Worker id: ${worker.id}); console.log("🔄 Starting asset upload session...");

const uploadResponse = await client.workers.scripts.assets.upload.create( config.workerName, { account_id: config.accountId, manifest, }, );

const { buckets, jwt: uploadJwt } = uploadResponse;

if (!uploadJwt || !buckets) { throw new Error("Failed to start asset upload session"); }

let completionJwt;

if (buckets.length === 0) { console.log("✅ No new assets to upload!"); // Use the initial upload JWT as completion JWT when no uploads are needed completionJwt = uploadJwt; } else { const payloads = await createUploadPayloads( buckets, manifest, config.assetsDirectory, );

completionJwt = await uploadAssets(payloads, uploadJwt, config.accountId); }

console.log("✏️ Creating Worker version...");

// Create a new version with assets const version = await client.workers.beta.workers.versions.create( worker.id, { account_id: config.accountId, main_module: SCRIPT_FILENAME, compatibility_date: new Date().toISOString().split("T")[0], bindings: [ { type: "assets", name: "ASSETS", }, ], assets: { jwt: completionJwt, }, modules: [ { name: SCRIPT_FILENAME, content_type: "application/javascript+module", content_base64: Buffer.from(scriptContent).toString("base64"), }, ], }, );

console.log("🚚 Creating Worker deployment...");

// Create a deployment and point all traffic to the version we created await client.workers.scripts.deployments.create(config.workerName, { account_id: config.accountId, strategy: "percentage", versions: [ { percentage: 100, version_id: version.id, }, ], });

console.log("✅ Deployment successful!");

if (config.subdomain) { console.log(🌍 Your Worker is live! 📍 Base URL: https://${config.workerName}.${config.subdomain}.workers.dev/ 📄 Try accessing: https://${config.workerName}.${config.subdomain}.workers.dev/${exampleFile}); } else { console.log(⚠️ Set up a route, custom domain, or workers.dev subdomain to access your Worker. Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.); } } catch (error) { console.error("❌ Deployment failed:", error); exit(1); } }

main();


```ts
#!/usr/bin/env -S npm run tsn -T

/**
 * Create a Worker that serves static assets
 *
 * This example demonstrates how to:
 * - Upload static assets to Cloudflare Workers
 * - Create and deploy a Worker that serves those assets
 *
 * Docs:
 * - https://developers.cloudflare.com/workers/static-assets/direct-upload
 *
 * Prerequisites:
 * 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/
 * 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/
 * 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/
 *
 * Environment variables:
 *   - CLOUDFLARE_API_TOKEN (required)
 *   - CLOUDFLARE_ACCOUNT_ID (required)
 *   - ASSETS_DIRECTORY (required)
 *   - CLOUDFLARE_SUBDOMAIN (optional)
 *
 * Usage:
 *   Place your static files in the ASSETS_DIRECTORY, then run this script.
 *   Assets will be available at: my-script-with-assets.$subdomain.workers.dev/$filename
 */

import crypto from 'crypto';
import fs from 'fs';
import { readFile } from 'node:fs/promises';
import { extname } from 'node:path';
import path from 'path';
import { exit } from 'node:process';

import Cloudflare from 'cloudflare';

interface Config {
  apiToken: string;
  accountId: string;
  assetsDirectory: string;
  subdomain: string | undefined;
  workerName: string;
}

interface AssetManifest {
  [path: string]: {
    hash: string;
    size: number;
  };
}

interface UploadPayload {
  [hash: string]: string; // base64 encoded content
}

const WORKER_NAME = 'my-worker-with-assets';
const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;

function loadConfig(): Config {
  const apiToken = process.env['CLOUDFLARE_API_TOKEN'];
  if (!apiToken) {
    throw new Error('Missing required environment variable: CLOUDFLARE_API_TOKEN');
  }

const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
  if (!accountId) {
    throw new Error('Missing required environment variable: CLOUDFLARE_ACCOUNT_ID');
  }

const assetsDirectory = process.env['ASSETS_DIRECTORY'];
  if (!assetsDirectory) {
    throw new Error('Missing required environment variable: ASSETS_DIRECTORY');
  }

if (!fs.existsSync(assetsDirectory)) {
    throw new Error(`Assets directory does not exist: ${assetsDirectory}`);
  }

const subdomain = process.env['CLOUDFLARE_SUBDOMAIN'];

return {
    apiToken,
    accountId,
    assetsDirectory,
    subdomain: subdomain || undefined,
    workerName: WORKER_NAME,
  };
}

const config = loadConfig();
const client = new Cloudflare({
  apiToken: config.apiToken,
});

/**
 * Recursively reads all files from a directory and creates a manifest
 * mapping file paths to their hash and size.
 */
function createManifest(directory: string): AssetManifest {
  const manifest: AssetManifest = {};

function processDirectory(currentDir: string, basePath = ''): void {
    try {
      const entries = fs.readdirSync(currentDir, { withFileTypes: true });

for (const entry of entries) {
        const fullPath = path.join(currentDir, entry.name);
        const relativePath = path.join(basePath, entry.name);

if (entry.isDirectory()) {
          processDirectory(fullPath, relativePath);
        } else if (entry.isFile()) {
          try {
            const fileContent = fs.readFileSync(fullPath);
            const extension = extname(relativePath).substring(1);

// Generate a hash for the file
            const hash = crypto
              .createHash('sha256')
              .update(fileContent.toString('base64') + extension)
              .digest('hex')
              .slice(0, 32);

// Normalize path separators to forward slashes
            const manifestPath = `/${relativePath.replace(/\\/g, '/')}`;

manifest[manifestPath] = {
              hash,
              size: fileContent.length,
            };

console.log(`Added to manifest: ${manifestPath} (${fileContent.length} bytes)`);
          } catch (error) {
            console.warn(`Failed to process file ${fullPath}:`, error);
          }
        }
      }
    } catch (error) {
      throw new Error(`Failed to read directory ${currentDir}: ${error}`);
    }
  }

processDirectory(directory);

if (Object.keys(manifest).length === 0) {
    throw new Error(`No files found in assets directory: ${directory}`);
  }

console.log(`Created manifest with ${Object.keys(manifest).length} files`);
  return manifest;
}

/**
 * Generates the Worker script content that serves static assets
 */
function generateWorkerScript(exampleFile: string): string {
  return `
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

// Serve a simple index page at the root
    if (url.pathname === '/') {
      return new Response(
        \`<!DOCTYPE html>
<html>
<head>
  <title>Static Assets Worker</title>
  <style>
    body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
    h1 { color: #f38020; }
    .asset-info { background: #f5f5f5; padding: 15px; border-radius: 5px; }
  </style>
</head>
<body>
  <h1>This Worker serves static assets!</h1>
  <div class="asset-info">
    <p><strong>To access your assets,</strong> add <code>/filename</code> to the URL.</p>
    <p>Try visiting <a href="\${url.origin}/${exampleFile}">/${exampleFile}</a></p>
  </div>
</body>
</html>\`,
        {
          status: 200,
          headers: { 'Content-Type': 'text/html' }
        }
      );
    }

// Serve static assets for all other paths
    return env.ASSETS.fetch(request);
  }
};
  `.trim();
}

/**
 * Creates upload payloads from buckets and manifest
 */
async function createUploadPayloads(
  buckets: string[][],
  manifest: AssetManifest,
  assetsDirectory: string
): Promise<UploadPayload[]> {
  const payloads: UploadPayload[] = [];

for (const bucket of buckets) {
    const payload: UploadPayload = {};

for (const hash of bucket) {
      // Find the file path for this hash
      const manifestEntry = Object.entries(manifest).find(
        ([_, data]) => data.hash === hash
      );

if (!manifestEntry) {
        throw new Error(`Could not find file for hash: ${hash}`);
      }

const [relativePath] = manifestEntry;
      const fullPath = path.join(assetsDirectory, relativePath);

try {
        const fileContent = await readFile(fullPath);
        payload[hash] = fileContent.toString('base64');
        console.log(`Prepared for upload: ${relativePath}`);
      } catch (error) {
        throw new Error(`Failed to read file ${fullPath}: ${error}`);
      }
    }

payloads.push(payload);
  }

return payloads;
}

/**
 * Uploads asset payloads
 */
async function uploadAssets(
  payloads: UploadPayload[],
  uploadJwt: string,
  accountId: string
): Promise<string> {
  let completionJwt: string | undefined;

console.log(`Uploading ${payloads.length} payload(s)...`);

for (let i = 0; i < payloads.length; i++) {
    const payload = payloads[i]!;
    console.log(`Uploading payload ${i + 1}/${payloads.length}...`);

try {
      const response = await client.workers.assets.upload.create(
        {
          account_id: accountId,
          base64: true,
          body: payload,
        },
        {
          headers: { Authorization: `Bearer ${uploadJwt}` },
        }
      );

if (response?.jwt) {
        completionJwt = response.jwt;
      }
    } catch (error) {
      throw new Error(`Failed to upload payload ${i + 1}: ${error}`);
    }
  }

if (!completionJwt) {
    throw new Error('Upload completed but no completion JWT received');
  }

console.log('✅ All assets uploaded successfully');
  return completionJwt;
}

async function main(): Promise&lt;void> {
  try {
    console.log('🚀 Starting Worker creation and deployment with static assets...');
    console.log(`📁 Assets directory: ${config.assetsDirectory}`);

console.log('📝 Creating asset manifest...');
    const manifest = createManifest(config.assetsDirectory);
    const exampleFile = Object.keys(manifest)[0]?.replace(/^\//, '') || 'file.txt';

const scriptContent = generateWorkerScript(exampleFile);

let worker;
    try {
      worker = await client.workers.beta.workers.get(config.workerName, {
        account_id: config.accountId,
      });
      console.log(`♻️  Worker ${config.workerName} already exists. Using it.`);
    } catch (error) {
      if (!(error instanceof Cloudflare.NotFoundError)) { throw error; }
      console.log(`✏️  Creating Worker ${config.workerName}...`);
      worker = await client.workers.beta.workers.create({
        account_id: config.accountId,
        name: config.workerName,
        subdomain: {
          enabled: config.subdomain !== undefined,
        },
        observability: {
          enabled: true,
        },
      });
    }

console.log(`⚙️  Worker id: ${worker.id}`);
    console.log('🔄 Starting asset upload session...');

const uploadResponse = await client.workers.scripts.assets.upload.create(
      config.workerName,
      {
        account_id: config.accountId,
        manifest,
      }
    );

const { buckets, jwt: uploadJwt } = uploadResponse;

if (!uploadJwt || !buckets) {
      throw new Error('Failed to start asset upload session');
    }

let completionJwt: string;

if (buckets.length === 0) {
      console.log('✅ No new assets to upload!');
      // Use the initial upload JWT as completion JWT when no uploads are needed
      completionJwt = uploadJwt;
    } else {
      const payloads = await createUploadPayloads(
        buckets,
        manifest,
        config.assetsDirectory
      );

completionJwt = await uploadAssets(
        payloads,
        uploadJwt,
        config.accountId
      );
    }

console.log('✏️  Creating Worker version...');

// Create a new version with assets
    const version = await client.workers.beta.workers.versions.create(worker.id, {
      account_id: config.accountId,
      main_module: SCRIPT_FILENAME,
      compatibility_date: new Date().toISOString().split('T')[0]!,
      bindings: [
        {
          type: 'assets',
          name: 'ASSETS',
        },
      ],
      assets: {
        jwt: completionJwt,
      },
      modules: [
        {
          name: SCRIPT_FILENAME,
          content_type: 'application/javascript+module',
          content_base64: Buffer.from(scriptContent).toString('base64'),
        },
      ],
    });

console.log('🚚 Creating Worker deployment...');

// Create a deployment and point all traffic to the version we created
    await client.workers.scripts.deployments.create(config.workerName, {
      account_id: config.accountId,
      strategy: 'percentage',
      versions: [
        {
            percentage: 100,
            version_id: version.id,
          },
        ],
    });

console.log('✅ Deployment successful!');

if (config.subdomain) {
      console.log(`
🌍 Your Worker is live!
📍 Base URL: https://${config.workerName}.${config.subdomain}.workers.dev/
📄 Try accessing: https://${config.workerName}.${config.subdomain}.workers.dev/${exampleFile}
`);
    } else {
      console.log(`
⚠️  Set up a route, custom domain, or workers.dev subdomain to access your Worker.
Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.
`);
    }
  } catch (error) {
    console.error('❌ Deployment failed:', error);
    exit(1);
  }
}

main();

Was this helpful?

YesNo

On this page

Direct Uploads·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/static-assets/direct-upload/#page","headline":"Direct Uploads","description":"Upload assets through the Workers API.","url":"https://developers.cloudflare.com/workers/static-assets/direct-upload/","inLanguage":"en","image":"https://developers.cloudflare.com/workers/static-assets/direct-upload/og.png?v=31b44b3abc01ae29","dateModified":"2026-08-10","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Run front-end websites — static or dynamic — directly on Cloudflare's global network. title: Get Started image: https://developers.cloudflare.com/workers/static-assets/get-started/og.png?v=d2fdc9dd705cddd5


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt
Use this file to discover all available pages before exploring further.