PHPackages                             thoughtco/statamic-cloudflare-images - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. thoughtco/statamic-cloudflare-images

ActiveLibrary

thoughtco/statamic-cloudflare-images
====================================

A {{ cfimage }} Statamic tag that serves and transforms images via a Cloudflare Worker (R2 + Workers Images binding) instead of Glide/GD on the app server.

v0.1.0(today)02↑2900%PHPPHP ^8.3CI passing

Since Jul 28Pushed todayCompare

[ Source](https://github.com/thoughtco/statamic-cloudflare-images)[ Packagist](https://packagist.org/packages/thoughtco/statamic-cloudflare-images)[ RSS](/packages/thoughtco-statamic-cloudflare-images/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (7)Versions (2)Used By (0)

Statamic Cloudflare Images
==========================

[](#statamic-cloudflare-images)

A `{{ cfimage }}` Statamic tag, shaped to feel like `{{ glide }}`, that serves and transforms images through a Cloudflare Worker (R2 binding + Workers Images binding) instead of Glide/GD on the app server. No image processing or image bytes ever pass through your Statamic site.

Why not Glide
-------------

[](#why-not-glide)

Glide (and Laravel's own `Image` facade) both do the resize/encode work on your own server, on request. This addon moves that entirely to Cloudflare's edge: the Worker reads the source straight out of R2 and transforms it there, so origin CPU and bandwidth are never in the path for image variants after the first request.

Architecture assumptions
------------------------

[](#architecture-assumptions)

This addon is built around two things being true of your infrastructure:

- **Your zone (domain) is on Cloudflare** — the Worker is deployed as a route on that zone, so Cloudflare has to be managing DNS for it.
- **Your asset originals live in Cloudflare R2** — the Worker reads the source file directly out of R2 itself. Assets stored elsewhere (S3, local/public disk, another CDN) aren't supported today.

How the pieces fit together
---------------------------

[](#how-the-pieces-fit-together)

If you haven't used Cloudflare before, three separate things need to exist before `{{ cfimage }}` will serve anything:

1. **R2** — Cloudflare's object storage. Your asset *files themselves*(originals) need to actually live here. This addon doesn't upload anything for you — you point a normal Laravel filesystem disk at R2 and Statamic uploads to it exactly as it would to local disk or S3.
2. **A Worker** — a small script Cloudflare runs at the edge, published by this package into your app under `workers/cfimage/`. It receives the signed URLs this addon builds, reads the source file out of R2, resizes it via the Workers Images binding, and returns the result.
3. **This package** — builds signed `{{ cfimage }}` URLs pointing at your Worker, and gives you `php artisan cfimage:publish` to keep the Worker's own config (`wrangler.toml`, preset table) in sync with `config/cfimage.php`so nothing is maintained in two places.

Prerequisites
-------------

[](#prerequisites)

- A Cloudflare account, with the domain you'll serve images from added to Cloudflare (i.e. Cloudflare is managing its DNS).
- **R2** enabled on the account — the R2 dashboard tab will prompt you to enable it the first time you visit, no separate paid plan required for R2 itself.
- **Cloudflare Images / Workers Images** enabled on the account — this is the part that actually resizes images at the edge, and it's a separate product from R2. Check the "Images" tab in the dashboard before your first deploy; on some accounts this requires a paid plan.

Installation
------------

[](#installation)

```
composer require thoughtco/statamic-cloudflare-images
php artisan vendor:publish --tag=cfimage-config  #this publishes config/cfimage.php
php artisan vendor:publish --tag=cfimage-worker  #this publishes workers/cfimage/
```

1. Create an R2 bucket and point a disk at it
---------------------------------------------

[](#1-create-an-r2-bucket-and-point-a-disk-at-it)

1. In the Cloudflare dashboard, go to **R2 → Create bucket** and give it a name (e.g. `my-app-assets`).
2. Create an API token scoped to R2: **R2 → Manage API tokens → Create API token**, with read/write access to that bucket. This gives you an **Access Key ID** and **Secret Access Key** — save both, the secret is only shown once.
3. Your Cloudflare **Account ID** is shown in the right-hand sidebar of almost every page in the dashboard — you'll need it for the R2 endpoint below and again later for the Worker.
4. Add an R2 disk in `config/filesystems.php`, using Laravel's S3 driver (R2 is S3-compatible): ```
    'r2' => [
        'driver' => 's3',
        'key' => env('R2_ACCESS_KEY_ID'),
        'secret' => env('R2_SECRET_ACCESS_KEY'),
        'region' => 'auto',
        'bucket' => env('R2_BUCKET'),
        'endpoint' => env('R2_ENDPOINT'), // https://.r2.cloudflarestorage.com
        'use_path_style_endpoint' => true,
    ],
    ```
5. Point a Statamic asset container (or your Spatie Media disk, etc.) at this `r2` disk. This is the disk `config('cfimage.disks')` will reference in step 2 — the addon reads existing assets from it, it doesn't migrate anything for you.

2. Configure the package
------------------------

[](#2-configure-the-package)

Open `config/cfimage.php` and set, at minimum:

- `disks` — every disk `{{ cfimage }}` may serve from, mapped to an arbitrary R2 binding name the Worker will use, e.g. `['assets' => 'ASSETS']`. Each key must be a disk that exists in `config/filesystems.php` and has a `bucket` value set (the `r2` disk from step 1, for example).
- `presets` — named shortcuts in the same shape as Statamic's Glide presets, e.g. `'thumbnail' => ['w' => 300, 'h' => 300, 'q' => 80, 'fm' => 'webp']`. You can literally copy/paste them from the assets config file.

See the comments in that file for the rest of the options.

3. Set up the Worker
--------------------

[](#3-set-up-the-worker)

From your app root, the published Worker source lives in `workers/cfimage/`.

1. **Install dependencies** (this installs `wrangler` locally — every command below runs it via `npx`, no global install needed): ```
    cd workers/cfimage
    npm install

    ```
2. **Authenticate wrangler** so it's allowed to deploy to your account: ```
    npx wrangler login

    ```

    This opens a browser to authorize against your Cloudflare account. For CI or other non-interactive environments, set a `CLOUDFLARE_API_TOKEN`environment variable instead (dashboard → **My Profile → API Tokens → Create Token**, the "Edit Cloudflare Workers" template covers it) — wrangler picks this up automatically and skips the browser flow.
3. **Point your chosen hostname at Cloudflare.** Decide on the hostname `{{ cfimage }}` URLs will use (e.g. `images.example.com`) and add a DNS record for it in the Cloudflare dashboard, **proxied** (orange cloud on). The record's target doesn't matter — the Worker's route intercepts requests to this hostname before they'd reach any actual origin. Set this hostname as `CFIMAGE_URL` in your app's `.env`(`https://images.example.com`).
4. **Fill in the Worker deploy settings** in `config/cfimage.php`'s `worker` array:
    - `zone_name` — your domain as it appears in Cloudflare (e.g. `example.com`), set via `CFIMAGE_ZONE_NAME`.
    - `account_id` — only needed if the account authenticated in step 2 has access to more than one Cloudflare account; otherwise wrangler infers it and this can stay blank. It's the Account ID from step 1.3, set via `CFIMAGE_ACCOUNT_ID`.
    - `bucket_name` values are generated automatically from `config('cfimage.disks')` and `config/filesystems.php` — nothing to fill in by hand here, but the R2 bucket(s) themselves must already exist (step 1).
5. **Generate a signing secret** — any random string works, e.g.: ```
    openssl rand -hex 32

    ```

    Set this value in **both** places, exactly matching, or every URL will 404: ```
    npx wrangler secret put CFIMAGE_SIGNING_KEY

    ```

    (run from `workers/cfimage/`) and as `CFIMAGE_SIGNING_KEY` in your app's `.env`.
6. **Generate and deploy the Worker.** From your app root: ```
    php artisan cfimage:publish --deploy

    ```

    This regenerates `wrangler.toml` and the preset table from `config/cfimage.php`, then runs `wrangler deploy`. (Run without `--deploy` to just regenerate the files without deploying.)

Regenerating wrangler.toml and the preset table
-----------------------------------------------

[](#regenerating-wranglertoml-and-the-preset-table)

`config/cfimage.php` is the single source of truth for the URL path prefix, presets, disks, and Worker deploy settings — bucket names come from `config/filesystems.php`, so nothing bucket-related needs duplicating by hand. Never hand-edit `wrangler.toml` or `src/presets.generated.js` in the published `workers/cfimage/` directory — both are fully overwritten any time you change `config/cfimage.php` and re-run:

```
php artisan cfimage:publish            # regenerate only
php artisan cfimage:publish --deploy   # regenerate + wrangler deploy

```

URL shape
---------

[](#url-shape)

The signature, preset/modifiers, and source key all live in the URL **path**, not the query string:

```
/////

```

e.g. `https://images.example.com/cfimage/assets//p_thumbnail/photo.jpg`or, without a preset, `.../w_300,h_200,fit_crop/photo.jpg`. Keeping these in the path (rather than query params) means a CDN cache rule or proxy that strips/ignores query strings can't silently drop the signature or collapse distinct variants onto one cache entry.

Every failure path (bad signature, unknown disk, unresolvable modifiers, missing source file) returns a bare `404` to the client — the specific reason is only ever logged server-side (`console.error`, visible via `wrangler tail` or Cloudflare Logs), so a prober can't use the response itself to learn which part of a forged or malformed URL is wrong.

Usage in templates
------------------

[](#usage-in-templates)

Mirrors `{{ glide }}`'s shape:

```
{{ cfimage:image preset="thumbnail" }}{{ url }}{{ /cfimage:image }}

{{ cfimage src="{ mobile_image ?? image }" preset="thumbnail" }}
```

Accepts a Statamic `Asset`, or any object exposing a public `$disk` property and a `getPathRelativeToRoot()` method (e.g. a Spatie `Media` model), or a plain path string (resolved against `cfimage.default_disk`).

Notes
-----

[](#notes)

- `dpr` is applied by multiplying the requested width/height before the resize — an approximation, not a pixel-for-pixel match of Glide's own dpr handling.

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/51899?v=4)[Ryan Mitchell](/maintainers/ryanmitchell)[@ryanmitchell](https://github.com/ryanmitchell)

---

Top Contributors

[![ryanmitchell](https://avatars.githubusercontent.com/u/51899?v=4)](https://github.com/ryanmitchell "ryanmitchell (3 commits)")

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/thoughtco-statamic-cloudflare-images/health.svg)

```
[![Health](https://phpackages.com/badges/thoughtco-statamic-cloudflare-images/health.svg)](https://phpackages.com/packages/thoughtco-statamic-cloudflare-images)
```

###  Alternatives

[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

135224.7k8](/packages/statamic-rad-pack-runway)[statamic/seo-pro

68516.6k](/packages/statamic-seo-pro)[statamic/statamic

Statamic

829179.5k](/packages/statamic-statamic)[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3518.3k](/packages/duncanmcclean-statamic-cargo)[statamic/eloquent-driver

Allows you to store Statamic data in a database.

127741.1k18](/packages/statamic-eloquent-driver)[rias/statamic-redirect

29335.6k](/packages/rias-statamic-redirect)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
