🚀 A new era: Kirby 4 Get to know
Skip to content

Content clean‑up

Kirby doesn't delete fields from content files if you later remove them from your blueprints, rename them, or if you add fields manually with an editor. Especially during development, you therefore often end up with a lot of unused stuff you will probably want to clean up before handing a project over to your client.

This script cleans up your page, file and user content files. It compares fields in the content files with those defined in the blueprint and removes all undefined fields, no matter if they contain content or not.

You can also set the fields to ignore in the $ignore array. By default, we ignore the following fields: uuid, for pages title and slug and for files template, focus and sort.

Create a new cleanup.php file in your site root and copy the script code into it.

Then run the script with http://yourdomain.com/cleanup.php. You can also run the script on the command line or put the code into a route if you cannot call a PHP script in your browser. Delete the script when done.

Before you use this script, make sure you have a backup of your project.

Do not use this script if your project contains fields not defined in blueprints on purpose.


 * Script to clean up unused fields in page or file content files
 * Compares fields present in the content file
 * with fields defined in the blueprint
 * and removes all undefined fields

require __DIR__ . '/kirby/bootstrap.php';

$kirby = new Kirby;

// Authenticate as almighty

// Define your collection;
$models = $kirby->models();

// set the fields to be ignored
$ignore = ['uuid', 'title', 'slug', 'template', 'sort', 'focus'];

foreach ($models as $model) {
    // call the script for all languages if multilang
    if ($kirby->multilang() === true) {
            $languages = $kirby->languages();
            foreach ($languages as $language) {
                    cleanUp($model, $ignore, $language->code());
    } else {
            cleanUp($model, $ignore);

function cleanUp($model, $ignore = null, string $lang = null) {
            // get all fields in the content file
            $contentFields = $model->content($lang)->fields();

            // unset all fields in the `$ignore` array
            foreach ($ignore as $field) {
                    if (array_key_exists($field, $contentFields) === true) {

            // get the keys
            $contentFields = array_keys($contentFields);

            // get all field keys from blueprint
            $blueprintFields = array_keys($model->blueprint()->fields());

            // get all field keys that are in $contentFields but not in $blueprintFields
            $fieldsToBeDeleted = array_diff($contentFields, $blueprintFields);

            // update page only if there are any fields to be deleted
            if (count($fieldsToBeDeleted) > 0) {

                    // flip keys and values and set new values to null
                    $data = array_map(fn ($value) => null, array_flip($fieldsToBeDeleted));

                    // try to update the page with the data
                    try {
                            $model->update($data, $lang);
                            echo Html::tag('p', 'The content file for ' . $model->id() . ' was updated');
                    } catch (Exception $e) {
                            echo Html::tag('p', $model->id() . ': ' .$e->getMessage());
            } else {
                    echo Html::tag('p', 'Nothing to clean up in ' . $model->id());

Instead of echoing the results on screen, you can also write them into a log file if you prefer.