
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Thu, 09 Apr 2026 10:36:01 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Introducing Moltworker: a self-hosted personal AI agent, minus the minis]]></title>
            <link>https://blog.cloudflare.com/moltworker-self-hosted-ai-agent/</link>
            <pubDate>Thu, 29 Jan 2026 14:00:00 GMT</pubDate>
            <description><![CDATA[ Moltworker is a middleware Worker and adapted scripts that allows running OpenClaw (formerly Moltbot, formerly Clawdbot) on Cloudflare's Sandbox SDK and our Developer Platform APIs. So you can self-host an AI personal assistant — without any new hardware. ]]></description>
            <content:encoded><![CDATA[ <p><i></i></p><p><i>Editorial note: As of January 30, 2026, Moltbot has been </i><a href="https://openclaw.ai/blog/introducing-openclaw"><i><u>renamed</u></i></a><i> to OpenClaw.</i></p><p>The Internet woke up this week to a flood of people <a href="https://x.com/AlexFinn/status/2015133627043270750"><u>buying Mac minis</u></a> to run <a href="https://github.com/moltbot/moltbot"><u>Moltbot</u></a> (formerly Clawdbot), an open-source, self-hosted AI agent designed to act as a personal assistant. Moltbot runs in the background on a user's own hardware, has a sizable and growing list of integrations for chat applications, AI models, and other popular tools, and can be controlled remotely. Moltbot can help you with your finances, social media, organize your day — all through your favorite messaging app.</p><p>But what if you don’t want to buy new dedicated hardware? And what if you could still run your Moltbot efficiently and securely online? Meet <a href="https://github.com/cloudflare/moltworker"><u>Moltworker</u></a>, a middleware Worker and adapted scripts that allows running Moltbot on Cloudflare's Sandbox SDK and our Developer Platform APIs.</p>
    <div>
      <h2>A personal assistant on Cloudflare — how does that work? </h2>
      <a href="#a-personal-assistant-on-cloudflare-how-does-that-work">
        
      </a>
    </div>
    <p>Cloudflare Workers has never been <a href="https://developers.cloudflare.com/workers/runtime-apis/nodejs/"><u>as compatible</u></a> with Node.js as it is now. Where in the past we had to mock APIs to get some packages running, now those APIs are supported natively by the Workers Runtime.</p><p>This has changed how we can build tools on Cloudflare Workers. When we first implemented <a href="https://developers.cloudflare.com/browser-rendering/playwright/"><u>Playwright</u></a>, a popular framework for web testing and automation that runs on <a href="https://developers.cloudflare.com/browser-rendering/"><u>Browser Rendering</u></a>, we had to rely on <a href="https://www.npmjs.com/package/memfs"><u>memfs</u></a>. This was bad because not only is memfs a hack and an external dependency, but it also forced us to drift away from the official Playwright codebase. Thankfully, with more Node.js compatibility, we were able to start using <a href="https://github.com/cloudflare/playwright/pull/62/changes"><u>node:fs natively</u></a>, reducing complexity and maintainability, which makes upgrades to the latest versions of Playwright easy to do.</p><p>The list of Node.js APIs we support natively keeps growing. The blog post “<a href="https://blog.cloudflare.com/nodejs-workers-2025/"><u>A year of improving Node.js compatibility in Cloudflare Workers</u></a>” provides an overview of where we are and what we’re doing.</p><p>We measure this progress, too. We recently ran an experiment where we took the 1,000 most popular NPM packages, installed and let AI loose, to try to run them in Cloudflare Workers, <a href="https://ghuntley.com/ralph/"><u>Ralph Wiggum as a "software engineer"</u></a> style, and the results were surprisingly good. Excluding the packages that are build tools, CLI tools or browser-only and don’t apply, only 15 packages genuinely didn’t work. <b>That's 1.5%</b>.</p><p>Here’s a graphic of our Node.js API support over time:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5GhwKJq2A2wG79I3NdhhDl/e462c30daf46b1b36d3f06bff479596b/image9.png" />
          </figure><p>We put together a page with the results of our internal experiment on npm packages support <a href="https://worksonworkers.southpolesteve.workers.dev/"><u>here</u></a>, so you can check for yourself.</p><p>Moltbot doesn’t necessarily require a lot of Workers Node.js compatibility because most of the code runs in a container anyway, but we thought it would be important to highlight how far we got supporting so many packages using native APIs. This is because when starting a new AI agent application from scratch, we can actually run a lot of the logic in Workers, closer to the user.</p><p>The other important part of the story is that the list of <a href="https://developers.cloudflare.com/directory/?product-group=Developer+platform"><u>products and APIs</u></a> on our Developer Platform has grown to the point where anyone can <a href="https://www.cloudflare.com/developer-platform/solutions/hosting/">build and run any kind of application</a> — even the most complex and demanding ones — on Cloudflare. And once launched, every application running on our Developer Platform immediately benefits from our secure and scalable global network.</p><p>Those products and services gave us the ingredients we needed to get started. First, we now have <a href="https://sandbox.cloudflare.com/"><u>Sandboxes</u></a>, where you can run untrusted code securely in isolated environments, providing a place to run the service. Next, we now have <a href="https://developers.cloudflare.com/browser-rendering/"><u>Browser Rendering</u></a>, where you can programmatically control and interact with headless browser instances. And finally, <a href="https://developers.cloudflare.com/r2/"><u>R2</u></a>, where you can store objects persistently. With those building blocks available, we could begin work on adapting Moltbot.</p>
    <div>
      <h2>How we adapted Moltbot to run on us</h2>
      <a href="#how-we-adapted-moltbot-to-run-on-us">
        
      </a>
    </div>
    <p>Moltbot on Workers, or Moltworker, is a combination of an entrypoint Worker that acts as an API router and a proxy between our APIs and the isolated environment, both protected by Cloudflare Access. It also provides an administration UI and connects to the Sandbox container where the standard Moltbot Gateway runtime and its integrations are running, using R2 for persistent storage.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3OD2oHgy5ilHpQO2GJvcLU/836a55b67a626d2cd378a654ad47901d/newdiagram.png" />
          </figure><p><sup>High-level architecture diagram of Moltworker.</sup></p><p>Let's dive in more.</p>
    <div>
      <h3>AI Gateway</h3>
      <a href="#ai-gateway">
        
      </a>
    </div>
    <p>Cloudflare AI Gateway acts as a proxy between your AI applications and any popular <a href="https://developers.cloudflare.com/ai-gateway/usage/providers/"><u>AI provider</u></a>, and gives our customers centralized visibility and control over the requests going through.</p><p>Recently we announced support for <a href="https://developers.cloudflare.com/changelog/2025-08-25-secrets-store-ai-gateway/"><u>Bring Your Own Key (BYOK)</u></a>, where instead of passing your provider secrets in plain text with every request, we centrally manage the secrets for you and can use them with your gateway configuration.</p><p>An even better option where you don’t have to manage AI providers' secrets at all end-to-end is to use <a href="https://developers.cloudflare.com/ai-gateway/features/unified-billing/"><u>Unified Billing</u></a>. In this case you top up your account with credits and use AI Gateway with any of the supported providers directly, Cloudflare gets charged, and we will deduct credits from your account.</p><p>To make Moltbot use AI Gateway, first we create a new gateway instance, then we enable the Anthropic provider for it, then we either add our Claude key or purchase credits to use Unified Billing, and then all we need to do is set the ANTHROPIC_BASE_URL environment variable so Moltbot uses the AI Gateway endpoint. That’s it, no code changes necessary.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/cMWRXgHR0mFLc5kp74nYk/a47fa09bdbb6acb3deb60fb16537945d/image11.png" />
          </figure><p>Once Moltbot starts using AI Gateway, you’ll have full visibility on costs and have access to logs and analytics that will help you understand how your AI agent is using the AI providers.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5GOrNdgtdwMcU4bE8oLE19/6bc29bcac643125f5332a8ffba9d1322/image1.png" />
          </figure><p>Note that Anthropic is one option; Moltbot supports <a href="https://www.molt.bot/integrations"><u>other</u></a> AI providers and so does <a href="https://developers.cloudflare.com/ai-gateway/usage/providers/"><u>AI Gateway</u></a>. The advantage of using AI Gateway is that if a better model comes along from any provider, you don’t have to swap keys in your AI Agent configuration and redeploy — you can simply switch the model in your gateway configuration. And more, you specify model or provider <a href="https://developers.cloudflare.com/ai-gateway/configuration/fallbacks/"><u>fallbacks</u></a> to handle request failures and ensure reliability.</p>
    <div>
      <h3>Sandboxes</h3>
      <a href="#sandboxes">
        
      </a>
    </div>
    <p>Last year we anticipated the growing need for AI agents to run untrusted code securely in isolated environments, and we <a href="https://developers.cloudflare.com/changelog/2025-06-24-announcing-sandboxes/"><u>announced</u></a> the <a href="https://developers.cloudflare.com/sandbox/"><u>Sandbox SDK</u></a>. This SDK is built on top of <a href="https://developers.cloudflare.com/containers/"><u>Cloudflare Containers</u></a>, but it provides a simple API for executing commands, managing files, running background processes, and exposing services — all from your Workers applications.</p><p>In short, instead of having to deal with the lower-level Container APIs, the Sandbox SDK gives you developer-friendly APIs for secure code execution and handles the complexity of container lifecycle, networking, file systems, and process management — letting you focus on building your application logic with just a few lines of TypeScript. Here’s an example:</p>
            <pre><code>import { getSandbox } from '@cloudflare/sandbox';
export { Sandbox } from '@cloudflare/sandbox';

export default {
  async fetch(request: Request, env: Env): Promise&lt;Response&gt; {
    const sandbox = getSandbox(env.Sandbox, 'user-123');

    // Create a project structure
    await sandbox.mkdir('/workspace/project/src', { recursive: true });

    // Check node version
    const version = await sandbox.exec('node -v');

    // Run some python code
    const ctx = await sandbox.createCodeContext({ language: 'python' });
    await sandbox.runCode('import math; radius = 5', { context: ctx });
    const result = await sandbox.runCode('math.pi * radius ** 2', { context: ctx });

    return Response.json({ version, result });
  }
};</code></pre>
            <p>This fits like a glove for Moltbot. Instead of running Docker in your local Mac mini, we run Docker on Containers, use the Sandbox SDK to issue commands into the isolated environment and use callbacks to our entrypoint Worker, effectively establishing a two-way communication channel between the two systems.</p>
    <div>
      <h3>R2 for persistent storage</h3>
      <a href="#r2-for-persistent-storage">
        
      </a>
    </div>
    <p>The good thing about running things in your local computer or VPS is you get persistent storage for free. Containers, however, are inherently <a href="https://developers.cloudflare.com/containers/platform-details/architecture/"><u>ephemeral</u></a>, meaning data generated within them is lost upon deletion. Fear not, though — the Sandbox SDK provides the sandbox.mountBucket() that you can use to automatically, well, mount your R2 bucket as a filesystem partition when the container starts.</p><p>Once we have a local directory that is guaranteed to survive the container lifecycle, we can use that for Moltbot to store session memory files, conversations and other assets that are required to persist.</p>
    <div>
      <h3>Browser Rendering for browser automation</h3>
      <a href="#browser-rendering-for-browser-automation">
        
      </a>
    </div>
    <p>AI agents rely heavily on browsing the sometimes not-so-structured web. Moltbot utilizes dedicated Chromium instances to perform actions, navigate the web, fill out forms, take snapshots, and handle tasks that require a web browser. Sure, we can run Chromium on Sandboxes too, but what if we could simplify and use an API instead?</p><p>With Cloudflare’s <a href="https://developers.cloudflare.com/browser-rendering/"><u>Browser Rendering</u></a>, you can programmatically control and interact with headless browser instances running at scale in our edge network. We support <a href="https://developers.cloudflare.com/browser-rendering/puppeteer/"><u>Puppeteer</u></a>, <a href="https://developers.cloudflare.com/browser-rendering/stagehand/"><u>Stagehand</u></a>, <a href="https://developers.cloudflare.com/browser-rendering/playwright/"><u>Playwright</u></a> and other popular packages so that developers can onboard with minimal code changes. We even support <a href="https://developers.cloudflare.com/browser-rendering/playwright/playwright-mcp/"><u>MCP</u></a> for AI.</p><p>In order to get Browser Rendering to work with Moltbot we do two things:</p><ul><li><p>First we create a <a href="https://github.com/cloudflare/moltworker/blob/main/src/routes/cdp.ts"><u>thin CDP proxy</u></a> (<a href="https://chromedevtools.github.io/devtools-protocol/"><u>CDP</u></a> is the protocol that allows instrumenting Chromium-based browsers) from the Sandbox container to the Moltbot Worker, back to Browser Rendering using the Puppeteer APIs.</p></li><li><p>Then we inject a <a href="https://github.com/cloudflare/moltworker/pull/20"><u>Browser Rendering skill</u></a> into the runtime when the Sandbox starts.</p></li></ul>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1ZvQa7vS1T9Mm3nywqarQZ/9dec3d8d06870ee575a519440d34c499/image12.png" />
          </figure><p>From the Moltbot runtime perspective, it has a local CDP port it can connect to and perform browser tasks.</p>
    <div>
      <h3>Zero Trust Access for authentication policies</h3>
      <a href="#zero-trust-access-for-authentication-policies">
        
      </a>
    </div>
    <p>Next up we want to protect our APIs and Admin UI from unauthorized access. Doing authentication from scratch is hard, and is typically the kind of wheel you don’t want to reinvent or have to deal with. Zero Trust Access makes it incredibly easy to protect your application by defining specific policies and login methods for the endpoints. </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1MDXXjbMs4PViN3kp9iFBY/a3095f07c986594d0c07d0276dbf22cc/image3.png" />
          </figure><p><sup>Zero Trust Access Login methods configuration for the Moltworker application.</sup></p><p>Once the endpoints are protected, Cloudflare will handle authentication for you and automatically include a <a href="https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/application-token/"><u>JWT token</u></a> with every request to your origin endpoints. You can then <a href="https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/"><u>validate</u></a> that JWT for extra protection, to ensure that the request came from Access and not a malicious third party.</p><p>Like with AI Gateway, once all your APIs are behind Access you get great observability on who the users are and what they are doing with your Moltbot instance.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3BV4eqxKPXTiq18vvVpmZh/e034b7e7ea637a00c73c2ebe4d1400aa/image8.png" />
          </figure>
    <div>
      <h2>Moltworker in action</h2>
      <a href="#moltworker-in-action">
        
      </a>
    </div>
    <p>Demo time. We’ve put up a Slack instance where we could play with our own instance of Moltbot on Workers. Here are some of the fun things we’ve done with it.</p><p>We hate bad news.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4FxN935AgINZ8953WSswKB/e52d3eb268aa0732c5e6aa64a8e2adba/image6.png" />
          </figure><p>Here’s a chat session where we ask Moltbot to find the shortest route between Cloudflare in London and Cloudflare in Lisbon using Google Maps and take a screenshot in a Slack channel. It goes through a sequence of steps using Browser Rendering to navigate Google Maps and does a pretty good job at it. Also look at Moltbot’s memory in action when we ask him the second time.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1phWt3cVUwxe9tvCYpuAW3/97f456094ede6ca8fb55bf0dddf65d5b/image10.png" />
          </figure><p>We’re in the mood for some Asian food today, let’s get Moltbot to work for help.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6nJY7GOCopGnMy4IY7KMcf/0d57794df524780c3f4b27e65c968e19/image5.png" />
          </figure><p>We eat with our eyes too.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/5BzB9pqJhuevRbOSJloeG0/23c2905f0c12c1e7f104aa28fcc1f595/image7.png" />
          </figure><p>Let’s get more creative and ask Moltbot to create a video where it browses our developer documentation. As you can see, it downloads and runs ffmpeg to generate the video out of the frames it captured in the browser.</p><div>
  
</div>
    <div>
      <h2>Run your own Moltworker</h2>
      <a href="#run-your-own-moltworker">
        
      </a>
    </div>
    <p>We open-sourced our implementation and made it available at<a href="https://github.com/cloudflare/moltworker"> <u>https://github.com/cloudflare/moltworker</u></a>, so you can deploy and run your own Moltbot on top of Workers today.</p><p>The <a href="https://github.com/cloudflare/moltworker/blob/main/README.md">README</a> guides you through the necessary setup steps. You will need a Cloudflare account and a <a href="https://developers.cloudflare.com/workers/platform/pricing/"><u>Workers Paid plan</u></a> to access Sandbox Containers; however, all other products are either entirely free (like <a href="https://developers.cloudflare.com/ai-gateway/reference/pricing/"><u>AI Gateway</u></a>) or include generous <a href="https://developers.cloudflare.com/r2/pricing/#free-tier"><u>free tiers </u></a>that allow you to get started and scale under reasonable limits.</p><p><b>Note that Moltworker is a proof of concept, not a Cloudflare product</b>. Our goal is to showcase some of the most exciting features of our <a href="https://developers.cloudflare.com/learning-paths/workers/devplat/intro-to-devplat/">Developer Platform</a> that can be used to run AI agents and unsupervised code efficiently and securely, and get great observability while taking advantage of our global network.</p><p>Feel free to contribute to or fork our <a href="https://github.com/cloudflare/moltworker"><u>GitHub</u></a> repository; we will keep an eye on it for a while for support. We are also considering contributing upstream to the official project with Cloudflare skills in parallel.</p>
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>We hope you enjoyed this experiment, and we were able to convince you that Cloudflare is the perfect place to run your AI applications and agents. We’ve been working relentlessly trying to anticipate the future and release features like the <a href="https://developers.cloudflare.com/agents/"><u>Agents SDK</u></a> that you can use to build your first agent <a href="https://developers.cloudflare.com/agents/guides/slack-agent/"><u>in minutes</u></a>, <a href="https://developers.cloudflare.com/sandbox/"><u>Sandboxes</u></a> where you can run arbitrary code in an isolated environment without the complications of the lifecycle of a container, and <a href="https://developers.cloudflare.com/ai-search/"><u>AI Search</u></a>, Cloudflare’s managed vector-based search service, to name a few.</p><p>Cloudflare now offers a complete toolkit for AI development: inference, storage APIs, databases, durable execution for stateful workflows, and built-in AI capabilities. Together, these building blocks make it possible to build and run even the most demanding AI applications on our global edge network.</p><p>If you're excited about AI and want to help us build the next generation of products and APIs, we're <a href="https://www.cloudflare.com/en-gb/careers/jobs/?department=Engineering"><u>hiring</u></a>.</p> ]]></content:encoded>
            <category><![CDATA[AI]]></category>
            <category><![CDATA[Agents]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Containers]]></category>
            <category><![CDATA[Sandbox]]></category>
            <guid isPermaLink="false">45LuZGCXAcs7EMnB64zTQm</guid>
            <dc:creator>Celso Martinho</dc:creator>
            <dc:creator>Brian Brunner</dc:creator>
            <dc:creator>Sid Chatterjee</dc:creator>
            <dc:creator>Andreas Jansson</dc:creator>
        </item>
        <item>
            <title><![CDATA[Cloudflare Workflows is now GA: production-ready durable execution]]></title>
            <link>https://blog.cloudflare.com/workflows-ga-production-ready-durable-execution/</link>
            <pubDate>Mon, 07 Apr 2025 14:00:00 GMT</pubDate>
            <description><![CDATA[ Workflows — a durable execution engine built directly on top of Workers — is now Generally Available. We’ve landed new human-in-the-loop capabilities, more scale, and more metrics. ]]></description>
            <content:encoded><![CDATA[ <p>Betas are useful for feedback and iteration, but at the end of the day, not everyone is willing to be a guinea pig or can tolerate the occasional sharp edge that comes along with beta software. Sometimes you need that big, shiny “Generally Available” label (or blog post), and now it’s Workflows’ turn.</p><p><a href="https://developers.cloudflare.com/workflows/"><u>Workflows</u></a>, our serverless durable execution engine that allows you to build long-running, multi-step applications (some call them “step functions”) on Workers, is now GA.</p><p>In short, that means it’s <i>production ready</i> —  but it also doesn’t mean Workflows is going to ossify. We’re continuing to scale Workflows (including more concurrent instances), bring new capabilities (like the new <code>waitForEvent</code> API), and make it easier to build <a href="https://www.cloudflare.com/learning/ai/what-is-agentic-ai/">AI agents</a> with <a href="https://developers.cloudflare.com/agents/api-reference/run-workflows/"><u>our Agents SDK and Workflows</u></a>.</p><p>If you prefer code to prose, you can quickly install the Workflows starter project and start exploring the code and the API with a single command:</p>
            <pre><code>npm create cloudflare@latest workflows-starter -- 
--template="cloudflare/workflows-starter"</code></pre>
            <p>How does Workflows work? What can I build with it? How do I think about building AI agents with Workflows and the <a href="https://developers.cloudflare.com/agents/"><u>Agents SDK</u></a>? Well, read on.</p>
    <div>
      <h2>Building with Workflows</h2>
      <a href="#building-with-workflows">
        
      </a>
    </div>
    <p>Workflows is a durable execution engine built on Cloudflare Workers that allows you to build resilient, multi-step applications.</p><p>At its core, Workflows implements a step-based architecture where each step in your application is independently retriable, with state automatically persisted between steps. This means that even if a step fails due to a transient error or network issue, Workflows can retry just that step without needing to restart your entire application from the beginning.</p><p>When you define a Workflow, you break your application into logical steps.</p><ul><li><p>Each step can either execute code (<code>step.do</code>), put your Workflow to sleep (<code>step.sleep</code> or <code>step.sleepUntil</code>), or wait on an event (<code>step.waitForEvent</code>).</p></li><li><p>As your Workflow executes, it automatically persists the state returned from each step, ensuring that your application can continue exactly where it left off, even after failures or hibernation periods. </p></li><li><p>This durable execution model is particularly powerful for applications that coordinate between multiple systems, process data in sequence, or need to handle long-running tasks that might span minutes, hours, or even days.</p></li></ul><p>Workflows are particularly useful at handling complex business processes that traditional stateless functions struggle with.</p><p>For example, an e-commerce order processing workflow might check inventory, charge a payment method, send an email confirmation, and update a database — all as separate steps. If the payment processing step fails due to a temporary outage, Workflows will automatically retry just that step when the payment service is available again, without duplicating the inventory check or restarting the entire process. </p><p>You can see how this works below: each call to a service can be modelled as a step, independently retried, and if needed, recovered from that step onwards:</p>
            <pre><code>import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';

// The params we expect when triggering this Workflow
type OrderParams = {
	orderId: string;
	customerId: string;
	items: Array&lt;{ productId: string; quantity: number }&gt;;
	paymentMethod: {
		type: string;
		id: string;
	};
};

// Our Workflow definition
export class OrderProcessingWorkflow extends WorkflowEntrypoint&lt;Env, OrderParams&gt; {
	async run(event: WorkflowEvent&lt;OrderParams&gt;, step: WorkflowStep) {
		// Step 1: Check inventory
		const inventoryResult = await step.do('check-inventory', async () =&gt; {
			console.log(`Checking inventory for order ${event.payload.orderId}`);

			// Mock: In a real workflow, you'd query your inventory system
			const inventoryCheck = await this.env.INVENTORY_SERVICE.checkAvailability(event.payload.items);

			// Return inventory status as state for the next step
			return {
				inStock: true,
				reservationId: 'inv-123456',
				itemsChecked: event.payload.items.length,
			};
		});

		// Exit workflow if items aren't in stock
		if (!inventoryResult.inStock) {
			return { status: 'failed', reason: 'out-of-stock' };
		}

		// Step 2: Process payment
		// Configure specific retry logic for payment processing
		const paymentResult = await step.do(
			'process-payment',
			{
				retries: {
					limit: 3,
					delay: '30 seconds',
					backoff: 'exponential',
				},
				timeout: '2 minutes',
			},
			async () =&gt; {
				console.log(`Processing payment for order ${event.payload.orderId}`);

				// Mock: In a real workflow, you'd call your payment processor
				const paymentResponse = await this.env.PAYMENT_SERVICE.processPayment({
					customerId: event.payload.customerId,
					orderId: event.payload.orderId,
					amount: calculateTotal(event.payload.items),
					paymentMethodId: event.payload.paymentMethod.id,
				});

				// If payment failed, throw an error that will trigger retry logic
				if (paymentResponse.status !== 'success') {
					throw new Error(`Payment failed: ${paymentResponse.message}`);
				}

				// Return payment info as state for the next step
				return {
					transactionId: 'txn-789012',
					amount: 129.99,
					timestamp: new Date().toISOString(),
				};
			},
		);

		// Step 3: Send email confirmation
		await step.do('send-confirmation-email', async () =&gt; {
			console.log(`Sending confirmation email for order ${event.payload.orderId}`);
			console.log(`Including payment confirmation ${paymentResult.transactionId}`);
			return await this.env.EMAIL_SERVICE.sendOrderConfirmation({ ... })
		});

		// Step 4: Update database
		const dbResult = await step.do('update-database', async () =&gt; {
			console.log(`Updating database for order ${event.payload.orderId}`);
			await this.updateOrderStatus(...)

			return { dbUpdated: true };
		});

		// Return final workflow state
		return {
			orderId: event.payload.orderId,
			processedAt: new Date().toISOString(),
		};
	}
}</code></pre>
            <p>
This combination of durability, automatic retries, and state persistence makes Workflows ideal for building reliable distributed applications that can handle real-world failures gracefully.</p>
    <div>
      <h2>Human-in-the-loop</h2>
      <a href="#human-in-the-loop">
        
      </a>
    </div>
    <p>Workflows are just code, and that makes them extremely powerful: you can define steps dynamically and on-the-fly, conditionally branch, and make API calls to any system you need. But sometimes you also need a Workflow to wait for something to happen in the real world.</p><p>For example:</p><ul><li><p>Approval from a human to progress.</p></li><li><p>An incoming webhook, like from a Stripe payment or a GitHub event. </p></li><li><p>A state change, such as a file upload to R2 that triggers an <a href="https://developers.cloudflare.com/r2/buckets/event-notifications/"><u>Event Notification</u></a>, and then pushes a reference to the file to the Workflow, so it can process the file (or run it through an AI model).</p></li></ul><p>The new <code>waitForEvent</code> API in Workflows allows you to do just that: </p>
            <pre><code>let event = await step.waitForEvent&lt;IncomingStripeWebhook&gt;("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" }) </code></pre>
            <p>You can then send an event to a specific instance from any external service that can make a HTTP request:</p>
            <pre><code>curl -d '{"transaction":"complete","id":"1234-6789"}' \
  -H "Authorization: Bearer ${CF_TOKEN}" \
\ "https://api.cloudflare.com/client/v4/accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/events/{event_type}"</code></pre>
            <p>… or via the <a href="https://developers.cloudflare.com/workflows/build/workers-api/#workflowinstance"><u>Workers API</u></a> within a Worker itself:</p>
            <pre><code>interface Env {
  MY_WORKFLOW: Workflow;
}

interface Payload {
  transaction: string;
  id: string;
}

export default {
  async fetch(req: Request, env: Env) {
    const instanceId = new URL(req.url).searchParams.get("instanceId")
    const webhookPayload = await req.json&lt;Payload&gt;()

    let instance = await env.MY_WORKFLOW.get(instanceId);
    // Send our event, with `type` matching the event type defined in
    // our step.waitForEvent call
    await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload})
    
    return Response.json({
      status: await instance.status(),
    });
  },
};</code></pre>
            <p>You can even wait for multiple events, using the <code>type</code> parameter, and/or race multiple events using <code>Promise.race</code> to continue on depending on which event was received first:</p>
            <pre><code>export class MyWorkflow extends WorkflowEntrypoint&lt;Env, Params&gt; {
	async run(event: WorkflowEvent&lt;Params&gt;, step: WorkflowStep) {
		let state = await step.do("get some data", () =&gt; { /* step call here */ })
		// Race the events, resolving the Promise based on which event
// we receive first
		let value = Promise.race([
step.waitForEvent("payment success", { type: "payment-success-webhook", timeout: "4 hours" ),
step.waitForEvent("payment failure", { type: "payment-failure-webhook", timeout: "4 hours" ),
])
// Continue on based on the value and event received
	}
}</code></pre>
            <p>To visualize <code>waitForEvent</code> in a bit more detail, let’s assume we have a Workflow that is triggered by a code review agent that watches a GitHub repository.</p><p>Without the ability to wait on events, our Workflow can’t easily get human approval to write suggestions back (or even submit a PR of its own). It <i>could</i> potentially poll for some state that was updated, but that means we have to call <code>step.sleep</code> for arbitrary periods of time, poll a storage service for an updated value, and repeat if it’s not there. That’s a lot of code and room for error:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/64dgTwe9V6bAfKUDQgJ1z3/e0a897623a8ca452139f00dd2cff9733/1.png" />
          </figure><p><sup><i>Without waitForEvent, it’s harder to send data to a Workflow instance that’s running</i></sup></p><p>If we modified that same example to incorporate the new waitForEvent API, we could use it to wait for human approval before making a mutating change:  </p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2BIuiSytb7roytyDhHVioz/0e005829fea9e60d772dcb6888acac2c/2.png" />
          </figure><p><sup><i>Adding waitForEvent to our code review Workflow, so it can seek explicit approval.</i></sup></p><p>You could even imagine an AI agent itself sending and/or acting on behalf of a human here: <code>waitForEvent</code> simply exposes a way for a Workflow to retrieve and pause on something in the world to change before it continues (or not).</p><p>Critically, you can call <code>waitForEvent</code> just like any other step in Workflows: you can call it conditionally, and/or multiple times, and/or in a loop. Workflows are just Workers: you have the full power of a programming language and are not restricted by a <a href="https://en.wikipedia.org/wiki/Domain-specific_language"><u>domain specific language (DSL)</u></a> or config language.</p>
    <div>
      <h2>Pricing</h2>
      <a href="#pricing">
        
      </a>
    </div>
    <p>Good news: we haven’t changed much since our original beta announcement! We’re adding storage pricing for state stored by your Workflows, and retaining our CPU-based and request (invocation) based pricing as follows:</p><table><tr><td><p><b>Unit</b></p></td><td><p><b>Workers Free</b></p></td><td><p><b>Workers Paid</b></p></td></tr><tr><td><p><b>CPU time (ms)</b></p></td><td><p>10 ms per Workflow</p></td><td><p>30 million CPU milliseconds included per month</p><p>+$0.02 per additional million CPU milliseconds</p></td></tr><tr><td><p><b>Requests</b></p></td><td><p>100,000 Workflow invocations per day (<a href="https://developers.cloudflare.com/workers/platform/pricing/#workers"><u>shared with Workers</u></a>)</p></td><td><p>10 million included per month</p><p>+$0.30 per additional million</p></td></tr><tr><td><p><b>Storage (GB)</b></p></td><td><p>1 GB</p></td><td><p>1 GB included per month
+ $0.20/ GB-month</p></td></tr></table><p>Because the storage pricing is new, we will not actively bill for storage until September 15, 2025. We will notify users above the included 1 GB limit ahead of charging for storage, and by default, Workflows will expire stored state after three (3) days (Free plan) or thirty (30) days (Paid plan).</p><p>If you’re wondering what “CPU time” is here: it’s the time your Workflow is actively consuming compute resources. It <i>doesn’t</i> include time spent waiting on API calls, reasoning LLMs, or other I/O (like writing to a database). That might seem like a small thing, but in practice, it adds up: most applications have single digit milliseconds of CPU time, and multiple seconds of wall time: an API or two taking 100 - 250 ms to respond adds up!</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6zRZ3gFQ0TrCetwlW0bqWG/87e41b7ab75ae48a4f2a6655d8ac2a86/3.png" />
          </figure><p><sup><i>Bill for CPU, not for time spent when a Workflow is idle or waiting.</i></sup></p><p>Workflow engines, especially, tend to spend a lot of time waiting: reading data from <a href="https://www.cloudflare.com/learning/cloud/what-is-object-storage/">object storage</a> (like <a href="https://www.cloudflare.com/developer-platform/products/r2/"><u>Cloudflare R2</u></a>), calling third-party APIs or LLMs like o3-mini or Claude 3.7, even querying databases like <a href="https://developers.cloudflare.com/d1/"><u>D1</u></a>, Postgres, or MySQL. With Workflows, just like Workers: you don’t pay for time your application is just waiting.</p>
    <div>
      <h2>Start building</h2>
      <a href="#start-building">
        
      </a>
    </div>
    <p>So you’ve got a good handle on Workflows, how it works, and want to get building. What next?</p><ol><li><p><a href="https://developers.cloudflare.com/workflows/"><u>Visit the Workflows documentation</u></a> to learn how it works, understand the Workflows API, and best practices</p></li><li><p>Review the code in the <a href="https://github.com/cloudflare/workflows-starter"><u>starter project</u></a></p></li><li><p>And lastly, deploy the starter to your own Cloudflare account with a few clicks:</p></li></ol><a href="https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/workflows-starter"><img src="https://deploy.workers.cloudflare.com/button" /></a><p></p> ]]></content:encoded>
            <category><![CDATA[Developer Week]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Workflows]]></category>
            <category><![CDATA[Developer Platform]]></category>
            <guid isPermaLink="false">7ju3oFGzR3iR8gO2TmMleF</guid>
            <dc:creator>Sid Chatterjee</dc:creator>
            <dc:creator>Matt Silverlock</dc:creator>
        </item>
        <item>
            <title><![CDATA[Build durable applications on Cloudflare Workers: you write the Workflows, we take care of the rest]]></title>
            <link>https://blog.cloudflare.com/building-workflows-durable-execution-on-workers/</link>
            <pubDate>Thu, 24 Oct 2024 13:05:00 GMT</pubDate>
            <description><![CDATA[ Cloudflare Workflows is now in open beta! Workflows allows you to build reliable, repeatable, long-lived multi-step applications that can automatically retry, persist state, and scale out. Read on to learn how Workflows works, how we built it on top of Durable Objects, and how you can deploy your first Workflows application. ]]></description>
            <content:encoded><![CDATA[ <p>Workflows, Cloudflare’s durable execution engine that allows you to build reliable, repeatable multi-step applications that scale for you, is now in open beta. Any developer with a free or paid <a href="https://workers.cloudflare.com/"><u>Workers</u></a> plan can build and deploy a Workflow right now: no waitlist, no sign-up form, no fake line around-the-block.</p><p>If you learn by doing, you can create your first Workflow via a single command (or <a href="https://developers.cloudflare.com/workflows/get-started/guide/"><u>visit the docs for the full guide)</u></a>:</p>
            <pre><code>npm create cloudflare@latest workflows-starter -- \
  --template "cloudflare/workflows-starter"</code></pre>
            <p>Open the <code>src/index.ts</code> file, poke around, start extending it, and deploy it with a quick <code>wrangler deploy</code>.</p><p>If you want to learn more about how Workflows works, how you can use it to build applications, and how we built it, read on.</p>
    <div>
      <h2>Workflows? Durable Execution?</h2>
      <a href="#workflows-durable-execution">
        
      </a>
    </div>
    <p>Workflows—which we <a href="https://blog.cloudflare.com/data-anywhere-events-pipelines-durable-execution-workflows/#durable-execution"><u>announced back during Developer Week</u></a> earlier this year—is our take on the concept of “Durable Execution”: the ability to build and execute applications that are <i>durable</i> in the face of errors, network issues, upstream API outages, rate limits, and (most importantly) infrastructure failure.</p><p>As <a href="https://cloudflare.tv/event/xvm4qdgm?startTime=8m5s"><u>over 2.4 million developers</u></a> continue to build applications on top of Cloudflare Workers, R2, and Workers AI, we’ve noticed more developers building multi-step applications and workflows that process user data, transform unstructured data into structured, export metrics, persist state as they progress, and automatically retry &amp; restart. But writing any non-trivial application and making it <i>durable</i> in the face of failure is hard: this is where Workflows comes in. Workflows manages the retries, emitting the metrics, and durably storing the state (without you having to stand up your own database) as the Workflow progresses.</p><p>What makes Workflows different from other takes on “Durable Execution” is that we manage the underlying compute and storage infrastructure for you. You’re not left managing a compute cluster and hoping it scales both up (on a Monday morning) and down (during quieter periods) to manage costs, or ensuring that you have compute running in the right locations. Workflows is built on Cloudflare Workers — our job is to run your code and operate the infrastructure for you.</p><p>As an example of how Workflows can help you build durable applications, assume you want to post-process file uploads from your users that were uploaded to an R2 bucket directly via <a href="https://developers.cloudflare.com/r2/api/s3/presigned-urls/"><u>a pre-signed URL</u></a>. That post-processing could involve multiple actions: text extraction via a <a href="https://developers.cloudflare.com/workers-ai/models/"><u>Workers AI model</u></a>, calls to a third-party API to validate data, updating or querying rows in a database once the file has been processed… the list goes on.</p><p>But what each of these actions has in common is that it could <i>fail</i>. Maybe that upstream API is unavailable, maybe you get rate-limited, maybe your database is down. Having to write extensive retry logic around each action, manage backoffs, and (importantly) ensure your application doesn’t have to start from scratch when a later <i>step</i> fails is more boilerplate to write and more code to test and debug.</p><p>What’s a <i>step, </i>you ask? The core building block of every Workflow is the step: an individually retriable component of your application that can optionally emit state. That state is then persisted, even if subsequent steps were to fail. This means that your application doesn’t have to restart, allowing it to not only recover more quickly from failure scenarios, but it can also avoid doing redundant work. You don’t want your application hammering an expensive third-party API (or getting you rate limited) because it’s naively retrying an API call that you don’t have to.</p>
            <pre><code>export class MyWorkflow extends WorkflowEntrypoint&lt;Env, Params&gt; {
	async run(event: WorkflowEvent&lt;Params&gt;, step: WorkflowStep) {
		const files = await step.do('my first step', async () =&gt; {
			return {
				inputParams: event,
				files: [
					'doc_7392_rev3.pdf',
					'report_x29_final.pdf',
					'memo_2024_05_12.pdf',
					'file_089_update.pdf',
					'proj_alpha_v2.pdf',
					'data_analysis_q2.pdf',
					'notes_meeting_52.pdf',
					'summary_fy24_draft.pdf',
				],
			};
		});

		// Other steps...
	}
}
</code></pre>
            <p>Notably, a Workflow can have hundreds of steps: one of the <a href="https://developers.cloudflare.com/workflows/build/rules-of-workflows/"><u>Rules of Workflows</u></a> is to encapsulate every API call or stateful action within your application into its own step. Each step can also define its own retry strategy, automatically backing off, adding a delay and/or (eventually) giving up after a set number of attempts.</p>
            <pre><code>await step.do(
	'make a call to write that could maybe, just might, fail',
	// Define a retry strategy
	{
		retries: {
			limit: 5,
			delay: '5 seconds',
			backoff: 'exponential',
		},
		timeout: '15 minutes',
	},
	async () =&gt; {
		// Do stuff here, with access to the state from our previous steps
		if (Math.random() &gt; 0.5) {
			throw new Error('API call to $STORAGE_SYSTEM failed');
		}
	},
);
</code></pre>
            <p>To illustrate this further, imagine you have an application that reads text files from an R2 storage bucket, pre-processes the text into chunks, generates text embeddings <a href="https://developers.cloudflare.com/workers-ai/models/bge-large-en-v1.5/"><u>using Workers AI</u></a>, and then inserts those into a vector database (like <a href="https://developers.cloudflare.com/vectorize/"><u>Vectorize</u></a>) for semantic search.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7b9m0rPDlGvIiTnhguyvzI/3f27678b141ce600f1f54eb999e9d671/WORKFLOWS.png" />
          </figure><p>In the Workflows programming model, each of those is a discrete step, and each can emit state. For example, each of the four actions below can be a discrete <code>step.do</code> call in a Workflow:</p><ol><li><p>Reading the files from storage and emitting the list of filenames</p></li><li><p>Chunking the text and emitting the results</p></li><li><p>Generating text embeddings</p></li><li><p>Upserting them into Vectorize and capturing the result of a test query</p></li></ol><p>You can also start to imagine that some steps, such as chunking text or generating text embeddings, can be broken down into even more steps — a step per file that we chunk, or a step per API call to our text embedding model, so that our application is even more resilient to failure.</p><p>Steps can be created programmatically or conditionally based on input, allowing you to dynamically create steps based on the number of inputs your application needs to process. You do not need to define all steps ahead of time, and each instance of a Workflow may choose to conditionally create steps on the fly.</p>
    <div>
      <h2>Building Cloudflare on Cloudflare</h2>
      <a href="#building-cloudflare-on-cloudflare">
        
      </a>
    </div>
    <p>As the Cloudflare Developer platform <a href="https://www.cloudflare.com/birthday-week/"><u>continues to grow</u></a>, almost all of our own products are built on top of it. Workflows is yet another example of how we built a new product from scratch using nothing but Workers and its vast catalog of features and APIs. This section of the blog has two goals: to explain how we built it, and to demonstrate that anyone can create a complex application or platform with demanding requirements and multiple architectural layers on our stack, too.</p><p>If you’re wondering how Workflows manages to make durable execution easy, how it persists state, and how it automatically scales: it’s because we built it on Cloudflare Workers, including the brand-new <a href="https://blog.cloudflare.com/sqlite-in-durable-objects/"><u>zero-latency SQLite storage we recently introduced to Durable Objects</u></a>.
</p><p>To understand how Workflows uses Workers &amp; Durable Objects, here’s the high-level overview of our architecture:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7pknYk0Sshxka3iPbxBCRj/bb8b75986601e38b6b69fe8d849c0cbe/image9.png" />
          </figure><p>There are three main blocks in this diagram:</p><p>The user-facing APIs are where the user interacts with the platform, creating and deploying new workflows or instances, controlling them, and accessing their state and activity logs. These operations can be executed through our public <a href="https://developers.cloudflare.com/api/"><u>API gateway</u></a> using REST calls, a Worker script using bindings, <a href="https://blog.cloudflare.com/wrangler3"><u>Wrangler</u></a> (Cloudflare's developer platform command line tool), or via the <a href="https://dash.cloudflare.com/"><u>Dashboard</u></a> user interface.</p><p>The managed platform holds the internal configuration APIs running on a Worker implementing a catalog of REST endpoints, the binding shim, which is supported by another dedicated Worker, every account controller, and their correspondent workflow engines, all powered by SQLite-backed Durable Objects. This is where all the magic happens and what we are sharing more details about in this technical blog.</p><p>Finally, there are the workflow instances, essentially independent clones of the workflow application. Instances are user account-owned and have a one-to-one relationship with a managed engine that powers them. You can run as many instances and engines as you want concurrently.</p><p>Let's get into more detail…</p>
    <div>
      <h3>Configuration API and Binding Shim</h3>
      <a href="#configuration-api-and-binding-shim">
        
      </a>
    </div>
    
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2qEGr9M8KwgPS66Ju8mELL/189db9764392c00ae34dd3a44eeb1ed7/image6.png" />
          </figure><p>The Configuration API and the Binding Shim are two stateless Workers; one receives REST API calls from clients calling our <a href="https://developers.cloudflare.com/api/"><u>API Gateway</u></a> directly, using <a href="https://developers.cloudflare.com/workers/wrangler/"><u>Wrangler</u></a>, or navigating the <a href="https://dash.cloudflare.com/"><u>Dashboard</u></a> UI, and the other is the endpoint for the Workflows <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/"><u>binding</u></a>, an efficient and authenticated interface to interact with the Cloudflare Developer Platform resources from a Workers script.</p><p>The configuration API worker uses <a href="https://hono.dev/docs/getting-started/cloudflare-workers"><u>HonoJS</u></a> and <a href="https://hono.dev/examples/zod-openapi"><u>Zod</u></a> to implement the REST endpoints, which are declared in an <a href="https://swagger.io/specification/"><u>OpenAPI</u></a> schema and exported to our API Gateway, thus adding our methods to the Cloudflare API <a href="https://developers.cloudflare.com/api/"><u>catalog</u></a>.</p>
            <pre><code>import { swaggerUI } from '@hono/swagger-ui';
import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
import { Hono } from 'hono';

...

​​api.openapi(
  createRoute({
    method: 'get',
    path: '/',
    request: {
      query: PaginationParams,
    },
    responses: {
      200: {
        content: {
          'application/json': {
             schema: APISchemaSuccess(z.array(WorkflowWithInstancesCountSchema)),
          },
        },
        description: 'List of all Workflows belonging to a account.',
      },
    },
  }),
  async (ctx) =&gt; {
    ...
  },
);

...

api.route('/:workflow_name', routes.workflows);
api.route('/:workflow_name/instances', routes.instances);
api.route('/:workflow_name/versions', routes.versions);</code></pre>
            <p>These Workers perform two different functions, but they share a large portion of their code and implement similar logic; once the request is authenticated and ready to travel to the next stage, they use the account ID to delegate the operation to a Durable Object called Account Controller.</p>
            <pre><code>// env.ACCOUNTS is the Account Controllers Durable Objects namespace
const accountStubId = c.env.ACCOUNTS.idFromName(accountId.toString());
const accountStub = c.env.ACCOUNTS.get(accountStubId);</code></pre>
            <p>As you can see, every account has its own Account Controller Durable Object.</p>
    <div>
      <h3>Account Controllers</h3>
      <a href="#account-controllers">
        
      </a>
    </div>
    <p>The Account Controller is a dedicated persisted database that stores the list of all the account’s workflows, versions, and instances. We scale to millions of account controllers, one per every Cloudflare account using Workflows, by leveraging the power of <a href="https://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#sqlite-storage-backend"><u>Durable Objects with SQLite backend</u></a>.</p><p><a href="https://developers.cloudflare.com/durable-objects/"><u>Durable Objects</u></a> (DOs) are single-threaded singletons that run in our data centers and are bound to a stateful storage API, in this case, SQLite. They are also Workers, just a special kind, and have access to all of our other APIs. This makes it easy to build consistent, highly available distributed applications with them.</p><p>Here’s what we get for free by using one Durable Object per Workflows account:</p><ul><li><p>Sharding based on account boundaries aligns perfectly with the way we manage resources at Cloudflare internally. Also, due to the nature of DOs, there are other things that this model gets us for free: Not that we expect them, but eventual bugs or state inconsistencies during beta are confined to the affected account, and don’t impact everyone.</p></li><li><p>DO instances run close to the end user; Alice is in London and will call the config API through our <a href="https://www.cloudflare.com/en-gb/network/"><u>LHR data center</u></a>, while Bob is in Lisbon and will connect to LIS.</p></li><li><p>Because every account is a Worker, we can gradually upgrade them to new versions, starting with the internal users, thus derisking real customers.</p></li></ul><p>Before SQLite, our only option was to use the Durable Object's <a href="https://developers.cloudflare.com/durable-objects/api/storage-api/#get"><u>key-value</u></a> storage API, but having a relational database at our fingertips and being able to create tables and do complex queries is a significant enabler. For example, take a look at how we implement the internal method getWorkflow():</p>
            <pre><code>async function getWorkflow(accountId: number, workflowName: string) {
  try {
    const res = this.ctx.storage.transactionSync(() =&gt; {
      const cursor = Array.from(
        this.ctx.storage.sql.exec(
          `
                    SELECT *,
                    (SELECT class_name
                        FROM   versions
                        WHERE  workflow_id = w.id
                        ORDER  BY created_on DESC
                        LIMIT  1) AS class_name
                    FROM   workflows w
                    WHERE  w.name = ? 
                    `,
          workflowName
        )
      )[0] as Workflow;

      return cursor;
    });

    this.sendAnalytics(accountId, begin, "getWorkflow");
    return res as Workflow | undefined;
  } catch (err) {
    this.sendErrorAnalytics(accountId, begin, "getWorkflow");
    throw err;
  }
}
</code></pre>
            <p>The other thing we take advantage of in Workflows is using the recently <a href="https://blog.cloudflare.com/javascript-native-rpc/"><u>announced</u></a> JavaScript-native RPC feature when communicating between components.</p><p>Before <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/"><u>RPC</u></a>, we had to <code>fetch()</code> between components, make HTTP requests, and serialize and deserialize the parameters and the payload. Now, we can async call the remote object's method as if it was local. Not only does this feel more natural and simplify our logic, but it's also more efficient, and we can take advantage of TypeScript type-checking when writing code.</p><p>This is how the Configuration API would call the Account Controller’s <code>countWorkflows()</code> method before:</p>
            <pre><code>const resp = await accountStub.fetch(
      "https://controller/count-workflows",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json; charset=utf-8",
        },
        body: JSON.stringify({ accountId }),
      },
    );

if (!resp.ok) {
  return new Response("Internal Server Error", { status: 500 });
}

const result = await resp.json();
const total_count = result.total_count;</code></pre>
            <p>This is how we do it using RPC:</p>
            <pre><code>const total_count = await accountStub.countWorkflows(accountId);</code></pre>
            <p>The other powerful feature of our RPC system is that it supports passing not only <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types"><u>Structured Cloneable</u></a> objects back and forth but also entire classes. More on this later.</p><p>Let’s move on to Engine.</p>
    <div>
      <h3>Engine and instance</h3>
      <a href="#engine-and-instance">
        
      </a>
    </div>
    <p>Every instance of a workflow runs alongside an Engine instance. The Engine is responsible for starting up the user’s workflow entry point, executing the steps on behalf of the user, handling their results, and tracking the workflow state until completion.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6yrKsuF501oRCDujckr3yM/bde40097ec5bedda07793375e53e99b9/image1.png" />
          </figure><p>When we started thinking about the Engine, we thought about modeling it after a <a href="https://en.wikipedia.org/wiki/Finite-state_machine"><u>state machine</u></a>, and that was what our initial prototypes looked like. However, state machines require an ahead-of-time understanding of the userland code, which implies having a build step before running them. This is costly at scale and introduces additional complexity.</p><p>A few iterations later, we had another idea. What if we could model the engine as a game loop?</p><p>Unlike other computer programs, games operate regardless of a user's input. The game loop is essentially a sequence of tasks that implement the game's logic and update the display, typically one loop per video frame. Here’s an example of a game loop in pseudo-code:</p>
            <pre><code>while (game in running)
    check for user input
    move graphics
    play sounds
end while</code></pre>
            <p>Well, an oversimplified version of our Workflow engine would look like this:</p>
            <pre><code>while (last step not completed)
    iterate every step
       use memoized cache as response if the step has run already
       continue running step or timer if it hasn't finished yet
end while</code></pre>
            <p>A workflow is indeed a loop that keeps on going, performing the same sequence of logical tasks until the last step completes.</p><p>The Engine and the instance run hand-in-hand in a one-to-one relationship. The first is managed, and part of the platform. It uses SQLite and other platform APIs internally, and we can constantly add new features, fix bugs, and deploy new versions, while keeping everything transparent to the end user. The second is the actual account-owned Worker script that declares the Workflow steps.</p><p>For example, when someone passes a callback into <code>step.do()</code>:</p>
            <pre><code>export class MyWorkflow extends WorkflowEntrypoint&lt;Env, Params&gt; {
  async run(event: WorkflowEvent&lt;Params&gt;, step: WorkflowStep) {
    step.do('step1', () =&gt; { ... });
  }
}</code></pre>
            <p>We switch execution over to the Engine. Again, this is possible because of the power of JS RPC. Besides passing Structured Cloneable objects back and forth, JS RPC allows us to <a href="https://developers.cloudflare.com/workers/runtime-apis/rpc/#send-functions-as-parameters-of-rpc-methods"><u>create and pass entire application-defined classes</u></a> that extend the built-in RpcTarget. So this is what happens behind the scenes when your Instance calls <code>step.do()</code> (simplified):</p>
            <pre><code>export class Context extends RpcTarget {

  async do&lt;T&gt;(name: string, callback: () =&gt; Promise&lt;T&gt;): Promise&lt;T&gt; {

    // First we check we have a cache of this step.do() already
    const maybeResult = await this.#state.storage.get(name);

    // We return the cache if it exists
    if (maybeValue) { return maybeValue; }

    // Else we run the user callback
    return doWrapper(callback);
  }

}
</code></pre>
            <p>Here’s a more complete diagram of the Engine’s <code>step.do()</code> lifecycle:</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4MymVGS7BxwityCRlWcBOX/136d4dcf0affce04164f87b6bbe8b12a/image5.png" />
          </figure><p>Again, this diagram only partially represents everything we do in the Engine; things like logging for observability or handling exceptions are missing, and we don't get into the details of how queuing is implemented. However, it gives you a good idea of how the Engine abstracts and handles all the complexities of completing a step under the hood, allowing us to expose a simple-to-use API to end users.</p><p>Also, it's worth reiterating that every workflow instance is an Engine behind the scenes, and every Engine is an SQLite-backed Durable Object. This ensures that every instance runtime and state are isolated and independent of each other and that we can effortlessly scale to run billions of workflow instances, a solved problem for Durable Objects.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4uEoEAtsjNquPCD3F50S9d/006556baf2a0478d1de10e4514843baa/image3.png" />
          </figure>
    <div>
      <h3>Durability</h3>
      <a href="#durability">
        
      </a>
    </div>
    <p>Durable Execution is all the rage now when we talk about workflow engines, and ours is no exception. Workflows are typically long-lived processes that run multiple functions in sequence where anything can happen. Those functions can time out or fail because of a remote server error or a network issue and need to be retried. A workflow engine ensures that your application runs smoothly and completes regardless of the problems it encounters.</p><p>Durability means that if and when a workflow fails, the Engine can re-run it, resume from the last recorded step, and deterministically re-calculate the state from all the successful steps' cached responses. This is possible because steps are stateful and idempotent; they produce the same result no matter how many times we run them, thus not causing unintended duplicate effects like sending the same invoice to a customer multiple times.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1R5UfQfNMKI7hB6QXJfCUr/242e85f2b5287394871e916844359bd4/image7.png" />
          </figure><p>We ensure durability and handle failures and retries by sharing the same technique we use for a <code>step.sleep()</code> that requires sleeping for days or months: a combination of using <code>scheduler.wait()</code>, a method of the <a href="https://github.com/WICG/scheduling-apis"><u>upcoming WICG Scheduling API</u></a> that we already <a href="https://developers.cloudflare.com/workers/platform/changelog/historical-changelog/#2021-12-10"><u>support</u></a>, and <a href="https://developers.cloudflare.com/durable-objects/api/alarms/"><u>Durable Objects alarms</u></a>, which allow you to schedule the Durable Object to be woken up at a time in the future.</p><p>These two APIs allow us to overcome the lack of guarantees that a Durable Object runs forever, giving us complete control of its lifecycle. Since every state transition through userland code persists in the Engine’s strongly consistent SQLite, we track timestamps when a step begins execution, its attempts (if it needs retries), and its completion.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6FSCXRt9fO4EaaBP7hLV8x/a59de27dfbe18f39addd4eb8240b9df9/image10.png" />
          </figure><p>This means that steps pending if a Durable Object is <a href="https://developers.cloudflare.com/durable-objects/reference/in-memory-state/"><u>evicted</u></a> — perhaps due to a two-month-long timer — get rerun on the next lifetime of the Engine (with its cache from the previous lifetime hydrated) that is triggered by an alarm set with the timestamp of the next expected state transition. </p>
    <div>
      <h2>Real-life workflow, step by step</h2>
      <a href="#real-life-workflow-step-by-step">
        
      </a>
    </div>
    <p>Let's walk through an example of a real-life application. You run an e-commerce website and would like to send email reminders to your customers for forgotten carts that haven't been checked out in a few days.</p><p>What would typically have to be a combination of a queue, a cron job, and querying a database table periodically can now simply be a Workflow that we start on every new cart:</p>
            <pre><code>import {
  WorkflowEntrypoint,
  WorkflowEvent,
  WorkflowStep,
} from "cloudflare:workers";
import { sendEmail } from "./legacy-email-provider";

type Params = {
  cartId: string;
};

type Env = {
  DB: D1Database;
};

export class Purchase extends WorkflowEntrypoint&lt;Env, Params&gt; {
  async run(
    event: WorkflowEvent&lt;Params&gt;,
    step: WorkflowStep
  ): Promise&lt;unknown&gt; {
    await step.sleep("wait for three days", "3 days");

    // Retrieve cart from D1
    const cart = await step.do("retrieve cart from database", async () =&gt; {
      const { results } = await this.env.DB.prepare(`SELECT * FROM cart WHERE id = ?`)
        .bind(event.payload.cartId)
        .all();
      return results[0];
    });

    if (!cart.checkedOut) {
      await step.do("send an email", async () =&gt; {
        await sendEmail("reminder", cart);
      });
    }
  }
}
</code></pre>
            <p>This works great. However, sometimes the <code>sendEmail</code> function fails due to an upstream provider erroring out. While <code>step.do</code> automatically retries with a reasonable default configuration, we can define our settings:</p>
            <pre><code>if (cart.isComplete) {
  await step.do(
    "send an email",
    {
      retries: {
        limit: 5,
        delay: "1 min",
        backoff: "exponential",
      },
    },
    async () =&gt; {
      await sendEmail("reminder", cart);
    }
  );
}
</code></pre>
            
    <div>
      <h3>Managing Workflows</h3>
      <a href="#managing-workflows">
        
      </a>
    </div>
    <p>Workflows allows us to create and manage workflows using four different interfaces:</p><ul><li><p>Using our REST HTTP API available on <a href="https://developers.cloudflare.com/api/"><u>Cloudflare’s API catalog</u></a></p></li><li><p>Using <a href="https://developers.cloudflare.com/workers/wrangler/"><u>Wrangler</u></a>, Cloudflare's developer platform command-line tool</p></li><li><p>Programmatically inside a Worker using <a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/"><u>bindings</u></a></p></li><li><p>Using our Web UI in the <a href="https://dash.cloudflare.com/"><u>dashboard</u></a></p></li></ul><p>The HTTP API makes it easy to trigger new instances of workflows from any system, even if it isn’t on Cloudflare, or from the command line. For example:</p>
            <pre><code>curl --request POST \
  --url https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/workflows/purchase-workflow/instances/$CART_INSTANCE_ID \
  --header 'Authorization: Bearer $ACCOUNT_TOKEN \
  --header 'Content-Type: application/json' \
  --data '{
	"id": "$CART_INSTANCE_ID",
	"params": {
		"cartId": "f3bcc11b-2833-41fb-847f-1b19469139d1"
	}
  }'</code></pre>
            <p>Wrangler goes one step further and gives us a friendlier set of commands to interact with workflows with fancy formatted outputs without needing to authenticate with tokens. Type <code>npx wrangler workflows</code> for help, or:</p>
            <pre><code>npx wrangler workflows trigger purchase-workflow '{ "cartId": "f3bcc11b-2833-41fb-847f-1b19469139d1" }'</code></pre>
            <p>Furthermore, Workflows has first-party support in wrangler, and you can test your instances locally. A Workflow is similar to a regular<a href="https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/"><u> WorkerEntrypoint</u></a> in your Worker, which means that <code>wrangler dev</code> just naturally works.</p>
            <pre><code>❯ npx wrangler dev

 ⛅️ wrangler 3.82.0
----------------------------

Your worker has access to the following bindings:
- Workflows:
  - CART_WORKFLOW: EcommerceCartWorkflow
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
╭───────────────────────────────────────────────╮
│  [b] open a browser, [d] open devtools        │
╰───────────────────────────────────────────────╯
</code></pre>
            <p>Workflow APIs are also available as a Worker binding. You can interact with the platform programmatically from another Worker script in the same account without worrying about permissions or authentication. You can even have workflows that call and interact with other workflows.</p>
            <pre><code>import { WorkerEntrypoint } from "cloudflare:workers";

type Env = { DEMO_WORKFLOW: Workflow };
export default class extends WorkerEntrypoint&lt;Env&gt; {
  async fetch() {
    // Pass in a user defined name for this instance
    // In this case, we use the same as the cartId
    const instance = await this.env.DEMO_WORKFLOW.create({
      id: "f3bcc11b-2833-41fb-847f-1b19469139d1",
      params: {
          cartId: "f3bcc11b-2833-41fb-847f-1b19469139d1",
      }
    });
  }
  async scheduled() {
    // Restart errored out instances in a cron
    const instance = await this.env.DEMO_WORKFLOW.get(
      "f3bcc11b-2833-41fb-847f-1b19469139d1"
    );
    const status = await instance.status();
    if (status.error) {
      await instance.restart();
    }
  }
}</code></pre>
            
    <div>
      <h3>Observability </h3>
      <a href="#observability">
        
      </a>
    </div>
    <p>Having good <a href="https://www.cloudflare.com/learning/performance/what-is-observability/">observability</a> and data on often long-lived asynchronous tasks is crucial to understanding how we're doing under normal operation and, more importantly, when things go south, and we need to troubleshoot problems or when we are iterating on code changes.</p><p>We designed Workflows around the philosophy that there is no such thing as too much logging. You can get all the SQLite data for your workflow and its instances by calling the REST APIs. Here is the output of an instance:</p>
            <pre><code>{
  "success": true,
  "errors": [],
  "messages": [],
  "result": {
    "status": "running",
    "params": {},
    "trigger": { "source": "api" },
    "versionId": "ae042999-39ff-4d27-bbcd-22e03c7c4d02",
    "queued": "2024-10-21 17:15:09.350",
    "start": "2024-10-21 17:15:09.350",
    "end": null,
    "success": null,
    "steps": [
      {
        "name": "send email",
        "start": "2024-10-21 17:15:09.411",
        "end": "2024-10-21 17:15:09.678",
        "attempts": [
          {
            "start": "2024-10-21 17:15:09.411",
            "end": "2024-10-21 17:15:09.678",
            "success": true,
            "error": null
          }
        ],
        "config": {
          "retries": { "limit": 5, "delay": 1000, "backoff": "constant" },
          "timeout": "15 minutes"
        },
        "output": "celso@example.com",
        "success": true,
        "type": "step"
      },
      {
        "name": "sleep-1",
        "start": "2024-10-21 17:15:09.763",
        "end": "2024-10-21 17:17:09.763",
        "finished": false,
        "type": "sleep",
        "error": null
      }
    ],
    "error": null,
    "output": null
  }
}</code></pre>
            <p>As you can see, this is essentially a dump of the instance engine SQLite in JSON. You have the <b>errors</b>, <b>messages</b>, current <b>status</b>, and what happened with <b>every step</b>, all time stamped to the millisecond.</p><p>It's one thing to get data about a specific workflow instance, but it's another to zoom out and look at aggregated statistics of all your workflows and instances over time. Workflows data is available through our <a href="https://developers.cloudflare.com/analytics/graphql-api/"><u>GraphQL Analytics API</u></a>, so you can query it in aggregate and generate valuable insights and reports. In this example we ask for aggregated analytics about the wall time of all the instances of the “e-commerce-carts” workflow:</p>
            <pre><code>{
  viewer {
    accounts(filter: { accountTag: "febf0b1a15b0ec222a614a1f9ac0f0123" }) {
      wallTime: workflowsAdaptiveGroups(
        limit: 10000
        filter: {
          datetimeHour_geq: "2024-10-20T12:00:00.000Z"
          datetimeHour_leq: "2024-10-21T12:00:00.000Z"
          workflowName: "e-commerce-carts"
        }
        orderBy: [count_DESC]
      ) {
        count
        sum {
          wallTime
        }
        dimensions {
          date: datetimeHour
        }
      }
    }
  }
}
</code></pre>
            <p>For convenience, you can evidently also use Wrangler to describe a workflow or an instance and get an instant and beautifully formatted response:</p>
            <pre><code>sid ~ npx wrangler workflows instances describe purchase-workflow latest

 ⛅️ wrangler 3.80.4

Workflow Name:         purchase-workflow
Instance Id:           d4280218-7756-41d2-bccd-8d647b82d7ce
Version Id:            0c07dbc4-aaf3-44a9-9fd0-29437ed11ff6
Status:                ✅ Completed
Trigger:               🌎 API
Queued:                14/10/2024, 16:25:17
Success:               ✅ Yes
Start:                 14/10/2024, 16:25:17
End:                   14/10/2024, 16:26:17
Duration:              1 minute
Last Successful Step:  wait for three days
Output:                false
Steps:

  Name:      wait for three days
  Type:      💤 Sleeping
  Start:     14/10/2024, 16:25:17
  End:       17/10/2024, 16:25:17
  Duration:  3 day</code></pre>
            <p>And finally, we worked really hard to get you the best dashboard UI experience when navigating Workflows data.</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/64XUtBwldkSXUTJ5xEJBgo/2aa861583c8c56c19194cb0869a15a2a/image8.png" />
          </figure>
    <div>
      <h2>So, how much does it cost?</h2>
      <a href="#so-how-much-does-it-cost">
        
      </a>
    </div>
    <p>It’d be painful if we introduced a powerful new way to build Workers applications but made it cost prohibitive.</p><p>Workflows is <a href="https://developers.cloudflare.com/workers/platform/pricing/#workers"><u>priced</u></a> just like Cloudflare Workers, where we <a href="https://blog.cloudflare.com/workers-pricing-scale-to-zero/"><u>introduced CPU-based pricing</u></a>: only on active CPU time and requests, not duration (aka: wall time).</p>
          <figure>
          <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/11WroT4xt0zPj6bsou4u3X/8f2775569f280107345322cb97603b3e/image4.png" />
          </figure><p><sup><i>Workers Standard pricing model</i></sup></p><p>This is especially advantageous when building the long-running, multi-step applications that Workflows enables: if you had to pay while your Workflow was sleeping, waiting on an event, or making a network call to an API, writing the “right” code would be at odds with writing affordable code.</p><p>There’s also no need to keep a Kubernetes cluster or a group of virtual machines running (and burning a hole in your wallet): we manage the infrastructure, and you only pay for the compute your Workflows consume.   </p>
    <div>
      <h2>What’s next?</h2>
      <a href="#whats-next">
        
      </a>
    </div>
    <p>Today, after months of developing the platform, we are announcing the open beta program, and we couldn't be more excited to see how you will be using Workflows. Looking forward, we want to do things like triggering instances from queue messages and have other ideas, but at the same time, we are certain that your feedback will help us shape the roadmap ahead.</p><p>We hope that this blog post gets you thinking about how to use Workflows for your next application, but also that it inspires you on what you can build on top of Workers. Workflows as a platform is entirely built on top of Workers, its resources, and APIs. Anyone can do it, too.</p><p>To chat with the team and other developers building on Workflows, join the #workflows-beta channel on the<a href="https://discord.cloudflare.com/"> <u>Cloudflare Developer Discord</u></a>, and keep an eye on the<a href="https://developers.cloudflare.com/workflows/reference/changelog/"> <u>Workflows changelog</u></a> during the beta. Otherwise,<a href="https://developers.cloudflare.com/workflows/get-started/guide/"> visit the Workflows tutorial</a> to get started.</p><p>If you're an engineer, <a href="https://www.cloudflare.com/en-gb/careers/jobs/"><u>look for opportunities</u></a> to work with us and help us improve Workflows or build other products.</p> ]]></content:encoded>
            <category><![CDATA[Developer Platform]]></category>
            <category><![CDATA[Cloudflare Workers]]></category>
            <category><![CDATA[Durable Objects]]></category>
            <category><![CDATA[Workflows]]></category>
            <guid isPermaLink="false">1YRfz7LKvAGrEMbRGhNrFP</guid>
            <dc:creator>Sid Chatterjee</dc:creator>
            <dc:creator>Matt Silverlock</dc:creator>
            <dc:creator>Celso Martinho</dc:creator>
        </item>
        <item>
            <title><![CDATA[Making Cloudflare Pages the fastest way to serve your sites]]></title>
            <link>https://blog.cloudflare.com/how-we-decreased-pages-latency/</link>
            <pubDate>Fri, 23 Jun 2023 13:00:37 GMT</pubDate>
            <description><![CDATA[ Pages is now the fastest way to serve your sites across Netlify, Vercel and many others and we’re so proud ]]></description>
            <content:encoded><![CDATA[ <p></p><p>In an era where visitors expect instant gratification and content on-demand, every millisecond counts. If you’re a web application developer, it’s an excellent time to be in this line of business, but with great power comes great responsibility. You’re tasked with creating an experience that is not only intuitive and delightful but also quick, reactive and responsive – sometimes with the two sides being at odds with each other. To add to this, if your business completely runs on the internet (say ecommerce), then your site’s Core Web Vitals could make or break your bottom line.</p><p>You don’t just need fast – you need <i>magic</i> fast. For the past two years, Cloudflare Pages has been serving up performant applications for users across the globe, but this week, we’re showing off our brand new, lightning fast architecture, decreasing the TTFB by up to 10X when serving assets.</p><p>And while a magician never reveals their secrets, this trick is too good to keep to ourselves. For all our application builders, we’re thrilled to share the juicy technical details on how we adopted Workers for Platforms — our extension of Workers to build SaaS businesses on top of — to make Pages one of the fastest ways to serve your sites.</p>
    <div>
      <h3>The problem</h3>
      <a href="#the-problem">
        
      </a>
    </div>
    <p>When we <a href="/cloudflare-pages-ga/">launched Pages in 2021</a>, we didn’t anticipate the exponential growth we would experience for our platform in the months and years to come. As our users began to adopt Pages into their development workflows, usage of our platform began to skyrocket. However, while riding the high of Pages’ success, we began to notice a problem – a rather large one. As projects grew in size, with every deployment came a pinch more latency, slowly affecting the end users visiting the Pages site. Customers with tens of thousands of deployments were at risk of introducing latency to their site – a problem that needed to be solved.</p><p>Before we dive into our technical solution, let’s first explore further the setup of Pages and the relationship between number of deployments and the observed latency.</p>
    <div>
      <h3>How could this be?</h3>
      <a href="#how-could-this-be">
        
      </a>
    </div>
    <p>Built on top of Cloudflare Workers, Pages serves static assets through a highly optimised Worker. We refer to this as the Asset Server Worker.</p><p>Users can also add dynamic content through Pages Functions which eventually get compiled into a separate Worker. Every single Pages deployment corresponds to unique instances of these Workers composed in a pipeline.</p><p>When a request hits Cloudflare we need to look up which pipeline to execute. As you’d expect, this is a function of the hostname in the URL.</p><p>If a user requested <a href="https://2b469e16.example.pages.dev/index.html">https://2b469e16.example.pages.dev/index.html</a>, the hostname is <a href="https://2b469e16.example.pages.dev/index.html">2b469e16.example.pages.dev</a> which is unique across all deployments on Pages — 2b469e16 is typically the commit hash and example in this case refers to the name of the project.</p><p>Every Pages project has its own routing table which is used to look up the pipeline to execute. The routing table happens to be a JSON object with a list of regexes for possible paths in that project (in our case, one for every deployment) and their corresponding pipelines.</p><p>The script_hash in the example below refers to the pipeline identifier. Naming is hard indeed.</p>
            <pre><code>{
 "filters": [
   {
     "pattern": "^(?:2b469e16.example.pages.dev(?:[:][0-9]+)?\\/(?&lt;p1&gt;.*))$",
     "script_hash": "..."
   },
   {
     "pattern": "^(?:example.pages.dev(?:[:][0-9]+)?\\/(?&lt;p1&gt;.*))$",
     "script_hash": "..."
   },
   {
     "pattern": "^(?:test.example.com(?:[:][0-9]+)?\\/(?&lt;p1&gt;.*))$",
     "script_hash": "..."
   }
 ],
 "v": 1
}</code></pre>
            <p>So to look up the pipeline in question, we would: download this JSON object from Quicksilver, parse it, and then iterate through this until it finds a regex that matches the current request.</p><p>Unsurprisingly, this is expensive. Let’s take a look at a quick real world example to see <i>how</i> expensive.</p><p>In one realistic case, it took us 107ms just to <i>parse</i> the JSON. The larger the JSON object gets, the more compute it takes to parse it — with tens of thousands of deployments (not unusual for very active projects that deploy immutable preview deployments for every git commit), this JSON could be several megabytes in size!</p><p>It doesn’t end there though. After parsing this, it took 29ms to then iterate and test several regexes to find the one that matched the current request.</p><p>To summarise, every single request to this project would take 136ms to just pick the right pipeline to execute. While this was the median case for projects with 10,000 deployments on average, we’ve seen projects with <b>seconds</b> in added latency making them unusable after 50,000 deployments, punishing users for using our platform.</p><p>Given most web sites load more than one asset for a page, this leads to timeouts and breakage leading to an unstable and unacceptable user experience.</p>
    <div>
      <h3>The secret sauce is Workers for Platforms</h3>
      <a href="#the-secret-sauce-is-workers-for-platforms">
        
      </a>
    </div>
    <p>We launched <a href="/workers-for-platforms/">Workers for Platforms</a> last year as a way to build ambitious platforms on top of Workers. Workers for Platforms lets one build complex pipelines where a request may be served by a Worker built and maintained by you but could then dispatch to a Worker written by a user of your platform. This allows your platform’s users to write their own Worker like they’ve been used to but while you control how and when they are executed.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/27P8dholN414dEiSnVKtUb/87156e682888a0a329799a756e0578ef/image4-23.png" />
            
            </figure><p>This isn’t very different from what we do with Pages. Users write their Pages functions which compile into a Worker. Users also upload their own static assets which are then bound to our special Asset Server Worker in unique pipelines for each of their deployments. And we control how and when which Worker gets executed based on a hostname in their URL.</p><p>Runtime lookups shouldn’t be O(n) though but O(1). Because Workers for Platforms was designed to build entire platforms on top of, lookups when trying to dispatch to a user’s Worker were designed as O(1) ensuring latency wasn’t a function of number of Workers in an account.</p>
    <div>
      <h3>The solution</h3>
      <a href="#the-solution">
        
      </a>
    </div>
    <p>By default, Workers for Platforms hashes the name of the Worker with a secret and uses that for lookups at runtime. However, because we need to dispatch by hostname, we had a different idea. At deployment time, we could hash the pipeline for the deployment by its hostname — <a href="https://2b469e16.example.pages.dev/index.html">2b469e16.example.pages.dev</a>, for example.</p><p>When a request comes in, we hash the hostname from the URL with our predefined secret and then use that value to look up the pipeline to execute. This entirely removes the necessity to fetch, parse and traverse the routing table JSON from before, now making our lookup O(1).</p><p>Once we were happy with our new setup from internal testing we wanted to onboard a real user. Our <a href="https://developers.cloudflare.com">Developer Docs</a> have been <a href="/new-dev-docs/">running on Pages</a> since the start of 2022 and during that time, we’ve dogfooded many different features and experiments. Due to the great relationship between our two teams and them being a sizable customer of ours we wanted to bring them onto our new Workers for Platform routing.</p><p>Before opting them in, TTFB was averaging at about 600ms.</p><p>After opting them in, TTFB is now 60ms. <a href="https://developers.cloudflare.com/analytics/web-analytics">Web Analytics</a> shows a noticeable drop in entire page load time as a result.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2hOpJu5iOcgDj5g7A9MR6W/690f8c5c96c4cc9a07f17d0a5d784472/Untitled.png" />
            
            </figure><p>This improvement was also visible through <a href="https://developer.chrome.com/en/docs/lighthouse/performance/performance-scoring/">Lighthouse scores</a> now approaching a perfect score of 100 instead of 78 which was the average we saw previously.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1DOMSoGft9tLCzg68wtXSN/bb3440d4f5e0c82a906dfb9793d71cfd/Untitled--1-.png" />
            
            </figure><p>The team was ecstatic about this especially given all of this happened under the hood with no downtime or required engineering team on their end. Not only is <a href="https://developers.cloudflare.com/">https://developers.cloudflare.com/</a> faster, we’re using less compute to serve it to all of you.</p>
    <div>
      <h3>The big migration</h3>
      <a href="#the-big-migration">
        
      </a>
    </div>
    <p>Migrating <a href="https://developers.cloudflare.com/">developers.cloudflare.com</a> was a big milestone for us and meant our new infrastructure was capable of handling traffic at scale. But a goal we were very certain of was migrating every Pages deployment ever created. We didn’t want to leave any users behind.</p><p>Turns out, that wasn’t a small number. There’d been over 14 million deployments so far over the years. This was about to be one of the biggest migrations we’d done to runtime assets and the risk was that we’d take down someone’s site.</p><p>We approached this migration with some key goals:</p><ul><li><p>Customer impact in terms of downtime was a no go, all of this needed to happen under the hood without anyone’s site being affected;</p></li><li><p>We needed the ability to A/B test the old and new setup so we could revert on a per site basis if something went wrong or was incompatible;</p></li><li><p>Migrations at this scale have the ability to cause incidents because they exceed the typical request capacity of our APIs in a short window so they need to run slowly;</p></li><li><p>Because this was likely to be a long running migration, we needed the ability to look at metrics and retry failures.</p></li></ul><p>The first step to all of this was to add the ability to A/B test between the legacy setup and the new one. To ensure we could A/B between the legacy setup and new one at any time, we needed to deploy both a regular pipeline (and updated routing table) and new Workers for Platforms hashed one for every deployment.</p><p>We also added a feature flag that allowed us to route to either the legacy setup or the new one per site or per data centre with the ability to explicitly opt out a site when an edgecase didn’t work.</p><p>With this setup, we started running our long running migration behind the scenes that duplicated every single deployment to the new Workers for Platforms enabled pipelines.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6cH3RPk39q50jgPD42nDej/d1ef38c399ffe3a8bb9a2e889863f267/unnamed--1-.png" />
            
            </figure><p>Duplicating them instead of replacing them meant that risk was low and A/B would be possible with the tradeoff of more cleanup after we finished but we picked that with reliability for users in mind.</p><p>A few days in after all 14 million deployments had finished migrating over, we started rollout to the new infrastructure with a percentage based rollout. This was a great way for us to find issues and ensure we were ready to serve all runtime traffic for Pages without the risk of an incident.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3UKjjt2upheD8aALjmDMT4/9634075b6f2d3d823ff7e257ed1e1f98/image--2-.png" />
            
            </figure>
    <div>
      <h3>Feeding three birds with one scone</h3>
      <a href="#feeding-three-birds-with-one-scone">
        
      </a>
    </div>
    <p>Alongside the significant latency improvements for Pages projects, this change also gave improvements in other areas:</p><ul><li><p><b><b><b>Lower CPU usage</b></b></b> - Since we no longer need to parse a huge JSON blob and do potentially thousands of regex matches, we saved a nice amount of CPU time across thousands of machines across our data centres.</p></li><li><p><b><b><b>Higher LRU hit rate</b></b></b> - We have LRU caches for things we fetch from <a href="/introducing-quicksilver-configuration-distribution-at-internet-scale/">Quicksilver</a> this is to reduce load on Quicksilver and improve performance. However, with the large routing tables we had previously, we could easily fill up this cache with one or just a few routing tables. Now that we have turned this into tiny single entry JSONs, we have improved the cache hit rate for <i>all Workers</i>.</p></li><li><p><b><b><b>Quicksilver storage reduction</b></b></b> - We also reduced the storage we take up with our routing tables by <b>92%</b>. This is a reduction of approximately 12 GiB on each of our hundreds of data centres.</p></li></ul>
    <div>
      <h3>We’re just getting started</h3>
      <a href="#were-just-getting-started">
        
      </a>
    </div>
    <p>Pages is now the <a href="https://www.webpagetest.org/video/compare.php?tests=230323_BiDcFV_ABE,230323_BiDcMX_ABF">fastest way</a> to serve your sites across Netlify, <a href="https://www.webpagetest.org/video/compare.php?tests=230323_AiDc65_AFF,230323_BiDcGD_ABX">Vercel</a> and many others and we’re so proud.</p><p>But it’s going to get even faster. With projects like <a href="/building-cloudflare-on-cloudflare/">Flame</a>, we can’t wait to shave off many more milliseconds to every request a user makes to your site.</p><p>To a faster web for all of us.</p> ]]></content:encoded>
            <category><![CDATA[Speed Week]]></category>
            <category><![CDATA[Cloudflare Pages]]></category>
            <category><![CDATA[Performance]]></category>
            <guid isPermaLink="false">5LCX7XNiq3YobLf0Mqolzp</guid>
            <dc:creator>Sid Chatterjee</dc:creator>
            <dc:creator>Daniel Walsh</dc:creator>
            <dc:creator>Nevi Shah</dc:creator>
        </item>
        <item>
            <title><![CDATA[Introducing Direct Uploads for Cloudflare Pages]]></title>
            <link>https://blog.cloudflare.com/cloudflare-pages-direct-uploads/</link>
            <pubDate>Tue, 10 May 2022 13:01:06 GMT</pubDate>
            <description><![CDATA[ Pages now supports direct uploads to give you more power to build and iterate how you want and with the tools you want ]]></description>
            <content:encoded><![CDATA[ <p></p><p>With Pages, we are constantly looking for ways to improve the developer experience. One of the areas we are keen to focus on is removing any barriers to entry for our users regardless of their use case or existing set up. Pages is an all-in-one solution with an automated Continuous Integration (CI) pipeline to help you build and deploy your site with one commit to your projects’ repositories hosted on GitHub or GitLab.</p><p>However, we realize that this excluded repositories that used a source control provider that Pages didn’t yet support and required varying build complexities. Even though Pages continues to build first-class integrations – for example, we <a href="/cloudflare-pages-partners-with-gitlab/">added GitLab support</a> in November 2021 – there are numerous providers to choose from, some of which use `git` alternatives like <a href="https://subversion.apache.org/">SVN</a> or <a href="https://www.mercurial-scm.org/">Mercurial</a> for their version control systems. It’s also common for larger companies to self-host their project repositories, guarded by a mix of custom authentication and/or proxy protocols.</p><p>Pages needed a solution that worked regardless of the repository’s source location and accommodate build project’s complexity. Today, we’re thrilled to announce that Pages now supports direct uploads to give you more power to build and iterate how you want and with the tools you want.</p>
    <div>
      <h2>What are direct uploads?</h2>
      <a href="#what-are-direct-uploads">
        
      </a>
    </div>
    <p>Direct uploads enable you to push your build artifacts directly to Pages, side-stepping the automatic, done-for-you CI pipeline that Pages provides for GitHub and GitLab repositories. This means that connecting a Pages project to a git repository is optional. In fact, using git or any version control system is optional!</p><p>Today, you can bring your assets directly to Pages by dragging and dropping them into our dashboard or pushing them through Wrangler CLI. You also have the power to use your own CI tool whether that’s something like <a href="http://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/">GitHub Actions or CircleCI</a> to handle your build. Taking your output directory you can bring these files directly to Pages to create a new project and all subsequent deployments after that. Every deployment will be distributed right to the Cloudflare network within seconds.</p>
    <div>
      <h2>How does it work?</h2>
      <a href="#how-does-it-work">
        
      </a>
    </div>
    <p>After using your preferred CI tooling outside of Pages, there are two ways to bring your pre-built assets and create a project with the direct uploads feature:</p><ol><li><p>Use the Wrangler CLI</p></li><li><p>Drag and drop them into the Pages interface</p></li></ol>
    <div>
      <h3>Wrangler CLI</h3>
      <a href="#wrangler-cli">
        
      </a>
    </div>
    <p>With an estimated 43k weekly Wrangler downloads, you too can use it to iterate quickly on your Pages projects right through the command line. With Wrangler (now with brand-new <a href="/10-things-I-love-about-wrangler">updates</a>!), you can both create your project and new deployments with a single command.</p><p>After Wrangler is installed and authenticated with your Cloudflare account, you can execute the following command to get your site up and running:</p>
            <pre><code>npx wrangler pages publish &lt;directory&gt;</code></pre>
            <div></div>
<p></p><p>Integration with Wrangler provides not only a great way to publish changes in a fast and consecutive manner, but also enables a seamless workflow between CI tooling for building right to Pages for deployment. Check out our tutorials on using <a href="http://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/">CircleCI and GitHub Actions</a> with Pages!</p>
    <div>
      <h3>Drag and drop</h3>
      <a href="#drag-and-drop">
        
      </a>
    </div>
    <p>However, we realize that sometimes you just want to get your site deployed instantaneously without any additional set up or installations. In fact, getting started with Pages shouldn’t have to require extensive configuration. The drag and drop feature allows you to take your pre-built assets and virtually drag them onto the Pages UI. With either a zip file or a single folder of assets, you can watch your project deploy in just a few short seconds straight to the 270+ cities in our network.</p><div></div>
<p></p>
    <div>
      <h2>What can you build?</h2>
      <a href="#what-can-you-build">
        
      </a>
    </div>
    <p>With this ease of deploying projects, the possibilities of what you can build are still endless. You can enjoy the fruits of Pages in a project created with direct uploads including but not limited to unique preview URLs, integration with Workers, Access and Web Analytics, and custom redirects/headers.</p><p>In thinking about your developer setup, direct uploads provide the flexibility to build the way you want such as:</p><ul><li><p>Designing and building your own CI workflow</p></li><li><p>Utilizing the CI tooling of your choice</p></li><li><p>Accommodating complex monorepo structures</p></li><li><p>Implementing custom CI logic for your builds.</p></li></ul>
    <div>
      <h2>Migrating from Workers Sites</h2>
      <a href="#migrating-from-workers-sites">
        
      </a>
    </div>
    <p>We’ll have to admit, the idea of publishing assets directly to our network came from a sister product to Pages called <a href="/workers-sites/">Workers Sites</a> and the resemblance is striking! However, Pages affords many feature enhancements to the developer experience that show as a pain point on Workers Sites.</p><p>With Pages direct uploads, you can enjoy the freedom and flexibility of customizing your workflow that Workers Sites provides while including an interface to track and share changes and manage production/preview environments. <a href="https://developers.cloudflare.com/pages/migrations/migrating-from-workers/">Check out our tutorial</a> on how to migrate over from Workers Sites.</p><p>This release immediately unlocks a broad range of use cases, allowing the most basic of projects to the most advanced to start deploying their websites to Pages today. Refer to our <a href="https://developers.cloudflare.com/pages/platform/direct-upload/">developer documentation</a> for more technical details. As always, head over to the <a href="https://discord.gg/cloudflaredev">Cloudflare Developers Discord</a> server and let us know what you think in the #direct-uploads-beta channel.</p>
    <div>
      <h2>Join us at Cloudflare Connect!</h2>
      <a href="#join-us-at-cloudflare-connect">
        
      </a>
    </div>
    <p>Calling all New York City developers! If you’re interested in learning more about Cloudflare Pages, join us for a series of workshops on how to build a full stack application on Thursday, May 12th. Follow along with demonstrations of using Pages alongside other products like Workers, Images and Cloudflare Gateway, and hear directly from our product managers. <a href="https://events.www.cloudflare.com/flow/cloudflare/connect2022nyc/landing/page/page">Register now</a>!</p> ]]></content:encoded>
            <category><![CDATA[Platform Week]]></category>
            <category><![CDATA[Cloudflare Pages]]></category>
            <category><![CDATA[Product News]]></category>
            <category><![CDATA[Serverless]]></category>
            <category><![CDATA[Wrangler]]></category>
            <guid isPermaLink="false">54zX3KuZvT6IeXghtXaDt6</guid>
            <dc:creator>Nevi Shah</dc:creator>
            <dc:creator>Greg Brimble</dc:creator>
            <dc:creator>John Fawcett</dc:creator>
            <dc:creator>Sid Chatterjee</dc:creator>
        </item>
    </channel>
</rss>