---
title: Artifacts
description: Store, version, and share filesystem artifacts across Workers, APIs, and Git-compatible tools.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Artifacts

Versioned storage that speaks Git.

Note

Artifacts is in beta. Refer to [How Artifacts works](https://developers.cloudflare.com/artifacts/concepts/how-artifacts-works/) for the architecture overview.

Artifacts stores versioned file trees behind a Git-compatible interface. Create repositories programmatically, import existing repositories, and hand off a URL to any standard Git client.

Use Artifacts when you need to:

* Store versioned file trees instead of raw blobs
* Hand off work to Git-aware tools, agents, and automation
* Isolate work in separate repos or branches for safer parallel execution
* Fork from a shared baseline and diff or merge the results later

The same repository can be addressed from [Workers](https://developers.cloudflare.com/workers/), the REST API, and Git clients. You can create one repo per agent, user, branch, or task, keep each unit of work separate, and compare or merge the results later.

[Get started](https://developers.cloudflare.com/artifacts/get-started/) 

Create your first repo with Workers or the REST API.

[Guides](https://developers.cloudflare.com/artifacts/guides/) 

Review authentication, imports, and ArtifactFS workflows.

[Concepts](https://developers.cloudflare.com/artifacts/concepts/) 

Learn how Artifacts works and how to structure repository workflows.

[API](https://developers.cloudflare.com/artifacts/api/) 

Review the Workers binding, REST API, and Git protocol.

[Observability](https://developers.cloudflare.com/artifacts/observability/) 

Explore metrics for understanding Artifact activity.

[Examples](https://developers.cloudflare.com/artifacts/examples/) 

See example integrations with Git clients, isomorphic-git, and Sandbox SDK.

[Platform](https://developers.cloudflare.com/artifacts/platform/) 

Review pricing, limits, and changelog entries for Artifacts.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}}]}
```

---

---
title: Get started
description: Start using Artifacts with Workers or the REST API.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/get-started/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Get started

Start here to create, inspect, and version Artifacts from Cloudflare developer workflows.

* Use **Workers** when you want the simplest path and already use Wrangler.
* Use **REST API** when you want control-plane HTTP access with a Cloudflare API token.
* [ Workers ](https://developers.cloudflare.com/artifacts/get-started/workers/)
* [ REST API ](https://developers.cloudflare.com/artifacts/get-started/rest-api/)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/get-started/","name":"Get started"}}]}
```

---

---
title: REST API
description: Create an Artifacts repo over HTTP.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/get-started/rest-api.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# REST API

Create an Artifacts repo with the REST API, then use a regular Git client to push and pull content.

By the end of this guide, you will create a repo inside an existing namespace, read back the repo remote URL, push a commit, and clone the same repo with a standard Git client.

## Prerequisites

You need:

* A Cloudflare account with access to Artifacts.
* A [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with **Artifacts** \> **Edit**.
* An existing Artifacts namespace, for example `default`.
* A local `git` client.
* `jq`, if you want to extract response fields automatically.

For Workers-based access instead of direct HTTP calls, use the [Workers get started guide](https://developers.cloudflare.com/artifacts/get-started/workers/).

## 1\. Export your environment variables

Set the variables used in the examples:

Terminal window

```

export ARTIFACTS_NAMESPACE="default"

export ARTIFACTS_REPO="starter-repo"

export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"

export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"

export ARTIFACTS_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE"


```

Use a unique repo name each time you run this guide.

Artifacts uses Bearer authentication for control-plane requests:

```

Authorization: Bearer $CLOUDFLARE_API_TOKEN


```

## 2\. Create a repo

Choose one of the following ways to create a repo inside that namespace:

* [ Manual ](#tab-panel-5285)
* [ jq ](#tab-panel-5286)

Terminal window

```

curl --request POST "$ARTIFACTS_BASE_URL/repos" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data "{\"name\":\"$ARTIFACTS_REPO\"}"


```

The response resembles the following:

```

{

  "result": {

    "id": "repo_123",

    "name": "starter-repo",

    "description": null,

    "default_branch": "main",

    "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git",

    "token": "art_v1_0123456789abcdef0123456789abcdef01234567?expires=1760000000"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

The REST control-plane base URL and the returned Git remote use different hosts. Use `result.remote` for Git operations.

Copy the `remote` and `token` values from `result` into local shell variables:

Terminal window

```

export ARTIFACTS_REMOTE="<PASTE_RESULT_REMOTE_FROM_RESPONSE>"

export ARTIFACTS_TOKEN="<PASTE_RESULT_TOKEN_FROM_RESPONSE>"


```

Capture the create response once and extract the fields with `jq`:

Terminal window

```

CREATE_RESPONSE=$(curl --silent --request POST "$ARTIFACTS_BASE_URL/repos" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data "{\"name\":\"$ARTIFACTS_REPO\"}")


export ARTIFACTS_REMOTE=$(printf '%s' "$CREATE_RESPONSE" | jq -r '.result.remote')

export ARTIFACTS_TOKEN=$(printf '%s' "$CREATE_RESPONSE" | jq -r '.result.token')


```

## 3\. Get the repo URL again

Fetch the repo metadata when you need to recover the remote URL later:

Terminal window

```

curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

```

{

  "result": {

    "id": "repo_123",

    "name": "starter-repo",

    "description": null,

    "default_branch": "main",

    "created_at": "<ISO_TIMESTAMP>",

    "updated_at": "<ISO_TIMESTAMP>",

    "last_push_at": null,

    "source": null,

    "read_only": false,

    "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

This endpoint returns repo metadata only. If you need a new repo token, mint one with `POST /tokens`.

## 4\. Push your first commit with git

Create a local repository and push it to the Artifacts remote:

Terminal window

```

mkdir artifacts-demo

cd artifacts-demo

git init -b main

printf '# Artifacts demo\n' > README.md

git add README.md

git commit -m "Initial commit"

git remote add origin "$ARTIFACTS_REMOTE"

git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" push -u origin main


```

This uses the recommended header-based form and keeps the token out of the remote URL.

If you need a self-contained remote URL for a short-lived command, build one from the token secret instead:

Terminal window

```

export ARTIFACTS_TOKEN_SECRET="${ARTIFACTS_TOKEN%%\?expires=*}"

export ARTIFACTS_AUTH_REMOTE="https://x:${ARTIFACTS_TOKEN_SECRET}@${ARTIFACTS_REMOTE#https://}"

git push "$ARTIFACTS_AUTH_REMOTE" HEAD:main


```

## 5\. Pull the repo with a regular git client

Clone the same repo into a second directory:

Terminal window

```

cd ..

git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$ARTIFACTS_REMOTE" artifacts-clone

git -C artifacts-clone log --oneline -1


```

You should see the commit you pushed in the previous step.

You can also clone with a self-contained remote URL for a short-lived command:

Terminal window

```

git clone "$ARTIFACTS_AUTH_REMOTE" artifacts-clone


```

## Next steps

[ REST API reference ](https://developers.cloudflare.com/artifacts/api/rest-api/) Review every repo and token endpoint with request and response examples. 

[ Git client example ](https://developers.cloudflare.com/artifacts/examples/git-client/) Use repo discovery and token minting with a standard Git client flow. 

[ Best practices ](https://developers.cloudflare.com/artifacts/concepts/best-practices/) Use repo isolation, least-privilege tokens, and namespace separation effectively. 

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/get-started/","name":"Get started"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/get-started/rest-api/","name":"REST API"}}]}
```

---

---
title: Workers
description: Create an Artifacts repo from a Worker.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/get-started/workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Workers

Create an Artifacts repo from a Worker and use a standard Git client to push and pull content.

By the end of this guide, you will create a Worker, bind it to Artifacts, create a repo through the Workers binding, push a commit, and clone the same repo back with a standard Git client.

## Prerequisites

1. Sign up for a [Cloudflare account ↗](https://dash.cloudflare.com/sign-up/workers-and-pages).
2. Install [Node.js ↗](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

Node.js version manager

Use a Node version manager like [Volta ↗](https://volta.sh/) or [nvm ↗](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), discussed later in this guide, requires a Node version of `16.17.0` or later.

You also need:

* Wrangler authenticated with `wrangler login`.
* Access to Artifacts in your Cloudflare account.
* An existing Artifacts namespace, for example `default`.
* A local `git` client.
* `jq`, if you want to extract response fields automatically.

## 1\. Create a Worker project

1. Create a new Worker project with C3:  
 npm  yarn  pnpm  
```  
npm create cloudflare@latest -- artifacts-worker  
```  
```  
yarn create cloudflare artifacts-worker  
```  
```  
pnpm create cloudflare@latest artifacts-worker  
```  
For setup, select the following options:  
   * For _What would you like to start with?_, choose `Hello World example`.  
   * For _Which template would you like to use?_, choose `Worker only`.  
   * For _Which language do you want to use?_, choose `TypeScript`.  
   * For _Do you want to use git for version control?_, choose `Yes`.  
   * For _Do you want to deploy your application?_, choose `No` (we will be making some changes before deploying).
2. Move into the project directory:  
Terminal window  
```  
cd artifacts-worker  
```

## 2\. Add the Artifacts binding

Open your Wrangler config file and add the Artifacts binding:

* [  wrangler.jsonc ](#tab-panel-5289)
* [  wrangler.toml ](#tab-panel-5290)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "artifacts-worker",

  "main": "src/index.ts",

  // Set this to today's date

  "compatibility_date": "2026-04-17",

  "artifacts": [

    {

      "binding": "ARTIFACTS",

      "namespace": "default"

    }

  ]

}


```

Explain Code

TOML

```

name = "artifacts-worker"

main = "src/index.ts"

# Set this to today's date

compatibility_date = "2026-04-17"


[[artifacts]]

binding = "ARTIFACTS"

namespace = "default"


```

This exposes Artifacts as `env.ARTIFACTS` inside your Worker.

If you authenticate with `wrangler login`, Wrangler requests `artifacts:write` by default.

If you are using TypeScript, regenerate your local binding types:

 npm  yarn  pnpm 

```
npx wrangler types
```

```
yarn wrangler types
```

```
pnpm wrangler types
```

Wrangler adds an `Artifacts` type to your generated `worker-configuration.d.ts` file.

## 3\. Write your Worker

Replace `src/index.ts` with the following code:

* [  JavaScript ](#tab-panel-5291)
* [  TypeScript ](#tab-panel-5292)

src/index.js

```

export default {

  async fetch(request, env) {

    const url = new URL(request.url);


    if (request.method === "POST" && url.pathname === "/repos") {

      // Read the repo name from the request body so the route is reusable.

      const body = await request.json().catch(() => ({}));


      const repoName = body.name ?? "starter-repo";


      // Create the repo and return the remote URL plus initial write token.

      const created = await env.ARTIFACTS.create(repoName);


      return Response.json({

        name: created.name,

        remote: created.remote,

        token: created.token,

      });

    }


    return new Response("Use POST /repos to create an Artifacts repo.", {

      status: 405,

      headers: { Allow: "POST" },

    });

  },

};


```

Explain Code

src/index.ts

```

interface Env {

  ARTIFACTS: Artifacts;

}


export default {

  async fetch(request: Request, env: Env): Promise<Response> {

    const url = new URL(request.url);


    if (request.method === "POST" && url.pathname === "/repos") {

      // Read the repo name from the request body so the route is reusable.

      const body = (await request.json().catch(() => ({}))) as {

        name?: string;

      };

      const repoName = body.name ?? "starter-repo";


      // Create the repo and return the remote URL plus initial write token.

      const created = await env.ARTIFACTS.create(repoName);


      return Response.json({

        name: created.name,

        remote: created.remote,

        token: created.token,

      });

    }


    return new Response("Use POST /repos to create an Artifacts repo.", {

      status: 405,

      headers: { Allow: "POST" },

    });

  },

} satisfies ExportedHandler<Env>;


```

Explain Code

This Worker does one job: create an Artifacts repo and return the values your Git client needs next.

Protect token-issuing routes

This example omits authentication so it can focus on the Artifacts flow. In production, authorize the caller before creating repos or returning write tokens.

For the demo, the Worker returns the initial write token. In production, mint short-lived read tokens for clone and pull flows, and mint write tokens only for operations that need push access.

## 4\. Invoke your Worker to create a repo

Start local development:

 npm  yarn  pnpm 

```
npx wrangler dev
```

```
yarn wrangler dev
```

```
pnpm wrangler dev
```

In a second terminal, choose one of the following ways to create a repo through your Worker.

If you rerun this guide, use a different repo name in the request body.

* [ Manual ](#tab-panel-5287)
* [ jq ](#tab-panel-5288)

Terminal window

```

curl http://localhost:8787/repos \

  --header "Content-Type: application/json" \

  --data '{

    "name": "starter-repo"

  }'


```

The response resembles the following:

```

{

  "name": "starter-repo",

  "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git",

  "token": "art_v1_0123456789abcdef0123456789abcdef01234567?expires=1760000000"

}


```

Copy the `remote` and `token` values into local shell variables:

Terminal window

```

export ARTIFACTS_REMOTE="<PASTE_REMOTE_FROM_RESPONSE>"

export ARTIFACTS_TOKEN="<PASTE_TOKEN_FROM_RESPONSE>"


```

Terminal window

```

RESPONSE=$(curl --silent http://localhost:8787/repos \

  --header "Content-Type: application/json" \

  --data '{"name":"starter-repo"}')


printf '%s\n' "$RESPONSE"


export ARTIFACTS_REMOTE=$(printf '%s' "$RESPONSE" | jq -r '.remote')

export ARTIFACTS_TOKEN=$(printf '%s' "$RESPONSE" | jq -r '.token')


```

## 5\. Push your first commit with git

Create a local repository and push it to Artifacts:

Terminal window

```

mkdir artifacts-demo

cd artifacts-demo

git init -b main

printf '# Artifacts demo\n' > README.md

git add README.md

git commit -m "Initial commit"

git remote add origin "$ARTIFACTS_REMOTE"

git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" push -u origin main


```

This uses the recommended header-based form and keeps the token out of the remote URL.

If you need a self-contained remote URL for a short-lived command, build one from the token secret instead:

Terminal window

```

export ARTIFACTS_TOKEN_SECRET="${ARTIFACTS_TOKEN%%\?expires=*}"

export ARTIFACTS_AUTH_REMOTE="https://x:${ARTIFACTS_TOKEN_SECRET}@${ARTIFACTS_REMOTE#https://}"

git push "$ARTIFACTS_AUTH_REMOTE" HEAD:main


```

## 6\. Pull the repo with a regular Git client

Clone the same repo into a second directory:

Terminal window

```

cd ..

git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$ARTIFACTS_REMOTE" artifacts-clone

git -C artifacts-clone log --oneline -1


```

You should see the commit you pushed in the previous step.

You can also clone with a self-contained remote URL for a short-lived command:

Terminal window

```

git clone "$ARTIFACTS_AUTH_REMOTE" artifacts-clone


```

## 7\. Deploy your Worker

Deploy the Worker so you can create repos without running `wrangler dev`:

 npm  yarn  pnpm 

```
npx wrangler deploy
```

```
yarn wrangler deploy
```

```
pnpm wrangler deploy
```

Wrangler prints your `workers.dev` URL. Use the same `curl` request against that URL to create additional repos from production.

## Next steps

[ Workers binding reference ](https://developers.cloudflare.com/artifacts/api/workers-binding/) Review the binding surface, return types, and method-by-method examples. 

[ Best practices ](https://developers.cloudflare.com/artifacts/concepts/best-practices/) Use repo isolation, least-privilege tokens, and namespace separation effectively. 

[ Git protocol ](https://developers.cloudflare.com/artifacts/api/git-protocol/) Use standard git-over-HTTPS remotes with either URL-based auth or \`http.extraHeader\`. 

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/get-started/","name":"Get started"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/get-started/workers/","name":"Workers"}}]}
```

---

---
title: Git protocol
description: Use Artifacts with standard git-over-HTTPS clients.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/api/git-protocol.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Git protocol

Artifacts exposes Git access for every Artifacts repository.

Each repo has a standard Git smart HTTP remote at `https://{accountId}.artifacts.cloudflare.net/git/{namespace}/{repo}.git`.

Use the returned repo `remote` with a regular Git client for `clone`, `fetch`, `pull`, and `push`.

## Authentication

Git routes accept repo access tokens in two forms:

| Format                            | Details                                                                                                                              | Example                                                                                                      |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| Bearer token in http.extraHeader  | Recommended for local workflows. Use the full token string returned by the control plane and keep credentials out of the remote URL. | git -c http.extraHeader="Authorization: Bearer $ARTIFACTS\_TOKEN" clone "$ARTIFACTS\_REMOTE" artifacts-clone |
| HTTP Basic auth in the remote URL | Use for short-lived, one-off commands when you need a self-contained remote. Put the token secret in the password slot.              | https://x:<token-secret>@<accountId>.artifacts.cloudflare.net/git/<namespace>/<repo>.git                     |

### Token format

Repo tokens are issued in the format `art_v1_<40 hex>?expires=<unix_seconds>`. The `?expires=` suffix is the token's expiry as a unix timestamp in seconds. To check when a token expires, parse the value after `?expires=`.

### Git `extraHeader` parameter

Git's [http.extraHeader ↗](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpextraHeader) setting lets you attach an HTTP header to git requests.

If you want to use the full token string returned by the API, pass it as a Bearer token:

Terminal window

```

git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$ARTIFACTS_REMOTE" artifacts-clone


```

### HTTPS remote with Basic auth

For the URL form, use the token secret in the password slot. Artifacts ignores the Basic auth username.

Use this form only when you need a self-contained remote URL for a short-lived command.

Terminal window

```

export ARTIFACTS_TOKEN_SECRET="${ARTIFACTS_TOKEN%%\?expires=*}"

export ARTIFACTS_AUTH_REMOTE="https://x:${ARTIFACTS_TOKEN_SECRET}@${ARTIFACTS_REMOTE#https://}"


```

Terminal window

```

git clone "$ARTIFACTS_AUTH_REMOTE" artifacts-clone


```

Terminal window

```

git push "$ARTIFACTS_AUTH_REMOTE" HEAD:main


```

Use any non-empty username in the URL. Artifacts accepts that username but does not otherwise use or log it, so `x` is just a placeholder.

## Protocol support

Artifacts supports Git protocol v1 and v2 for clone and fetch. Git clients negotiate the protocol automatically.

| Operation                         | Git service      | Protocol support | Notes                                                                                                                  |
| --------------------------------- | ---------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Clone and fetch                   | git-upload-pack  | v1 and v2        | Protocol v2 supports ls-refs and fetch. Protocol v1 supports normal fetch flows, including shallow and deepen fetches. |
| Push                              | git-receive-pack | v1               | Push uses the standard v1 receive-pack flow.                                                                           |
| Push over protocol v2             | git-receive-pack | Not supported    | Artifacts does not support v2 receive-pack.                                                                            |
| Optional protocol v1 capabilities | git-upload-pack  | Partial          | Some optional v1 capabilities, such as filter and include-tag, are not supported.                                      |

## Token scopes

| Scope | Commands                                 | Notes                                                 |
| ----- | ---------------------------------------- | ----------------------------------------------------- |
| read  | git clone, git fetch, git pull           | Use for read-only access.                             |
| write | git clone, git fetch, git pull, git push | git push mutates the repo and requires a write token. |

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/api/","name":"API"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/api/git-protocol/","name":"Git protocol"}}]}
```

---

---
title: REST API
description: Manage Artifacts repos and tokens over HTTP.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/api/rest-api.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# REST API

Use the Artifacts REST API to manage repos, remotes, forks, imports, and tokens from external systems.

## Base URL and authentication

Artifacts REST routes use this base path:

```

https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE


```

Requests to `/v1/api/...` use Bearer authentication:

```

Authorization: Bearer <CLOUDFLARE_API_TOKEN>


```

All routes below are relative to this base URL.

Cloudflare API tokens authenticate REST control-plane routes. Repo tokens authenticate Git operations against the returned `remote` URL.

The following examples assume:

Terminal window

```

export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"

export ARTIFACTS_NAMESPACE="default"

export ARTIFACTS_REPO="starter-repo"

export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"

export ARTIFACTS_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE"


```

All responses use the standard Cloudflare v4 envelope.

Returned repo tokens are secrets. Do not log them or store them in long-lived remotes unless your workflow requires it.

## Shared types

TypeScript

```

export type NamespaceName = string;

export type RepoName = string;

export type BranchName = string;

export type Scope = "read" | "write";

export type TokenState = "active" | "expired" | "revoked";

export type ArtifactToken = string;

export type Cursor = string;

export type RepoSortField =

  | "created_at"

  | "updated_at"

  | "last_push_at"

  | "name";

export type SortDirection = "asc" | "desc";


export interface ApiError {

  code: number;

  message: string;

  documentation_url?: string;

  source?: {

    pointer?: string;

  };

}


export interface CursorResultInfo {

  cursor: string;

  per_page: number;

  count: number;

}


export interface OffsetResultInfo {

  page: number;

  per_page: number;

  total_pages: number;

  count: number;

  total_count: number;

}


export type ResultInfo = CursorResultInfo | OffsetResultInfo;


export interface ApiEnvelope<T> {

  result: T | null;

  success: boolean;

  errors: ApiError[];

  messages: ApiError[];

  result_info?: ResultInfo;

}


export interface RepoInfo {

  id: string;

  name: RepoName;

  description: string | null;

  default_branch: string;

  created_at: string;

  updated_at: string;

  last_push_at: string | null;

  source: string | null;

  read_only: boolean;

}


export interface RemoteRepoInfo extends RepoInfo {

  remote: string;

}


export interface TokenInfo {

  id: string;

  scope: Scope;

  state: TokenState;

  created_at: string;

  expires_at: string;

}


```

Explain Code

## Repos

### Create a repo

Route: `POST /repos`

Request body:

* `name` ` RepoName ` required
* `description` ` string ` optional
* `default_branch` ` BranchName ` optional
* `read_only` ` boolean ` optional

Response type:

TypeScript

```

export interface CreateRepoRequest {

  name: RepoName;

  description?: string;

  default_branch?: BranchName;

  read_only?: boolean;

}


export interface CreateRepoResult {

  id: string;

  name: RepoName;

  description: string | null;

  default_branch: string;

  remote: string;

  token: ArtifactToken;

}


export type CreateRepoResponse = ApiEnvelope<CreateRepoResult>;


```

Explain Code

Terminal window

```

curl --request POST "$ARTIFACTS_BASE_URL/repos" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data '{

    "name": "starter-repo",

    "description": "Repository for automation experiments",

    "default_branch": "main",

    "read_only": false

  }'


```

```

{

  "result": {

    "id": "repo_123",

    "name": "starter-repo",

    "description": "Repository for automation experiments",

    "default_branch": "main",

    "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git",

    "token": "art_v1_0123456789abcdef0123456789abcdef01234567?expires=1760000000"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

### List repos

Route: `GET /repos?limit=&cursor=&search=&sort=&direction=`

Query parameters:

* `limit` ` number ` optional (default: 50, max: 200)
* `cursor` ` Cursor ` optional
* `search` ` string ` optional
* `sort` ` "created_at" | "updated_at" | "last_push_at" | "name" ` optional (default: "created\_at")
* `direction` ` "asc" | "desc" ` optional (default: "desc")

Response type:

TypeScript

```

export interface ListReposQuery {

  limit?: number;

  cursor?: Cursor;

  search?: string;

  sort?: RepoSortField;

  direction?: SortDirection;

}


export type ListReposResponse = ApiEnvelope<RepoInfo[]>;


```

Terminal window

```

curl "$ARTIFACTS_BASE_URL/repos?limit=20&sort=updated_at&direction=desc" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

```

{

  "result": [

    {

      "id": "repo_123",

      "name": "starter-repo",

      "description": "Repository for automation experiments",

      "default_branch": "main",

      "created_at": "<ISO_TIMESTAMP>",

      "updated_at": "<ISO_TIMESTAMP>",

      "last_push_at": "<ISO_TIMESTAMP>",

      "source": null,

      "read_only": false

    }

  ],

  "success": true,

  "errors": [],

  "messages": [],

  "result_info": {

    "cursor": "next-cursor",

    "per_page": 20,

    "count": 1

  }

}


```

Explain Code

### Get a repo

Route: `GET /repos/:name`

Response type:

TypeScript

```

export type GetRepoResponse = ApiEnvelope<RemoteRepoInfo>;


```

Terminal window

```

curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

```

{

  "result": {

    "id": "repo_123",

    "name": "starter-repo",

    "description": "Repository for automation experiments",

    "default_branch": "main",

    "created_at": "<ISO_TIMESTAMP>",

    "updated_at": "<ISO_TIMESTAMP>",

    "last_push_at": "<ISO_TIMESTAMP>",

    "source": null,

    "read_only": false,

    "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

### Delete a repo

Route: `DELETE /repos/:name`

This route returns `202 Accepted`.

Response type:

TypeScript

```

export interface DeleteRepoResult {

  id: string;

}


export type DeleteRepoResponse = ApiEnvelope<DeleteRepoResult>;


```

Terminal window

```

curl --request DELETE "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

```

{

  "result": {

    "id": "repo_123"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

### Fork a repo

Route: `POST /repos/:name/fork`

Request body:

* `name` ` RepoName ` required
* `description` ` string ` optional
* `read_only` ` boolean ` optional
* `default_branch_only` ` boolean ` optional

Response type:

TypeScript

```

export interface ForkRepoRequest {

  name: RepoName;

  description?: string;

  read_only?: boolean;

  default_branch_only?: boolean;

}


export interface ForkRepoResult extends CreateRepoResult {

  objects: number;

}


export type ForkRepoResponse = ApiEnvelope<ForkRepoResult>;


```

Explain Code

Terminal window

```

curl --request POST "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/fork" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data '{

    "name": "starter-repo-copy",

    "description": "Fork for testing",

    "read_only": false,

    "default_branch_only": true

  }'


```

```

{

  "result": {

    "id": "repo_456",

    "name": "starter-repo-copy",

    "description": "Repository for automation experiments",

    "default_branch": "main",

    "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo-copy.git",

    "token": "art_v1_89abcdef0123456789abcdef0123456789abcdef?expires=1760003600",

    "objects": 128

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

### Import a public HTTPS remote

Route: `POST /repos/:name/import`

Request body:

* `url` ` string ` required
* `branch` ` string ` optional
* `depth` ` number ` optional
* `read_only` ` boolean ` optional

Response type:

TypeScript

```

export interface ImportRepoRequest {

  url: string;

  branch?: string;

  depth?: number;

  read_only?: boolean;

}


export type ImportRepoResponse = ApiEnvelope<CreateRepoResult>;


```

Pass a full HTTPS Git remote URL, for example `https://github.com/facebook/react` or `https://gitlab.com/group/project.git`.

Terminal window

```

curl --request POST "$ARTIFACTS_BASE_URL/repos/react-mirror/import" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data '{

    "url": "https://github.com/facebook/react",

    "branch": "main",

    "depth": 100

  }'


```

```

{

  "result": {

    "id": "repo_789",

    "name": "react-mirror",

    "description": null,

    "default_branch": "main",

    "remote": "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/react-mirror.git",

    "token": "art_v1_fedcba9876543210fedcba9876543210fedcba98?expires=1760007200"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

## Tokens

These tokens are for Git routes. They do not authenticate REST API requests.

### List tokens for a repo

Route: `GET /repos/:name/tokens?state=&per_page=&page=`

Query parameters:

* `state` ` "active" | "expired" | "revoked" | "all" ` optional (default: "active")
* `per_page` ` number ` optional (default: 30, max: 100)
* `page` ` number ` optional (default: 1)

Response type:

TypeScript

```

export interface ListTokensQuery {

  state?: TokenState | "all";

  per_page?: number;

  page?: number;

}


export type ListTokensResponse = ApiEnvelope<TokenInfo[]>;


```

Terminal window

```

curl "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/tokens?state=all&per_page=30&page=1" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

```

{

  "result": [

    {

      "id": "tok_123",

      "scope": "read",

      "state": "active",

      "created_at": "<ISO_TIMESTAMP>",

      "expires_at": "<ISO_TIMESTAMP>"

    }

  ],

  "success": true,

  "errors": [],

  "messages": [],

  "result_info": {

    "page": 1,

    "per_page": 30,

    "total_pages": 1,

    "count": 1,

    "total_count": 1

  }

}


```

Explain Code

### Create a token

Route: `POST /tokens`

Request body:

* `repo` ` RepoName ` required
* `scope` ` "read" | "write" ` optional (default: "write")
* `ttl` ` number ` optional (seconds, default: 86400)

Response type:

TypeScript

```

export interface CreateTokenRequest {

  repo: RepoName;

  scope?: Scope;

  ttl?: number;

}


export interface CreateTokenResult {

  id: string;

  plaintext: ArtifactToken;

  scope: Scope;

  expires_at: string;

}


export type CreateTokenResponse = ApiEnvelope<CreateTokenResult>;


```

Explain Code

Terminal window

```

curl --request POST "$ARTIFACTS_BASE_URL/tokens" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data '{

    "repo": "starter-repo",

    "scope": "read",

    "ttl": 3600

  }'


```

```

{

  "result": {

    "id": "tok_123",

    "plaintext": "art_v1_0123456789abcdef0123456789abcdef01234567?expires=1760000000",

    "scope": "read",

    "expires_at": "<ISO_TIMESTAMP>"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

Explain Code

### Revoke a token

Route: `DELETE /tokens/:id`

Response type:

TypeScript

```

export interface RevokeTokenResult {

  id: string;

}


export type RevokeTokenResponse = ApiEnvelope<RevokeTokenResult>;


```

Terminal window

```

curl --request DELETE "$ARTIFACTS_BASE_URL/tokens/tok_123" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

```

{

  "result": {

    "id": "tok_123"

  },

  "success": true,

  "errors": [],

  "messages": []

}


```

## Errors

Application errors also use the v4 envelope:

TypeScript

```

export interface ApiError {

  code: number;

  message: string;

  documentation_url?: string;

  source?: {

    pointer?: string;

  };

}


```

## Next steps

[ Workers binding ](https://developers.cloudflare.com/artifacts/api/workers-binding/) Call the same Artifacts operations from a Worker through the Artifacts binding. 

[ Git protocol ](https://developers.cloudflare.com/artifacts/api/git-protocol/) Use repo remotes and tokens with standard git-over-HTTPS tooling. 

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/api/","name":"API"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/api/rest-api/","name":"REST API"}}]}
```

---

---
title: Workers binding
description: Call Artifacts from a Worker binding.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/api/workers-binding.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Workers binding

Use the Artifacts Workers binding to create, import, inspect, fork, and delete repos directly from your Worker. The Artifacts binding returns repo handles that allow repo-scoped operations such as token management and forking.

## Configure the binding

Add the Artifacts binding to your Wrangler config file:

* [  wrangler.jsonc ](#tab-panel-5245)
* [  wrangler.toml ](#tab-panel-5246)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "artifacts": [

    {

      "binding": "ARTIFACTS",

      "namespace": "default"

    }

  ]

}


```

TOML

```

[[artifacts]]

binding = "ARTIFACTS"

namespace = "default" # any name works — use namespaces to organise repos


```

After you run `npx wrangler types`, your Worker environment looks like this:

TypeScript

```

export interface Env {

  ARTIFACTS: Artifacts;

}


```

Wrangler generates the `Artifacts` type for consumers and binds it directly in your environment.

If you authenticate with `wrangler login`, Wrangler requests `artifacts:write` by default.

## Namespace methods

Use namespace methods on `env.ARTIFACTS` to create, list, inspect, import, or delete repos.

### `create(name, opts?)`

* `name` ` RepoName ` required
* `opts.readOnly` ` boolean ` optional
* `opts.description` ` string ` optional
* `opts.setDefaultBranch` ` string ` optional
* Returns ` Promise<ArtifactsCreateRepoResult> `

* [  JavaScript ](#tab-panel-5253)
* [  TypeScript ](#tab-panel-5254)

JavaScript

```

async function createRepo(artifacts) {

  const created = await artifacts.create("starter-repo", {

    description: "Repository for automation experiments",

    readOnly: false,

    setDefaultBranch: "main",

  });


  return {

    defaultBranch: created.defaultBranch,

    name: created.name,

    remote: created.remote,

    initialToken: created.token,

  };

}


```

Explain Code

TypeScript

```

async function createRepo(artifacts: Artifacts) {

  const created = await artifacts.create("starter-repo", {

    description: "Repository for automation experiments",

    readOnly: false,

    setDefaultBranch: "main",

  });


  return {

    defaultBranch: created.defaultBranch,

    name: created.name,

    remote: created.remote,

    initialToken: created.token,

  };

}


```

Explain Code

### `get(name)`

* `name` ` RepoName ` required
* Returns ` Promise<ArtifactsRepo> `
* Throws if the repo does not exist.

* [  JavaScript ](#tab-panel-5247)
* [  TypeScript ](#tab-panel-5248)

JavaScript

```

async function getRepoHandle(artifacts) {

  const repo = await artifacts.get("starter-repo");

  return repo;

}


```

TypeScript

```

async function getRepoHandle(artifacts: Artifacts) {

  const repo = await artifacts.get("starter-repo");

  return repo;

}


```

### `list(opts?)`

* `opts.limit` ` number ` optional
* `opts.cursor` ` Cursor ` optional
* Returns ` Promise<ArtifactsRepoListResult> `

* [  JavaScript ](#tab-panel-5251)
* [  TypeScript ](#tab-panel-5252)

JavaScript

```

async function listRepos(artifacts) {

  const page = await artifacts.list({ limit: 10 });


  return {

    names: page.repos.map((repo) => repo.name),

    nextCursor: page.cursor ?? null,

  };

}


```

TypeScript

```

async function listRepos(artifacts: Artifacts) {

  const page = await artifacts.list({ limit: 10 });


  return {

    names: page.repos.map((repo) => repo.name),

    nextCursor: page.cursor ?? null,

  };

}


```

### `import(params)`

Import a repository from an external git remote.

* `params.source.url` ` string ` required — HTTPS URL of the source repository.
* `params.source.branch` ` string ` optional — Branch to import (defaults to the remote's default branch).
* `params.source.depth` ` number ` optional — Shallow clone depth.
* `params.target.name` ` RepoName ` required — Name for the imported repo.
* `params.target.opts.description` ` string ` optional
* `params.target.opts.readOnly` ` boolean ` optional
* Returns ` Promise<ArtifactsCreateRepoResult> `

* [  JavaScript ](#tab-panel-5263)
* [  TypeScript ](#tab-panel-5264)

JavaScript

```

async function importFromGitHub(artifacts) {

  const imported = await artifacts.import({

    source: {

      url: "https://github.com/cloudflare/workers-sdk",

      branch: "main",

    },

    target: {

      name: "workers-sdk",

    },

  });


  return {

    name: imported.name,

    remote: imported.remote,

    token: imported.token,

  };

}


```

Explain Code

TypeScript

```

async function importFromGitHub(artifacts: Artifacts) {

  const imported = await artifacts.import({

    source: {

      url: "https://github.com/cloudflare/workers-sdk",

      branch: "main",

    },

    target: {

      name: "workers-sdk",

    },

  });


  return {

    name: imported.name,

    remote: imported.remote,

    token: imported.token,

  };

}


```

Explain Code

### `delete(name)`

* `name` ` RepoName ` required
* Returns ` Promise<boolean> `

* [  JavaScript ](#tab-panel-5249)
* [  TypeScript ](#tab-panel-5250)

JavaScript

```

async function deleteRepo(artifacts) {

  return artifacts.delete("starter-repo");

}


```

TypeScript

```

async function deleteRepo(artifacts: Artifacts) {

  return artifacts.delete("starter-repo");

}


```

## Repo handle methods

Call `await artifacts.get(name)` to get a repo handle. The handle extends `ArtifactsRepoInfo`, so repo metadata (`id`, `name`, `remote`, `defaultBranch`, etc.) is available directly as properties.

* [  JavaScript ](#tab-panel-5255)
* [  TypeScript ](#tab-panel-5256)

JavaScript

```

async function getRemoteUrl(artifacts) {

  const repo = await artifacts.get("starter-repo");

  return repo.remote;

}


```

TypeScript

```

async function getRemoteUrl(artifacts: Artifacts) {

  const repo = await artifacts.get("starter-repo");

  return repo.remote;

}


```

### `createToken(scope?, ttl?)`

* `scope` ` "read" | "write" ` optional (default: "write")
* `ttl` ` number ` optional (seconds)
* Returns ` Promise<ArtifactsCreateTokenResult> `

* [  JavaScript ](#tab-panel-5257)
* [  TypeScript ](#tab-panel-5258)

JavaScript

```

async function mintReadToken(artifacts) {

  const repo = await artifacts.get("starter-repo");

  return repo.createToken("read", 3600);

}


```

TypeScript

```

async function mintReadToken(artifacts: Artifacts) {

  const repo = await artifacts.get("starter-repo");

  return repo.createToken("read", 3600);

}


```

### `listTokens()`

* Returns ` Promise<ArtifactsTokenListResult> `

* [  JavaScript ](#tab-panel-5261)
* [  TypeScript ](#tab-panel-5262)

JavaScript

```

async function listRepoTokens(artifacts) {

  const repo = await artifacts.get("starter-repo");

  const result = await repo.listTokens();

  return {

    total: result.total,

    tokens: result.tokens,

  };

}


```

TypeScript

```

async function listRepoTokens(artifacts: Artifacts) {

  const repo = await artifacts.get("starter-repo");

  const result = await repo.listTokens();

  return {

    total: result.total,

    tokens: result.tokens,

  };

}


```

### `revokeToken(tokenOrId)`

* `tokenOrId` ` string ` required
* Returns ` Promise<boolean> `

* [  JavaScript ](#tab-panel-5259)
* [  TypeScript ](#tab-panel-5260)

JavaScript

```

async function revokeToken(artifacts, tokenOrId) {

  const repo = await artifacts.get("starter-repo");

  return repo.revokeToken(tokenOrId);

}


```

TypeScript

```

async function revokeToken(artifacts: Artifacts, tokenOrId: string) {

  const repo = await artifacts.get("starter-repo");

  return repo.revokeToken(tokenOrId);

}


```

### `fork(name, opts?)`

* `name` ` RepoName ` required
* `opts.description` ` string ` optional
* `opts.readOnly` ` boolean ` optional
* `opts.defaultBranchOnly` ` boolean ` optional
* Returns ` Promise<ArtifactsCreateRepoResult> `

* [  JavaScript ](#tab-panel-5265)
* [  TypeScript ](#tab-panel-5266)

JavaScript

```

async function forkRepo(artifacts) {

  const repo = await artifacts.get("starter-repo");

  const forked = await repo.fork("starter-repo-copy", {

    description: "Fork for testing",

    defaultBranchOnly: true,

    readOnly: false,

  });


  return forked.remote;

}


```

Explain Code

TypeScript

```

async function forkRepo(artifacts: Artifacts) {

  const repo = await artifacts.get("starter-repo");

  const forked = await repo.fork("starter-repo-copy", {

    description: "Fork for testing",

    defaultBranchOnly: true,

    readOnly: false,

  });


  return forked.remote;

}


```

Explain Code

## Worker example

This example combines the binding methods in one Worker route.

* [  JavaScript ](#tab-panel-5267)
* [  TypeScript ](#tab-panel-5268)

src/index.js

```

export default {

  async fetch(request, env) {

    const url = new URL(request.url);


    if (request.method === "POST" && url.pathname === "/repos") {

      const created = await env.ARTIFACTS.create("starter-repo");

      return Response.json({

        name: created.name,

        remote: created.remote,

      });

    }


    if (request.method === "GET" && url.pathname === "/repos/starter-repo") {

      const repo = await env.ARTIFACTS.get("starter-repo");

      return Response.json({

        id: repo.id,

        name: repo.name,

        remote: repo.remote,

        defaultBranch: repo.defaultBranch,

      });

    }


    if (request.method === "POST" && url.pathname === "/tokens") {

      const repo = await env.ARTIFACTS.get("starter-repo");

      const token = await repo.createToken("read", 3600);

      return Response.json(token);

    }


    return Response.json(

      { message: "Use POST /repos, GET /repos/starter-repo, or POST /tokens." },

      { status: 404 },

    );

  },

};


```

Explain Code

src/index.ts

```

interface Env {

  ARTIFACTS: Artifacts;

}


export default {

  async fetch(request: Request, env: Env): Promise<Response> {

    const url = new URL(request.url);


    if (request.method === "POST" && url.pathname === "/repos") {

      const created = await env.ARTIFACTS.create("starter-repo");

      return Response.json({

        name: created.name,

        remote: created.remote,

      });

    }


    if (request.method === "GET" && url.pathname === "/repos/starter-repo") {

      const repo = await env.ARTIFACTS.get("starter-repo");

      return Response.json({

        id: repo.id,

        name: repo.name,

        remote: repo.remote,

        defaultBranch: repo.defaultBranch,

      });

    }


    if (request.method === "POST" && url.pathname === "/tokens") {

      const repo = await env.ARTIFACTS.get("starter-repo");

      const token = await repo.createToken("read", 3600);

      return Response.json(token);

    }


    return Response.json(

      { message: "Use POST /repos, GET /repos/starter-repo, or POST /tokens." },

      { status: 404 },

    );

  },

} satisfies ExportedHandler<Env>;


```

Explain Code

Protect token routes

This example omits authentication so it can focus on the binding surface. In production, authorize the caller before creating repos or returning tokens.

## Generated types

Run `npx wrangler types` in your own project and treat the generated `worker-configuration.d.ts` file as the source of truth for the Artifacts binding types in that environment.

## Next steps

[ REST API ](https://developers.cloudflare.com/artifacts/api/rest-api/) Compare the binding methods with the underlying HTTP routes. 

[ Get started with Workers ](https://developers.cloudflare.com/artifacts/get-started/workers/) Use the binding in a full Worker project from local development through deploy. 

[ Git protocol ](https://developers.cloudflare.com/artifacts/api/git-protocol/) Use repo remotes and tokens with standard git-over-HTTPS clients. 

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/api/","name":"API"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/api/workers-binding/","name":"Workers binding"}}]}
```

---

---
title: Best practices for Artifacts
description: Use repo, token, metadata, and namespace patterns.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/concepts/best-practices.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Best practices for Artifacts

Artifacts works best when you isolate work, scope access narrowly, keep metadata separate, and partition storage deliberately.

Use these patterns to structure repos for agents, automation, and shared systems.

## Organize repos for isolation

### Create a repo per agent, session, or application

Create one repo for each unit of autonomous work. If you have `10,000` agents, create `10,000` repos.

This keeps each agent's changes, failures, and cleanup lifecycle separate. It also avoids turning one shared repo into a hot spot for conflicts, large diffs, and accidental overwrites.

Use this pattern when you need to:

* isolate one agent's work from another agent's work
* hand off a repo to a single session or user application
* review, merge, archive, or delete work independently

Use branches only when collaborators share the same lifecycle and need to work on the same repository. Do not use one shared repo as a queue for many autonomous agents.

### Use unique names

Repo names are unique within a namespace. If multiple agents need isolated copies of the same baseline repo in one namespace, do not reuse a short shared name such as `docs-site`.

Include stable identifiers in the repo name, such as the agent name, session ID, user ID, or workflow ID. A name like `${agentName}-${sessionId}-${repoName}` is safer than `${repoName}` because it avoids collisions and makes cleanup easier.

This example creates a unique repo name before creating the repo.

* [  JavaScript ](#tab-panel-5269)
* [  TypeScript ](#tab-panel-5270)

src/index.js

```

async function createRepoCopy(env, agentName, sessionId, repoName) {

  const uniqueRepoName = `${agentName}-${sessionId}-${repoName}`;


  return env.ARTIFACTS.create(uniqueRepoName);

}


```

src/index.ts

```

interface Env {

  ARTIFACTS: Artifacts;

}


async function createRepoCopy(

  env: Env,

  agentName: string,

  sessionId: string,

  repoName: string,

) {

  const uniqueRepoName = `${agentName}-${sessionId}-${repoName}`;


  return env.ARTIFACTS.create(uniqueRepoName);

}


```

Explain Code

### Fork from a stable baseline

Start new repos from a trusted baseline when agents need the same starter files, prompts, or application structure. Forking from a reviewed repo is safer than copying files into every new repo by hand.

This keeps your starting point consistent and makes downstream diffs easier to review. It also lets you merge back only the results you want.

This example forks a reviewed baseline repo into a session-specific repo.

* [  JavaScript ](#tab-panel-5271)
* [  TypeScript ](#tab-panel-5272)

src/index.js

```

async function forkFromBaseline(env, sessionId) {

  const baseline = await env.ARTIFACTS.get("starter-repo");

  const forked = await baseline.fork(`starter-repo-${sessionId}`, {

    description: `Fork for session ${sessionId}`,

    defaultBranchOnly: true,

    readOnly: false,

  });


  return {

    name: forked.name,

    remote: forked.remote,

  };

}


```

Explain Code

src/index.ts

```

interface Env {

  ARTIFACTS: Artifacts;

}


async function forkFromBaseline(env: Env, sessionId: string) {

  const baseline = await env.ARTIFACTS.get("starter-repo");

  const forked = await baseline.fork(`starter-repo-${sessionId}`, {

    description: `Fork for session ${sessionId}`,

    defaultBranchOnly: true,

    readOnly: false,

  });


  return {

    name: forked.name,

    remote: forked.remote,

  };

}


```

Explain Code

## Scope access narrowly

### Mint least-privilege repo tokens

Artifacts tokens are repo-scoped. Prefer `read` tokens for cloning, indexing, review, and retrieval.

Use `write` tokens only for the agent or system that must push changes. Give tokens short lifetimes, and re-issue a fresh token for each agent session.

This example uses the [Workers binding](https://developers.cloudflare.com/artifacts/api/workers-binding/) to mint a short-lived read token for a repo.

Assume the caller is already authenticated and authorized before this route returns a token.

* [  JavaScript ](#tab-panel-5273)
* [  TypeScript ](#tab-panel-5274)

src/index.js

```

export default {

  async fetch(request, env) {

    const url = new URL(request.url);

    const repoName = url.searchParams.get("repo") ?? "starter-repo";


    const repo = await env.ARTIFACTS.get(repoName);

    const token = await repo.createToken("read", 900);


    return Response.json({

      repo: repoName,

      scope: token.scope,

      expiresAt: token.expiresAt,

      token: token.plaintext,

    });

  },

};


```

Explain Code

src/index.ts

```

interface Env {

  ARTIFACTS: Artifacts;

}


export default {

  async fetch(request: Request, env: Env): Promise<Response> {

    const url = new URL(request.url);

    const repoName = url.searchParams.get("repo") ?? "starter-repo";


    const repo = await env.ARTIFACTS.get(repoName);

    const token = await repo.createToken("read", 900);


    return Response.json({

      repo: repoName,

      scope: token.scope,

      expiresAt: token.expiresAt,

      token: token.plaintext,

    });

  },

} satisfies ExportedHandler<Env>;


```

Explain Code

Use the same pattern for `write` tokens only after your Worker authorizes a session that must push changes.

Do not issue one long-lived write token to every agent. Mint the narrowest token you can, for the shortest time you can.

## Store harness metadata separately

### Use git notes for prompts and model output

Use [git notes ↗](https://git-scm.com/docs/git-notes) to attach prompts, model output, run IDs, or other harness metadata to a commit without changing the commit object or working tree.

This lets you use Artifacts as both the versioned filesystem for agent work and the source of truth for your agent harness. Your files stay focused on the work product, while the commit notes hold the surrounding execution context.

This example stores the user prompt and the assistant summary on the current commit, then reads the note back.

Terminal window

```

git notes add -m 'user: Add a best-practices section for unique repo names.' HEAD

git notes append -m 'assistant: Added naming guidance and a code example.' HEAD

git notes show HEAD


```

If you sync repos between systems, remember that notes live on separate refs. Push and fetch `refs/notes/*` with the rest of your repo data when you want that metadata to travel with the repository.

## Partition namespaces deliberately

### Separate environments, teams, and high-rate workloads

Use namespaces to separate operating boundaries. Repo separation isolates units of work, while namespace separation isolates ownership, environments, and traffic patterns.

Do not keep every repo in one default namespace once usage grows. Split namespaces when you need clearer ownership or more room to scale within the [request rate limits](https://developers.cloudflare.com/artifacts/platform/limits/) for each namespace.

| Use case          | Example namespaces            | Why                                                                 |
| ----------------- | ----------------------------- | ------------------------------------------------------------------- |
| Environments      | staging, prod                 | Keep test traffic and production traffic separate.                  |
| Team boundaries   | sales, finance, devtools      | Keep ownership, access, and cleanup policies distinct.              |
| Traffic isolation | agents-batch, agents-realtime | Prevent one workload from consuming the limits of another workload. |

When one namespace becomes hot, shard new repos into additional namespaces instead of continuing to grow a single shared namespace.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/concepts/","name":"Concepts"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/concepts/best-practices/","name":"Best practices for Artifacts"}}]}
```

---

---
title: How Artifacts works
description: Understand namespaces, repos, and durability.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/concepts/how-artifacts-works.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# How Artifacts works

Artifacts creates Git repos on demand. Each repo is an isolated service with its own identity, access model, and durable state.

Each Artifacts repo is its own independent Git server, with its own remote URL, tokens, and durable state.

## Core model

Namespaces are the top-level container for repos. A repo lives inside one namespace, and its name is unique within that namespace.

A namespace provides the naming and routing boundary for repos. Together, the namespace and repo name form the repo's stable address, and API responses also return a repo ID.

Like [Durable Objects](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/), a repo is a single logical instance that Cloudflare can route to from any region.

Because each repo is isolated, it has its own:

* Git history and refs
* access tokens and remote URL
* lifecycle and durable state

Repos can be created as needed. This lets Artifacts model many small units of work across separate repos.

Forking follows the same model. A fork creates a new repo that starts from an existing repo's history, then diverges independently with its own tokens, routing, and lifecycle.

Access is also repo-scoped. Each repo has its own tokens, and each token can be limited to a specific level of access:

* `read` for clone, fetch, pull, indexing, and review
* `write` for push and other mutations

Your Worker or API layer decides when to mint those tokens. That keeps authentication and authorization outside the repo while still making the repo usable from Workers, the REST API, or any standard Git client.

## Durability

Artifacts is durable by default. A repo does not depend on one process staying alive or on one data center staying available.

Behind the scenes, Cloudflare replicates repo data synchronously across multiple data centers and copies it asynchronously to object storage and snapshots. You do not need to build your own replication, failover, or snapshot pipeline to keep repository state available.

Artifacts handles the Git server lifecycle and storage infrastructure underneath these Git workflows.

## Learn more

For repo patterns, refer to [Best practices for Artifacts](https://developers.cloudflare.com/artifacts/concepts/best-practices/). For token behavior, refer to [Git protocol](https://developers.cloudflare.com/artifacts/api/git-protocol/). For product updates, refer to the [Artifacts changelog](https://developers.cloudflare.com/artifacts/platform/changelog/).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/concepts/","name":"Concepts"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/concepts/how-artifacts-works/","name":"How Artifacts works"}}]}
```

---

---
title: Git client
description: Example Artifacts integration with a Git client.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/examples/git-client.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Git client

Use this pattern when you want to discover a repo over the REST API and then hand the returned HTTPS remote to a standard Git client.

This example assumes the repo already exists and that you have a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with **Artifacts** \> **Edit**.

## Fetch the remote and clone the repo

First, fetch the repo metadata from the REST API. Then mint a read token for that repo and use the returned remote with `git clone`.

The example below uses `jq` to extract fields from the JSON responses.

Terminal window

```

export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"

export ARTIFACTS_NAMESPACE="default"

export ARTIFACTS_REPO="starter-repo"

export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"

export ARTIFACTS_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE"


REPO_JSON=$(curl --silent "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN")


ARTIFACTS_REMOTE=$(printf '%s' "$REPO_JSON" | jq -r '.result.remote')


TOKEN_JSON=$(curl --silent "$ARTIFACTS_BASE_URL/tokens" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data "{\"repo\":\"$ARTIFACTS_REPO\",\"scope\":\"read\",\"ttl\":3600}")


ARTIFACTS_TOKEN=$(printf '%s' "$TOKEN_JSON" | jq -r '.result.plaintext')


git -c http.extraHeader="Authorization: Bearer $ARTIFACTS_TOKEN" clone "$ARTIFACTS_REMOTE" artifacts-clone


```

Explain Code

This flow is useful when another system owns repo discovery or access control, but your local tooling still expects a normal git remote.

Treat `ARTIFACTS_TOKEN` as a secret. Keep it out of logs, and prefer `http.extraHeader` over saving credentials in a remote URL.

If you need a self-contained remote URL for a short-lived workflow, extract the token secret and build the authenticated remote only for that command:

Terminal window

```

ARTIFACTS_TOKEN_SECRET="${ARTIFACTS_TOKEN%%\?expires=*}"

ARTIFACTS_AUTH_REMOTE="https://x:${ARTIFACTS_TOKEN_SECRET}@${ARTIFACTS_REMOTE#https://}"


git clone "$ARTIFACTS_AUTH_REMOTE" artifacts-clone


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/examples/git-client/","name":"Git client"}}]}
```

---

---
title: isomorphic-git
description: Push commits to Artifacts repos from Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/examples/isomorphic-git.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# isomorphic-git

Use [isomorphic-git ↗](https://isomorphic-git.org/) in a Cloudflare Worker when you need Git operations without a Git binary.

This works with Artifacts because Artifacts exposes standard Git smart HTTP remotes. In Workers, pair `isomorphic-git/http/web` with a small in-memory filesystem because the runtime does not expose a local disk.

## Install the dependency

Install `isomorphic-git` in your Worker project:

 npm  yarn  pnpm  bun 

```
npm i isomorphic-git
```

```
yarn add isomorphic-git
```

```
pnpm add isomorphic-git
```

```
bun add isomorphic-git
```

## Example

This demo creates a new repo on each request, writes two files, commits them, and pushes `main` to the new remote.

Use this as a reference for the end-to-end flow. In a production Worker, look up or reuse an existing repo instead of creating a new one for every request.

Protect write-capable routes

This example omits authentication so it can focus on the Git flow. In production, authorize the caller before creating repos or granting write capability.

* [  JavaScript ](#tab-panel-5275)
* [  TypeScript ](#tab-panel-5276)

src/index.js

```

import git from "isomorphic-git";

import http from "isomorphic-git/http/web";

import { MemoryFS } from "./memory-fs";


export default {

  async fetch(_request, env) {

    const repoName = `worker-demo-${crypto.randomUUID().slice(0, 8)}`;

    const created = await env.ARTIFACTS.create(repoName);


    // Artifacts returns art_v1_<secret>?expires=<unix_seconds>.

    // For Git Basic auth, pass only the secret as the password.

    const tokenSecret = created.token.split("?expires=")[0];

    const dir = "/workspace";

    const fs = new MemoryFS();


    await git.init({ fs, dir, defaultBranch: "main" });


    await fs.promises.writeFile(

      `${dir}/README.md`,

      "# Artifacts repo created from a Worker\n",

    );

    await fs.promises.writeFile(

      `${dir}/src/index.ts`,

      'export const message = "hello from Artifacts";\n',

    );


    await git.add({ fs, dir, filepath: "README.md" });

    await git.add({ fs, dir, filepath: "src/index.ts" });


    const commit = await git.commit({

      fs,

      dir,

      message: "Create starter files",

      author: {

        name: "Artifacts example",

        email: "artifacts@example.com",

      },

    });


    const push = await git.push({

      fs,

      http,

      dir,

      url: created.remote,

      ref: "main",

      onAuth: () => ({

        username: "x",

        password: tokenSecret,

      }),

    });


    return Response.json({

      repo: created.name,

      remote: created.remote,

      commit,

      refs: push.refs,

    });

  },

};


```

Explain Code

src/index.ts

```

import git from "isomorphic-git";

import http from "isomorphic-git/http/web";

import { MemoryFS } from "./memory-fs";


export interface Env {

  ARTIFACTS: Artifacts;

}


export default {

  async fetch(_request: Request, env: Env) {

    const repoName = `worker-demo-${crypto.randomUUID().slice(0, 8)}`;

    const created = await env.ARTIFACTS.create(repoName);


    // Artifacts returns art_v1_<secret>?expires=<unix_seconds>.

    // For Git Basic auth, pass only the secret as the password.

    const tokenSecret = created.token.split("?expires=")[0];

    const dir = "/workspace";

    const fs = new MemoryFS();


    await git.init({ fs, dir, defaultBranch: "main" });


    await fs.promises.writeFile(

      `${dir}/README.md`,

      "# Artifacts repo created from a Worker\n",

    );

    await fs.promises.writeFile(

      `${dir}/src/index.ts`,

      'export const message = "hello from Artifacts";\n',

    );


    await git.add({ fs, dir, filepath: "README.md" });

    await git.add({ fs, dir, filepath: "src/index.ts" });


    const commit = await git.commit({

      fs,

      dir,

      message: "Create starter files",

      author: {

        name: "Artifacts example",

        email: "artifacts@example.com",

      },

    });


    const push = await git.push({

      fs,

      http,

      dir,

      url: created.remote,

      ref: "main",

      onAuth: () => ({

        username: "x",

        password: tokenSecret,

      }),

    });


    return Response.json({

      repo: created.name,

      remote: created.remote,

      commit,

      refs: push.refs,

    });

  },

} satisfies ExportedHandler<Env>;


```

Explain Code

In-memory filesystem helper

Use this helper with `isomorphic-git` in Workers when you need a short-lived working tree in memory.

* [  JavaScript ](#tab-panel-5277)
* [  TypeScript ](#tab-panel-5278)

src/memory-fs.js

```

class MemoryStats {

  entry;


  constructor(entry) {

    this.entry = entry;

  }


  get size() {

    return this.entry.kind === "file" ? this.entry.data.byteLength : 0;

  }


  get mtimeMs() {

    return this.entry.mtimeMs;

  }


  get ctimeMs() {

    return this.entry.mtimeMs;

  }


  get mode() {

    return this.entry.kind === "file" ? 0o100644 : 0o040000;

  }


  isFile() {

    return this.entry.kind === "file";

  }


  isDirectory() {

    return this.entry.kind === "dir";

  }


  isSymbolicLink() {

    return false;

  }

}


export class MemoryFS {

  encoder = new TextEncoder();

  decoder = new TextDecoder();

  entries = new Map([

    ["/", { kind: "dir", children: new Set(), mtimeMs: Date.now() }],

  ]);


  promises = {

    readFile: this.readFile.bind(this),

    writeFile: this.writeFile.bind(this),

    unlink: this.unlink.bind(this),

    readdir: this.readdir.bind(this),

    mkdir: this.mkdir.bind(this),

    rmdir: this.rmdir.bind(this),

    stat: this.stat.bind(this),

    lstat: this.lstat.bind(this),

  };


  normalize(input) {

    const segments = [];


    for (const part of input.split("/")) {

      if (!part || part === ".") {

        continue;

      }


      if (part === "..") {

        segments.pop();

        continue;

      }


      segments.push(part);

    }


    return `/${segments.join("/")}` || "/";

  }


  parent(path) {

    const normalized = this.normalize(path);

    if (normalized === "/") {

      return "/";

    }


    const parts = normalized.split("/").filter(Boolean);

    parts.pop();

    return parts.length ? `/${parts.join("/")}` : "/";

  }


  basename(path) {

    return this.normalize(path).split("/").filter(Boolean).pop() ?? "";

  }


  getEntry(path) {

    return this.entries.get(this.normalize(path));

  }


  requireEntry(path) {

    const entry = this.getEntry(path);

    if (!entry) {

      throw new Error(`ENOENT: ${path}`);

    }


    return entry;

  }


  requireDir(path) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "dir") {

      throw new Error(`ENOTDIR: ${path}`);

    }


    return entry;

  }


  async mkdir(path, options) {

    const target = this.normalize(path);

    if (target === "/") {

      return;

    }


    const recursive =

      typeof options === "object" && options !== null && options.recursive;

    const parent = this.parent(target);


    if (!this.entries.has(parent)) {

      if (!recursive) {

        throw new Error(`ENOENT: ${parent}`);

      }


      await this.mkdir(parent, { recursive: true });

    }


    if (this.entries.has(target)) {

      return;

    }


    this.entries.set(target, {

      kind: "dir",

      children: new Set(),

      mtimeMs: Date.now(),

    });


    this.requireDir(parent).children.add(this.basename(target));

  }


  async writeFile(path, data) {

    const target = this.normalize(path);

    await this.mkdir(this.parent(target), { recursive: true });


    const bytes =

      typeof data === "string"

        ? this.encoder.encode(data)

        : data instanceof Uint8Array

          ? data

          : new Uint8Array(data);


    this.entries.set(target, {

      kind: "file",

      data: bytes,

      mtimeMs: Date.now(),

    });


    this.requireDir(this.parent(target)).children.add(this.basename(target));

  }


  async readFile(path, options) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    const encoding = typeof options === "string" ? options : options?.encoding;

    return encoding ? this.decoder.decode(entry.data) : entry.data;

  }


  async readdir(path) {

    return [...this.requireDir(path).children].sort();

  }


  async unlink(path) {

    const target = this.normalize(path);

    const entry = this.requireEntry(target);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async rmdir(path) {

    const target = this.normalize(path);

    const entry = this.requireDir(target);

    if (entry.children.size > 0) {

      throw new Error(`ENOTEMPTY: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async stat(path) {

    return new MemoryStats(this.requireEntry(path));

  }


  async lstat(path) {

    return this.stat(path);

  }

}


```

Explain Code

src/memory-fs.ts

```

type Entry =

  | {

      kind: "dir";

      children: Set<string>;

      mtimeMs: number;

    }

  | {

      kind: "file";

      data: Uint8Array;

      mtimeMs: number;

    };


class MemoryStats {

  entry: Entry;


  constructor(entry: Entry) {

    this.entry = entry;

  }


  get size() {

    return this.entry.kind === "file" ? this.entry.data.byteLength : 0;

  }


  get mtimeMs() {

    return this.entry.mtimeMs;

  }


  get ctimeMs() {

    return this.entry.mtimeMs;

  }


  get mode() {

    return this.entry.kind === "file" ? 0o100644 : 0o040000;

  }


  isFile() {

    return this.entry.kind === "file";

  }


  isDirectory() {

    return this.entry.kind === "dir";

  }


  isSymbolicLink() {

    return false;

  }

}


export class MemoryFS {

  encoder = new TextEncoder();

  decoder = new TextDecoder();

  entries = new Map<string, Entry>([

    ["/", { kind: "dir", children: new Set(), mtimeMs: Date.now() }],

  ]);


  promises = {

    readFile: this.readFile.bind(this),

    writeFile: this.writeFile.bind(this),

    unlink: this.unlink.bind(this),

    readdir: this.readdir.bind(this),

    mkdir: this.mkdir.bind(this),

    rmdir: this.rmdir.bind(this),

    stat: this.stat.bind(this),

    lstat: this.lstat.bind(this),

  };


  normalize(input: string) {

    const segments: string[] = [];


    for (const part of input.split("/")) {

      if (!part || part === ".") {

        continue;

      }


      if (part === "..") {

        segments.pop();

        continue;

      }


      segments.push(part);

    }


    return `/${segments.join("/")}` || "/";

  }


  parent(path: string) {

    const normalized = this.normalize(path);

    if (normalized === "/") {

      return "/";

    }


    const parts = normalized.split("/").filter(Boolean);

    parts.pop();

    return parts.length ? `/${parts.join("/")}` : "/";

  }


  basename(path: string) {

    return this.normalize(path).split("/").filter(Boolean).pop() ?? "";

  }


  getEntry(path: string) {

    return this.entries.get(this.normalize(path));

  }


  requireEntry(path: string) {

    const entry = this.getEntry(path);

    if (!entry) {

      throw new Error(`ENOENT: ${path}`);

    }


    return entry;

  }


  requireDir(path: string) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "dir") {

      throw new Error(`ENOTDIR: ${path}`);

    }


    return entry;

  }


  async mkdir(path: string, options?: { recursive?: boolean } | number) {

    const target = this.normalize(path);

    if (target === "/") {

      return;

    }


    const recursive =

      typeof options === "object" && options !== null && options.recursive;

    const parent = this.parent(target);


    if (!this.entries.has(parent)) {

      if (!recursive) {

        throw new Error(`ENOENT: ${parent}`);

      }


      await this.mkdir(parent, { recursive: true });

    }


    if (this.entries.has(target)) {

      return;

    }


    this.entries.set(target, {

      kind: "dir",

      children: new Set(),

      mtimeMs: Date.now(),

    });


    this.requireDir(parent).children.add(this.basename(target));

  }


  async writeFile(path: string, data: string | Uint8Array | ArrayBuffer) {

    const target = this.normalize(path);

    await this.mkdir(this.parent(target), { recursive: true });


    const bytes =

      typeof data === "string"

        ? this.encoder.encode(data)

        : data instanceof Uint8Array

          ? data

          : new Uint8Array(data);


    this.entries.set(target, {

      kind: "file",

      data: bytes,

      mtimeMs: Date.now(),

    });


    this.requireDir(this.parent(target)).children.add(this.basename(target));

  }


  async readFile(path: string, options?: string | { encoding?: string }) {

    const entry = this.requireEntry(path);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    const encoding = typeof options === "string" ? options : options?.encoding;

    return encoding ? this.decoder.decode(entry.data) : entry.data;

  }


  async readdir(path: string) {

    return [...this.requireDir(path).children].sort();

  }


  async unlink(path: string) {

    const target = this.normalize(path);

    const entry = this.requireEntry(target);

    if (entry.kind !== "file") {

      throw new Error(`EISDIR: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async rmdir(path: string) {

    const target = this.normalize(path);

    const entry = this.requireDir(target);

    if (entry.children.size > 0) {

      throw new Error(`ENOTEMPTY: ${path}`);

    }


    this.entries.delete(target);

    this.requireDir(this.parent(target)).children.delete(this.basename(target));

  }


  async stat(path: string) {

    return new MemoryStats(this.requireEntry(path));

  }


  async lstat(path: string) {

    return this.stat(path);

  }

}


```

Explain Code

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/examples/isomorphic-git/","name":"isomorphic-git"}}]}
```

---

---
title: Sandbox SDK + Artifacts
description: Connect a sandbox to an Artifacts repo.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/examples/sandbox-sdk-artifacts.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Sandbox SDK + Artifacts

This example uses the `git-repo-per-sandbox` Sandbox SDK template and highlights the Artifacts-specific pieces.

Start from the template with `create cloudflare`, as shown in [Run Claude Code on a Sandbox](https://developers.cloudflare.com/sandbox/tutorials/claude-code/#1-create-your-project). Then adapt the Artifacts flow with the focused snippets below.

* Creates or reuses a sandbox by ID.
* Creates or reuses an Artifacts repo with the same ID.
* Passes an authenticated Git remote into the sandbox as `ARTIFACTS_GIT_REMOTE`.

## Create your project

 npm  yarn  pnpm 

```
npm create cloudflare@latest -- repo-per-sandbox --template=cloudflare/sandbox-sdk/examples/git-repo-per-sandbox
```

```
yarn create cloudflare repo-per-sandbox --template=cloudflare/sandbox-sdk/examples/git-repo-per-sandbox
```

```
pnpm create cloudflare@latest repo-per-sandbox --template=cloudflare/sandbox-sdk/examples/git-repo-per-sandbox
```

Terminal window

```

cd repo-per-sandbox


```

## 1\. Create or reuse the repo

The template keeps one Artifacts repo per sandbox ID:

* [  JavaScript ](#tab-panel-5283)
* [  TypeScript ](#tab-panel-5284)

src/index.js

```

let defaultBranch;

let remote;

let token;


try {

  const repo = await env.ARTIFACTS.get(sandboxId);


  defaultBranch = repo.defaultBranch;

  remote = repo.remote;

  token = (await repo.createToken("write", 3600)).plaintext;

} catch {

  // Repo doesn't exist yet — create it.

  const created = await env.ARTIFACTS.create(sandboxId);


  defaultBranch = created.defaultBranch;

  remote = created.remote;

  token = created.token;

}


```

Explain Code

src/index.ts

```

let defaultBranch: string;

let remote: string;

let token: string;


try {

  const repo = await env.ARTIFACTS.get(sandboxId);


  defaultBranch = repo.defaultBranch;

  remote = repo.remote;

  token = (await repo.createToken("write", 3600)).plaintext;

} catch {

  // Repo doesn't exist yet — create it.

  const created = await env.ARTIFACTS.create(sandboxId);


  defaultBranch = created.defaultBranch;

  remote = created.remote;

  token = created.token;

}


```

Explain Code

## 2\. Create or reuse the sandbox

Use the same ID for the sandbox:

* [  JavaScript ](#tab-panel-5279)
* [  TypeScript ](#tab-panel-5280)

src/index.js

```

import { getSandbox } from "@cloudflare/sandbox";


const sandbox = getSandbox(env.Sandbox, sandboxId);


```

src/index.ts

```

import { getSandbox } from "@cloudflare/sandbox";


const sandbox = getSandbox(env.Sandbox, sandboxId);


```

## 3\. Pass the repo into the sandbox

Convert the write token into an authenticated Git remote, then store it as an environment variable inside the sandbox.

Use a short-lived token and pass it into the sandbox only after the sandbox session is authorized to push changes.

* [  JavaScript ](#tab-panel-5281)
* [  TypeScript ](#tab-panel-5282)

src/index.js

```

function toAuthenticatedRemote(remote, token) {

  const tokenSecret = token.split("?expires=")[0];

  return `https://x:${tokenSecret}@${remote.slice("https://".length)}`;

}


await sandbox.setEnvVars({

  ARTIFACTS_GIT_REMOTE: toAuthenticatedRemote(remote, token),

});


```

src/index.ts

```

function toAuthenticatedRemote(remote: string, token: string) {

  const tokenSecret = token.split("?expires=")[0];

  return `https://x:${tokenSecret}@${remote.slice("https://".length)}`;

}


await sandbox.setEnvVars({

  ARTIFACTS_GIT_REMOTE: toAuthenticatedRemote(remote, token),

});


```

Code running inside the sandbox can then use `ARTIFACTS_GIT_REMOTE` with `git clone`, `git fetch`, `git pull`, or `git push`.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/examples/sandbox-sdk-artifacts/","name":"Sandbox SDK + Artifacts"}}]}
```

---

---
title: ArtifactFS
description: Mount large repos without waiting for full clones.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/guides/artifact-fs.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# ArtifactFS

ArtifactFS mounts a Git repository as a local filesystem without waiting for a full clone. It works well when your environment needs a working tree quickly and can tolerate file contents hydrating on demand.

Use ArtifactFS for large repos in sandboxes, containers, and virtual machines. For smaller repos, a regular `git clone` is usually simpler.

ArtifactFS works with [Artifacts Git remotes](https://developers.cloudflare.com/artifacts/api/git-protocol/) and other Git repositories.

## Choose ArtifactFS when

* startup time matters more than a complete local clone
* the repo is large enough that cloning slows down sandbox startup
* tools need a mounted working tree instead of direct Git access

For smaller repos, start with a regular `git clone`. It is usually fast enough and simpler to operate.

## Understand how it behaves

ArtifactFS starts with a blobless clone. It fetches commits, trees, and refs first, then mounts the working tree through FUSE.

File contents hydrate asynchronously as tools read them. Reads only block when a requested blob is not hydrated yet, and later reads come from the local blob cache.

ArtifactFS prioritizes files that usually unblock developer tools first, such as package manifests, dependency files, and common source files. Large binary assets are deprioritized.

## Mount an Artifacts repo

This example installs ArtifactFS, builds an authenticated Artifacts remote from a repo token, mounts the repo, and reads files from the mounted working tree.

This example assumes you already have a working FUSE implementation on the host and a repo-scoped Artifacts token.

Terminal window

```

go install github.com/cloudflare/artifact-fs/cmd/artifact-fs@latest


export ARTIFACTS_REMOTE="https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git"

export ARTIFACTS_TOKEN="<YOUR_READ_TOKEN>"

export ARTIFACTS_TOKEN_SECRET="${ARTIFACTS_TOKEN%%\?expires=*}"

export ARTIFACTS_AUTH_REMOTE="https://x:${ARTIFACTS_TOKEN_SECRET}@${ARTIFACTS_REMOTE#https://}"


artifact-fs add-repo \

  --name starter-repo \

  --remote "$ARTIFACTS_AUTH_REMOTE" \

  --branch main \

  --mount-root /tmp


artifact-fs daemon --root /tmp &


ls /tmp/starter-repo/

cat /tmp/starter-repo/README.md

git -C /tmp/starter-repo log --oneline -5


```

Explain Code

Use a short-lived token in the authenticated remote URL. If you need a smaller repo or a simpler local workflow, use a normal [Git protocol](https://developers.cloudflare.com/artifacts/api/git-protocol/) clone instead.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/guides/","name":"Guides"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/guides/artifact-fs/","name":"ArtifactFS"}}]}
```

---

---
title: Authentication
description: Choose auth for bindings, API calls, and Git.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/guides/authentication.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Authentication

Artifacts uses a different authentication path for each interface. Choose auth based on how your code reaches the repo.

## Compare auth methods

| Interface       | Authenticate with                                 | Permissions or scopes                                                                                                       | Use for                              |
| --------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| Workers binding | Wrangler OAuth or a Cloudflare API token          | wrangler login requests artifacts:write by default. API tokens need **Artifacts** \> **Read** or **Artifacts** \> **Edit**. | Worker code that calls env.ARTIFACTS |
| REST API        | Cloudflare API token in Authorization: Bearer ... | **Artifacts** \> **Read** for read routes, **Artifacts** \> **Edit** for create, import, delete, and token routes           | Control-plane HTTP requests          |
| Git protocol    | Repo-scoped Artifacts token                       | read for clone, fetch, and pull. write for push.                                                                            | Standard Git over HTTPS              |

Cloudflare API tokens authenticate control-plane access. Repo-scoped Artifacts tokens authenticate Git access.

## Authenticate the Workers binding

The Workers binding uses your Wrangler authentication. Your Worker code does not pass a token when it calls `env.ARTIFACTS`.

Add the binding in your Wrangler config:

* [  wrangler.jsonc ](#tab-panel-5293)
* [  wrangler.toml ](#tab-panel-5294)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "name": "artifacts-worker",

  "main": "src/index.ts",

  // Set this to today's date

  "compatibility_date": "2026-04-17",

  "artifacts": [

    {

      "binding": "ARTIFACTS",

      "namespace": "default"

    }

  ]

}


```

Explain Code

TOML

```

name = "artifacts-worker"

main = "src/index.ts"

# Set this to today's date

compatibility_date = "2026-04-17"


[[artifacts]]

binding = "ARTIFACTS"

namespace = "default"


```

For local development, `wrangler login` requests `artifacts:write` by default. For CI or other headless environments, use a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) scoped to the target account:

* **Artifacts** \> **Read** for read-only operations
* **Artifacts** \> **Edit** for create, update, delete, fork, or token-minting operations

Terminal window

```

export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"

export CLOUDFLARE_ACCOUNT_ID="<YOUR_ACCOUNT_ID>"


```

## Authenticate the REST API

The REST API uses a Cloudflare API token in the `Authorization` header. Use **Artifacts** \> **Read** for read routes, and **Artifacts** \> **Edit** for routes that change repo state.

Terminal window

```

export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"

export ARTIFACTS_NAMESPACE="default"

export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"

export ARTIFACTS_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE"


```

Read repo metadata with a read-capable token:

Terminal window

```

curl "$ARTIFACTS_BASE_URL/repos/starter-repo" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN"


```

Create a repo with an edit-capable token:

Terminal window

```

curl --request POST "$ARTIFACTS_BASE_URL/repos" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data '{

    "name": "starter-repo"

  }'


```

## Authenticate the Git protocol

Git uses repo-scoped Artifacts tokens, not Cloudflare API tokens. Mint these tokens from the Workers binding or the REST API, then use them with the repo `remote` URL.

| Token scope | Allowed commands                         |
| ----------- | ---------------------------------------- |
| read        | git clone, git fetch, git pull           |
| write       | git clone, git fetch, git pull, git push |

Use a read token to clone:

Terminal window

```

git -c http.extraHeader="Authorization: Bearer <YOUR_READ_TOKEN>" clone "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git" artifacts-clone


```

Use a write token to push:

Terminal window

```

git -c http.extraHeader="Authorization: Bearer <YOUR_WRITE_TOKEN>" push "https://<ACCOUNT_ID>.artifacts.cloudflare.net/git/default/starter-repo.git" HEAD:main


```

For more information on token handling and authenticated remotes, refer to [Git protocol](https://developers.cloudflare.com/artifacts/api/git-protocol/).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/guides/","name":"Guides"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/guides/authentication/","name":"Authentication"}}]}
```

---

---
title: Import repositories
description: Import existing Git repos into Artifacts.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/guides/import-repositories.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Import repositories

Import an existing repository when you already have a baseline outside Artifacts and want to start using it as an Artifacts repo.

This works well for:

* a baseline repo that agents fork from
* a template repo for new sessions or users
* a shared prompts or configuration repo used across workflows

Artifacts imports public HTTPS remotes through the [REST API](https://developers.cloudflare.com/artifacts/api/rest-api/#import-a-public-https-remote) or the [Workers binding](https://developers.cloudflare.com/artifacts/api/workers-binding/#importparams). After import, the repo has a normal Artifacts remote URL and can be cloned, forked, or issued repo-scoped tokens like any other repo.

## Import a repo from GitHub

This example imports a public GitHub repo into the `default` namespace.

Use a [Cloudflare API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with **Artifacts** \> **Edit**.

Terminal window

```

export ACCOUNT_ID="<YOUR_ACCOUNT_ID>"

export ARTIFACTS_NAMESPACE="default"

export ARTIFACTS_REPO="workers-sdk-baseline"

export CLOUDFLARE_API_TOKEN="<YOUR_API_TOKEN>"

export ARTIFACTS_BASE_URL="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/artifacts/namespaces/$ARTIFACTS_NAMESPACE"


```

Terminal window

```

IMPORT_RESPONSE=$(curl --silent --request POST "$ARTIFACTS_BASE_URL/repos/$ARTIFACTS_REPO/import" \

  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \

  --header "Content-Type: application/json" \

  --data '{

    "url": "https://github.com/cloudflare/workers-sdk",

    "branch": "main",

    "depth": 100

  }')


printf '%s\n' "$IMPORT_RESPONSE"


```

Explain Code

The response includes the new Artifacts repo metadata, including `result.remote` and an initial repo token.

## Use the imported repo

After import, use the repo like any other Artifacts repo.

* Keep it as a stable baseline and fork from it for agent work
* clone it with a repo-scoped token for direct Git access
* mark it read-only if you want a fixed template repo

For the endpoint details, refer to [REST API](https://developers.cloudflare.com/artifacts/api/rest-api/#import-a-public-https-remote). For auth details, refer to [Authentication](https://developers.cloudflare.com/artifacts/guides/authentication/).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/guides/","name":"Guides"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/guides/import-repositories/","name":"Import repositories"}}]}
```

---

---
title: Metrics
description: Review the metrics exposed by Artifacts.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/observability/metrics.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Metrics

Artifacts exposes analytics that let you inspect repo activity, errors, and operation duration across your account.

Artifacts metrics are available through Cloudflare's [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/). You can use them to answer questions like which repos are busiest, where errors cluster, and how long operations take.

## Metrics

Artifacts currently exports the `artifactsEventsAdaptiveGroups` GraphQL dataset.

| Metric           | GraphQL field            | Description                                                                                                |
| ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| Operations       | count                    | Total number of Artifacts events that match the query filter. This includes successful actions and errors. |
| Total duration   | sum.durationMs           | Total time spent handling matching Artifacts operations, in milliseconds.                                  |
| Average duration | avg.durationMs           | Average time per matching operation, in milliseconds.                                                      |
| Duration p25     | quantiles.durationMsP25  | 25th percentile operation duration, in milliseconds.                                                       |
| Duration p50     | quantiles.durationMsP50  | Median operation duration, in milliseconds.                                                                |
| Duration p75     | quantiles.durationMsP75  | 75th percentile operation duration, in milliseconds.                                                       |
| Duration p90     | quantiles.durationMsP90  | 90th percentile operation duration, in milliseconds.                                                       |
| Duration p95     | quantiles.durationMsP95  | 95th percentile operation duration, in milliseconds.                                                       |
| Duration p99     | quantiles.durationMsP99  | 99th percentile operation duration, in milliseconds.                                                       |
| Duration p999    | quantiles.durationMsP999 | 99.9th percentile operation duration, in milliseconds.                                                     |

Metrics can be queried for the past 31 days. Queries require an `accountTag` filter with your Cloudflare account ID.

## Dimensions

Use these dimensions to filter or group results:

| Dimension              | Description                                                                            |
| ---------------------- | -------------------------------------------------------------------------------------- |
| repository             | Fully qualified repo path in the form namespace/name.                                  |
| repositoryNamespace    | Namespace that contains the repo.                                                      |
| repositoryName         | Repo name inside the namespace.                                                        |
| eventKind              | Top-level event category. Use action for successful operations and error for failures. |
| eventType              | Specific operation or error type.                                                      |
| errorMessage           | Error message for failed operations.                                                   |
| date                   | Calendar date of the event.                                                            |
| datetime               | Exact event timestamp.                                                                 |
| datetimeMinute         | Event time truncated to the minute.                                                    |
| datetimeFiveMinutes    | Event time truncated to five-minute windows.                                           |
| datetimeFifteenMinutes | Event time truncated to fifteen-minute windows.                                        |
| datetimeHour           | Event time truncated to the hour.                                                      |
| datetimeSixHours       | Event time truncated to six-hour windows.                                              |

## Event types

Artifacts currently emits these values in `eventType`:

| Event type          | Kind   | Description                                        |
| ------------------- | ------ | -------------------------------------------------- |
| create              | action | A repo was created.                                |
| fork                | action | A repo was forked.                                 |
| push                | action | A client pushed data to a repo.                    |
| pull                | action | A client fetched or cloned data from a repo.       |
| delete              | action | A repo was deleted.                                |
| storageLimitReached | error  | An operation hit a storage limit condition.        |
| serverError         | error  | The service failed while handling the request.     |
| clientError         | error  | The client sent an invalid or unsupported request. |
| rateLimited         | error  | The request was rejected by a rate limiter.        |

## Example GraphQL queries

You can query Artifacts analytics with the [GraphQL Analytics API](https://developers.cloudflare.com/analytics/graphql-api/). All examples on this page use the `artifactsEventsAdaptiveGroups` dataset.

### Operations by repo within a namespace

Use this query to find the busiest repos in one namespace over a time range. It also returns average operation duration so you can compare activity and latency together.

```

query ArtifactsOperationsByRepo(

  $accountTag: string!

  $datetimeStart: Time

  $datetimeEnd: Time

  $repositoryNamespace: string!

) {

  viewer {

    accounts(filter: { accountTag: $accountTag }) {

      artifactsEventsAdaptiveGroups(

        limit: 100

        filter: {

          datetime_geq: $datetimeStart

          datetime_leq: $datetimeEnd

          repositoryNamespace: $repositoryNamespace

          eventKind: "action"

        }

        orderBy: [count_DESC]

      ) {

        count

        avg {

          durationMs

        }

        dimensions {

          repositoryName

        }

      }

    }

  }

}


```

Explain Code

### Errors by repo, descending

Use this query to rank repos by error volume. It helps you spot which repos fail most often and which error types are driving those failures.

```

query ArtifactsErrorsByRepo(

  $accountTag: string!

  $datetimeStart: Time

  $datetimeEnd: Time

) {

  viewer {

    accounts(filter: { accountTag: $accountTag }) {

      artifactsEventsAdaptiveGroups(

        limit: 100

        filter: {

          datetime_geq: $datetimeStart

          datetime_leq: $datetimeEnd

          eventKind: "error"

        }

        orderBy: [count_DESC]

      ) {

        count

        dimensions {

          repository

          eventType

        }

      }

    }

  }

}


```

Explain Code

### Repos by pushes, descending

Use this query to see which repos receive the most pushes in a time window. It is useful for identifying active write-heavy repos across an account.

```

query ArtifactsPushesByRepo(

  $accountTag: string!

  $datetimeStart: Time

  $datetimeEnd: Time

) {

  viewer {

    accounts(filter: { accountTag: $accountTag }) {

      artifactsEventsAdaptiveGroups(

        limit: 100

        filter: {

          datetime_geq: $datetimeStart

          datetime_leq: $datetimeEnd

          eventKind: "action"

          eventType: "push"

        }

        orderBy: [count_DESC]

      ) {

        count

        dimensions {

          repository

        }

      }

    }

  }

}


```

Explain Code

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/observability/","name":"Observability"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/observability/metrics/","name":"Metrics"}}]}
```

---

---
title: Changelog
description: Review recent changes to Artifacts.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/platform/changelog.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Changelog

[ Subscribe to RSS ](https://developers.cloudflare.com/changelog/rss/artifacts.xml) 

## 2026-04-16

  
**Artifacts now in beta: versioned filesystem with Git access**   

[Artifacts](https://developers.cloudflare.com/artifacts/) is now in private beta. Artifacts is Git-compatible storage built for scale: create tens of millions of repos, fork from any remote, and hand off a URL to any Git client. It provides a versioned filesystem for storing and exchanging file trees across Workers, the REST API, and any Git client, running locally or within an agent.

You can [read the announcement blog ↗](https://blog.cloudflare.com/artifacts-git-for-agents-beta/) to learn more about what Artifacts does, how it works, and how to create repositories for your agents to use.

Artifacts has three API surfaces:

* Workers bindings (for creating and managing repositories)
* REST API (for creating and managing repos from any other compute platform)
* Git protocol (for interacting with repos)

As an example: you can use the Workers binding to create a repo and read back its remote URL:

TypeScript

```

# Create a thousand, a million or ten million repos: one for every agent, for every upstream branch, or every user.

const created = await env.PROD_ARTIFACTS.create("agent-007");

const remote = (await created.repo.info())?.remote;


```

Or, use the REST API to create a repo inside a namespace from your agent(s) running on any platform:

Terminal window

```

curl --request POST "https://artifacts.cloudflare.net/v1/api/namespaces/some-namespace/repos" --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" --header "Content-Type: application/json" --data '{"name":"agent-007"}'


```

Any Git client that speaks smart HTTP can use the returned remote URL:

Terminal window

```

# Agents know git.

# Every repository can act as a git repo, allowing agents to interact with Artifacts the way they know best: using the git CLI.

git clone https://x:${REPO_TOKEN}@artifacts.cloudflare.net/some-namespace/agent-007.git


```

To learn more, refer to [Get started](https://developers.cloudflare.com/artifacts/get-started/), [Workers binding](https://developers.cloudflare.com/artifacts/api/workers-binding/), and [Git protocol](https://developers.cloudflare.com/artifacts/api/git-protocol/).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/platform/changelog/","name":"Changelog"}}]}
```

---

---
title: Limits
description: Review Artifacts platform limits.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/platform/limits.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Limits

Limits that apply to creating, importing, cloning, and pushing Artifacts are detailed below.

These limits cover naming rules and request rates for control-plane and git operations.

| Feature                          | Limit                                                    |
| -------------------------------- | -------------------------------------------------------- |
| Control-plane request rate       | 2,000 requests per 10 seconds                            |
| Git request rate (per namespace) | 2,000 requests per 10 seconds                            |
| Maximum storage per repository   | 10 GB                                                    |
| Maximum storage per account      | 1 TB (can be raised on request)                          |
| Maximum number of repositories   | Unlimited                                                |
| Maximum number of namespaces     | Unlimited                                                |
| Namespace and repo names         | 3-63 characters, lowercase only, a-z and hyphens allowed |
| Maximum name length              | 63 characters                                            |

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/platform/limits/","name":"Limits"}}]}
```

---

---
title: Pricing
description: Review Artifacts pricing information.
image: https://developers.cloudflare.com/dev-products-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/artifacts/platform/pricing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Pricing

Artifacts pricing is billed on two dimensions:

* **Operations**: the number of repo operations, such as `create`, `push`, `pull`, and `clone`.
* **Storage**: the total amount of stored data, measured in gigabyte-months (`GB-mo`).

## Artifacts pricing

| Unit                          | Workers Free | Workers Paid                                                   |
| ----------------------------- | ------------ | -------------------------------------------------------------- |
| Operations (1,000 operations) | Unavailable  | First 10,000 per month + $0.15 per additional 1,000 operations |
| Storage (GB-mo)               | Unavailable  | First 1 GB per month + $0.50 per additional GB-mo              |

## Storage usage

Storage is billed using gigabyte-month (`GB-mo`) as the billing metric, identical to [Durable Objects SQL storage](https://developers.cloudflare.com/durable-objects/platform/pricing/#sqlite-storage-backend). A `GB-mo` is calculated by averaging peak storage per day over a 30-day billing period.

* Storage is calculated across all repositories.
* Replicas do not add storage charges. Storage is replicated by default, and you do not need to manage repository availability or uptime.
* Repos remain stored until you explicitly delete them.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/artifacts/","name":"Artifacts"}},{"@type":"ListItem","position":3,"item":{"@id":"/artifacts/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/artifacts/platform/pricing/","name":"Pricing"}}]}
```
