# writeContent deprecated in v5

**URL:** <https://forum.getkirby.com/t/writecontent-deprecated-in-v5/34814>\
**Category:** Questions\
**Tags:** v5\
**Created:** [October 3, 2025, 6:39am UTC](https://forum.getkirby.com/t/writecontent-deprecated-in-v5/34814 "2025-10-03T06:39:11Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![rasteiner](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/rasteiner/32/4788_2.png) [@rasteiner](https://forum.getkirby.com/u/rasteiner)\
**Post date:** [October 3, 2025, 6:39am UTC](https://forum.getkirby.com/t/writecontent-deprecated-in-v5/34814/1 "2025-10-03T06:39:11Z")

</div>

In v4 I often did something like this:

```php
    // 1. load json file containing an array of page contents
    // 2. search and update corresponding element in array
    // 3. save json file
    public function writeContent(array $data, ?string $languageCode = null): bool
    {
        $patch = array_filter([
            'only' => $data['price'] ?? null, 
            'some' => $data['some'] ?? null,
            'fields' => $data['fields'] ?? null,
            'allowed' => $data['allowed'] ?? null,
        ], fn($v) => $v !== null);

        $filePath = "path/to/json-file.json";
        $allPages = json_decode(file_get_contents($filePath), true);
        $itemIndex = array_search(
                $this->ref()->value(),
                array_column($allPages, 'ref')
        );
        $allPages[$itemIndex] = array_replace($allPages[$itemIndex], $data);
        file_put_contents($filePath, json_encode($allPages));
        return false;
    }

```

In Kirby 5, `writeContent` is deprecated. This is what the changelog has to say:

> - Extending the internal `$model->contentFile()`, `$model->contentFiles()`, `$model->contentFileDirectory()`, `$model->contentFileExtension()`, `$model->contentFileName()`, `$model->readContent()` and `$model->writeContent()` methods in a page model will no longer have an effect as these methods are no longer called by the core. **Please extend the new `Kirby\Content\PlainTextContentStorageHandler` class instead and return an instance of your custom class from `$model->storage()`**. Please note that the interface of `PlainTextContentStorageHandler` is internal and may change in the future.

(emphasis mine)

Now, I’ve tried extending `PlainTextContentStorageHandler` but I can’t get it to actually save anything. No “changes” version and no “latest” version. My `write()` is never called (like, I put a `die("hello world")` in it and it’s never called).

The storage class roughly looks like this:

```php
class MyStorage extends PlainTextStorage {
    protected static array $fileCache = [];

    public function __construct(MyPage $page, protected string $ref) {
        parent::__construct($page);
    }

    protected function contentDirectory(VersionId $versionId): string
    {
        return "path/to";
    }

    public function contentFile(VersionId $versionId, Language $language): string
    {
        $filename = match ($versionId->value()) {
            VersionId::LATEST => 'json-file.json',
            VersionId::CHANGES => 'json-file-changes.json',
        };
        return $this->contentDirectory($versionId) . '/' . $filename;
    }

    public function delete(VersionId $versionId, Language $language): void {
        throw new Exception('this must never happen');
    }

    public function exists(VersionId $versionId, Language $language): bool {
        // this is what the original code looks like if you only have 1 file and no languages
        return $versionId->is(VersionId::LATEST);
    }

    // new utility function to read and cache the json file
    protected function readContentFile(VersionId $versionId, Language $language): array {
        $contentFile = $this->contentFile($versionId, $language);

        if (!file_exists($contentFile)) {
            return [];
        }
        $json = file_get_contents($contentFile);
        if ($json === false) {
            return [];
        }
        return self::$fileCache[$versionId->value()] = json_decode($json, true);
    }

    public function read(VersionId $versionId, Language $language): array {
        if($versionId->is(VersionId::LATEST)) {
            $content = self::$fileCache[$versionId->value()] ?? $this->readContentFile($versionId, $language);
            $index = array_search($this->ref, array_column($content, 'ref'));
            if($index === false) throw new Exception('Page not found: ' . $this->ref);
            return $content[$index];
        }
        
        // changes version merges the latest version with the changes
        $latest = self::$fileCache[VersionId::LATEST] ?? $this->readContentFile(VersionId::latest(), $language);
        $changes = self::$fileCache[VersionId::CHANGES] ?? $this->readContentFile(VersionId::changes(), $language);
        $index_latest = array_search($this->ref, array_column($latest, 'ref'));
        $index_changes = array_search($this->ref, array_column($changes, 'ref'));
        if($index_latest === false && $index_changes === false) throw new Exception('Page not found: ' . $this->ref);
        if($index_latest === false) return $changes[$index_changes];
        if($index_changes === false) return $latest[$index_latest];
        return array_replace($latest[$index_latest], $changes[$index_changes]);
    }

    public function write(VersionId $versionId, Language $language, array $fields): void {
        $patch = array_filter([
            'only' => $fields['price'] ?? null, 
            'some' => $fields['some'] ?? null,
            'fields' => $fields['fields'] ?? null,
            'allowed' => $fields['allowed'] ?? null,
        ], fn($v) => $v !== null);

        $data = self::$fileCache[$versionId->value()] ?? $this->readContentFile($versionId, $language);

        $index = array_search($this->ref, array_column($data, 'ref'));
        if($index === false) throw new Exception('Apartment not found: ' . $this->ref);

        $data[$index] = array_merge($data[$index], $patch);
        self::$fileCache[$versionId->value()] = $data;
        file_put_contents($this->contentFile($versionId, $language), json_encode($data));
    }
}

```

The model looks like this:

```php
    public function storage(): Storage
    {
        return $this->storage ??= new MyStorage($this, $this->_ref);
    }

```

* * *

Is there an example of how to update virtual pages with the panel?

---

<div class="post-metadata">

**Author:** ![rasteiner](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/rasteiner/32/4788_2.png) [@rasteiner](https://forum.getkirby.com/u/rasteiner)\
**Post date:** [October 3, 2025, 4:57pm UTC](https://forum.getkirby.com/t/writecontent-deprecated-in-v5/34814/2 "2025-10-03T16:57:57Z")

</div>

So, I’ve been looking at the stuff that calls my `function storage()` and what it returns.

When doing an edit, it’s getting called exactly once; by `ModelWithContent::changeStorage`.

That happens essentially in the constructor of the model. The calls go something like this:

```auto
 - ...
 - MyPage::__construct()
 - ModelWithContent::__construct()
 - ModelWithContent::setContent()
 - ModelWithContent::changeStorage() <- here the storage is changed to MemoryStorage

```

After that it only returns `MemoryStorage`.

So I guess Kirby is switching out my storage and never gives it back, and that’s supposedly why my `write` function never gets called.  
How can I make it go back to `MyStorage`?

---

<div class="post-metadata">

**Author:** ![rasteiner](https://dub1.discourse-cdn.com/flex017/user_avatar/forum.getkirby.com/rasteiner/32/4788_2.png) [@rasteiner](https://forum.getkirby.com/u/rasteiner)\
**Post date:** [October 6, 2025, 8:47pm UTC](https://forum.getkirby.com/t/writecontent-deprecated-in-v5/34814/3 "2025-10-06T20:47:42Z")

</div>

Things I’ve learned:

If you want to use the storage stuff (and I don’t think there’s a way around that): **don’t pass `content` or `translations` props to the model constructor**.  
Otherwise Kirby will switch out your storage handler with a `MemoryStorage`.

Kirby will need to be able to instantiate both your `Page` model as well as your `Storage`, this means you can _only_ use the `__construct` signature Kirby expects:

```php
// MyStorage
public function __construct(Page $page);

```

```php
// MyPage
public function __construct(array $props);

```

No additional parameters allowed. If your model needs more infomation to construct itself, you’ll have to work around that. Also remember that you can’t store that info in `'content'` (because above).  
Like in my case above, I need `$this->_ref` to identify the object in the json file. I needed to reconstruct that reference number from the slug.
