getgrav/grav · error · RuntimeException

json_encode(): failed to encode group permissions

Error message

json_encode(): failed to encode group permissions

What it means

When a form asks FlexPageObject for 'header.permissions.groups', the value is round-tripped through json_encode() to normalize it; if encoding fails (false), Grav throws this RuntimeException. json_encode() fails on invalid UTF-8 sequences, resources, INF/NAN floats, or deeply recursive arrays. The permissions data itself is malformed for JSON, usually because the page frontmatter or a custom permission resolver produced non-encodable values.

Source

Thrown at system/src/Grav/Framework/Flex/Pages/FlexPageObject.php:206

     */
    public function getFormValue(string $name, $default = null, ?string $separator = null)
    {
        $test = new stdClass();

        $value = $this->pageContentValue($name, $test);
        if ($value !== $test) {
            return $value;
        }

        switch ($name) {
            case 'name':
                return $this->getProperty('template');
            case 'route':
                return $this->hasKey() ? '/' . $this->getKey() : null;
            case 'header.permissions.groups':
                $encoded = json_encode($this->getPermissions());
                if ($encoded === false) {
                    throw new RuntimeException('json_encode(): failed to encode group permissions');
                }

                return json_decode($encoded, true);
        }

        return parent::getFormValue($name, $default, $separator);
    }

    /**
     * Get master storage key.
     *
     * @return string
     * @see FlexObjectInterface::getStorageKey()
     */
    public function getMasterKey(): string
    {
        $key = (string)($this->storage_key ?? $this->getMetaData()['storage_key'] ?? null);
        if (($pos = strpos($key, '|')) !== false) {

View on GitHub (pinned to 6040efed04)

Solutions

  1. Fix the source data: ensure header.permissions.groups in the page file uses valid UTF-8 strings and plain arrays.
  2. json_last_error() after a failed encode to identify the cause (JSON_ERROR_UTF8 vs JSON_ERROR_RECURSION etc.) and strip/sanitize accordingly (mb_convert_encoding, removing resources).
  3. If a plugin modifies permissions at runtime, make it return scalar arrays of permission strings only.
  4. Catch the RuntimeException around form rendering to degrade gracefully with an admin notice instead of a 500.

Example fix

// before (frontmatter)
permissions:
  groups:
    site-editors: ["\xB1-invalid-utf8"]

# after
permissions:
  groups:
    site-editors: ["pages.read", "pages.update"]
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate group permissions are JSON-safe before rendering forms
$groups = $page->getPermissions();
if (json_encode($groups) === false) {
    // identify cause: json_last_error_msg(); sanitize invalid UTF-8, drop resources
    $groups = array_map(
        fn($v) => is_string($v) ? mb_convert_encoding($v, 'UTF-8', 'UTF-8') : $v,
        $groups
    );
}

Try / catch

try {
    $value = $page->getFormValue('header.permissions.groups');
} catch (\Grav\Framework\Flex\Exception\RuntimeException $e) {
    // permissions data not JSON-encodable: flag the page for repair instead of a hard 500
    $admin->setMessage('Page ' . $page->route() . ' has malformed permissions data', 'error');
}

Prevention

When it happens

Trigger: Page header permissions.groups containing invalid UTF-8 bytes (pasted binary/smart characters from a bad editor); a plugin injecting resources or objects into group permission arrays; group values containing NAN/INF from arithmetic; array recursion from self-referencing permission structures.

Common situations: Frontmatter edited in a non-UTF-8 editor or copied from word processors; third-party access-control plugins that attach runtime objects to permission groups.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/c6a73cc95fd679c2. Report an issue: GitHub.