flarum/framework · error · ValidationException

core.admin.appearance.custom_styles_cannot_use_less_features

Error message

core.admin.appearance.custom_styles_cannot_use_less_features

What it means

ValidateCustomLess::whenSettingsSaving throws ValidationException with the translated message core.admin.appearance.custom_styles_cannot_use_less_features when custom LESS/CSS settings contain constructs less.php cannot safely evaluate: @import directives (or the mistyped '@impor' which less.php parses the same way) or data-uri() calls. These features would let admins read arbitrary files at compile time, so they are blocked.

Solutions

  1. Remove any @import statements and data-uri() calls from the custom LESS/CSS.
  2. Load external stylesheets with <link> tags in custom HTML/header instead of CSS @import.
  3. Inline the imported LESS content directly, or add fonts via the site's HTML head section rather than LESS imports.

Example fix

// before (custom_less)
@import url("https://fonts.googleapis.com/css?family=Open+Sans");

// after (custom header HTML instead)
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans">
Defensive patterns

Strategy: validation

Validate before calling

// before saving custom styles
const blocked = /@impor|data-uri\s*\(/i;
if (typeof customLess === 'string' && blocked.test(customLess)) {
  alert('Custom styles cannot use @import or data-uri()');
}

Try / catch

try {
    $this->settings->save($payload);
} catch (ValidationException $e) {
    return response()->json(['errors' => $e->errors()], 422);
}

Prevention

When it happens

Trigger: Saving admin Appearance settings where custom_less or custom_header contains '@import', '@impor', or 'data-uri(' (case-insensitive), detected by the regex in whenSettingsSaving before assets are compiled.

Common situations: Admins pasting CSS/LESS copied from themes that use @import for fonts or external stylesheets; importing Google Fonts via @import url(...) in custom styles.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of flarum/framework@4b939f6853 (2026-09-15). Data as JSON: /api/errors/cddaa860c72597f0. Report an issue: GitHub.

Appendix: source

Thrown at framework/core/src/Forum/ValidateCustomLess.php:70

            isset($event->settings['custom_less']) ? ['custom_less'] : [],
            array_intersect(
                array_keys($event->settings),
                array_column($this->customLessSettings, 'key')
            )
        );

        foreach ($lessFeatureKeys as $key) {
            // The file system is taken away from the compiler by
            // LessCompiler::containImports(), which is what actually stops a
            // custom-LESS file read. This check stays so the administrator is
            // told at save time rather than silently getting a stylesheet with
            // the import dropped. `@impor` is matched as well as `@import`,
            // because less.php matches the directive as `@import?` and so
            // parses both the same way.
            if (is_string($event->settings[$key]) && preg_match('/@impor|data-uri\s*\(/i', $event->settings[$key])) {
                $translator = $this->container->make(TranslatorInterface::class);

                throw new ValidationException([
                    $key => $translator->trans('core.admin.appearance.custom_styles_cannot_use_less_features')
                ]);
            }
        }

        // We haven't saved the settings yet, but we want to trial a full
        // recompile of the CSS to see if this custom LESS will break
        // anything. In order to do that, we will temporarily override the
        // settings repository with the new settings so that the recompile
        // is effective. We will also use a dummy filesystem so that nothing
        // is actually written yet.

        $settings = $this->container->make(SettingsRepositoryInterface::class);

        $this->container->extend(
            SettingsRepositoryInterface::class,
            function ($settings) use ($event) {
                return new OverrideSettingsRepository($settings, $event->settings);

View on GitHub (pinned to 4b939f6853)