Remote Assets Local Metadata in Statamic

Published in Statamic, Git, on Aug 18, 2025

Statamic has the fantastic option of storing assets in a remote S3-like bucket right out of the box, but this comes with a pretty large caveat: performance. Let's fix that.

The Problem

Statamic's asset management relies on storing metadata in a hidden directory .meta next to the corresponding assets. This directory contains a metadata yaml file per asset in the parent directory. So the directory structure would look something like:

./public/assets/
	./.meta/
		file1.jpg.yaml
		file2.jpg.yaml
	file1.jpg.yaml
	file2.jpg.yaml

As mentioned before, Statamic then allows you to set up remote storage of these assets via S3 storage using Laravel's filesystem layer. This is ridiculously simple compared to what you'd have to do with other CMS's. However, Statamic stores the files and metadata in the exact same way as it does locally. What this means is that any time you are searching files, editing alt text/custom fields, or even just visiting a page with an image on it (requiring Glide to retrieve the metadata), Statamic is having to go crawl the remote S3 bucket for both the asset and the metadata file. With a decent amount of assets, this is ridiculously slow, and with enough assets can cause timeouts on both the CP backend and your website frontend.

Solution 1: Store metadata in the database

Spend enough time in the Statamic Discord and you will see someone come across this very issue. They have a large remote asset library and pages start timing out. The proposed solution is a relatively simple one, move the asset metadata to the first party Eloquent driver addon. The setup is as follows:

  1. Install the eloquent driver - php please install:eloquent-driver

  2. Import the asset metadata into eloquent - php please eloquent:import-assets

That's it! The Eloquent driver now stores all the asset metadata in the default Laravel database driver, and pages should feel snappy once again.

There's just one problem, one of Statamic's main advantages is that it is flat file by default. Normally this means that asset metadata is committed and synced to and from dev, staging, production. Even when you move the assets to the remote S3 storage the metadata is still accessible in all environments that are set up with that bucket. But now we've added a database to the mix, we've landed right back in the muck that so many CMS's are in. Now we have to somehow export and import and merge this database of asset metadata. Yuck.

Solution 2: Store metadata in flat files

Wait a second, you may say, I thought we just moved the asset metadata out of flat files because it was slow? Well, it was slow, but it wasn't slow due to it being in flat files; it was slow because the flat files were in a remote filesystem. So if we can figure out a way to store asset metadata locally while keeping the assets in the remote bucket that will be the winning ticket.

NOTE: Ideally this type of setup would be something that Statamic would support via a simple config flag. I have suggested it to them and submitted a PR for it. I will update this post if/when I (or they) get a PR merged.

The solution to this lies in a simple but not well-known Laravel package called Orbit. Orbit is an eloquent driver made by Ryan Chandler that takes standard Eloquent models and stores them in flat files. It supports storing models in markdown, JSON, and Statamic's favorite, YAML.

Let's walk through how to get this set up:

  1. Install the eloquent driver - php please install:eloquent-driver

  2. Create a Laravel model - php artisan make:model Asset

  3. In the config/statamic/eloquent-driver.php file change the model for the assets from \Statamic\Eloquent\Assets\AssetModel::class to \App\Models\Asset::class 

  4. Install the Orbit eloquent driver - composer require ryangjchandler/orbit@^2.0.0-beta6
    The beta version isn't required for this technique to work but the code below is under the assumption of using the reworked v2 of Orbit.

  5. Publish the Orbit config - php artisan vendor:publish --tag=orbit-config 

  6. Edit the config/orbit.php file and change the content base_path from content to content/models
    This isn't entirely necessary but it cleans up your content folder if you end up doing more Orbit models

  7. Edit the app/Models/Asset.php model and paste the following:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Schema\Blueprint;
use Orbit\Concerns\Orbital;
use Orbit\Contracts\Orbit;
use Orbit\Drivers\Yaml;
use Override;
use Statamic\Eloquent\Assets\AssetModel;

/**
 * Extending the AssetModel so we are compatible with the Statamic eloquent driver
 * Implementing the Orbit interface so Orbit knows we are compatible with it too
 */
class Asset extends AssetModel implements Orbit
{
    use Orbital;

    /**
     * Tell Orbit to store the Asset model data in yaml flat files in:
	 * content/models/{id}.yml
     */
    public function getOrbitDriver(): string
    {
        return Yaml::class;
    }

    /**
	 * This is a migration for Orbit to use on its SQLite cache layer
	 * These fields are taken from the eloquent driver migration:
	 * https://github.com/statamic/eloquent-driver/blob/master/database/migrations/2024_03_07_100000_create_asset_table.php
	 */
    public function schema(Blueprint $table): void
    {
        $table->id();
        $table->string('container')->index();
        $table->string('folder')->index();
        $table->string('basename')->index();
        $table->string('filename')->index();
        $table->char('extension', 10)->index();
        $table->string('path')->index();
        $table->jsonb('meta')->nullable();
        // Don't include timstamps here as Orbit includes them by default already
	  	// $table->timestamps();

        $table->unique(['container', 'folder', 'basename']);
    }

    /**
	 * Orbit has a bug with JSON being double encoded when using Laravel's casts:
	 * https://github.com/ryangjchandler/orbit/issues/171
     * We can remove this when the Orbit issue is resolved
	 */
    #[Override]
    public function setAttribute($key, $value)
    {
        if (in_array($key, ['meta', 'data']) && is_string($value)) {
            $value = json_decode($value);
        }

        return parent::setAttribute($key, $value);
    }
}
  1. Import the asset metadata into the eloquent - php please eloquent:import-assets 

  2. (Optional) Clear Orbit's SQLite cache on deploy by adding this to your deployment script - php artisan orbit:clear - this probably isn't necessary as Orbit does check the filesystem periodically for changes

And that's it. Asset metadata is now snappy due to it being local on the server. It is flat file so it can be tracked, synced, and updated via git. And the assets are still remote, so there is no need to worry about filling up the server. I feel like this is truly the best of all worlds.

I will leave you with one more thing to think about: Orbit behind the scenes uses an SQLite cache to store all the flat file data and make queries super snappy. What if Statamic did the same for all content? 🤯

---

EDIT 07/17/25 - Apparently I had a lot of syntax and flat out errors in the code snippet above. I also clarified the code is for Orbit v2 - although not required. Also added a notice about the orbit:clear command not being necessary.

EDIT 08/07/25 - Made some more fixes to the double json issue with Orbit. Should work smoothly now.

EDIT 08/16/25 - Added PR link and fixed some grammar