octobercms/october · error · SystemException
The metric code can only contain Latin letters, numbers and
Error message
The metric code can only contain Latin letters, numbers and underscore. The first character must be a letter
What it means
ReportMetric codes must match ^[a-z][a-z0-9_]+$/i: they start with a letter, continue with letters (either case), digits, or underscores — and, because of the '+', must be at least TWO characters long. Violations throw immediately in the constructor.
Source
Thrown at modules/dashboard/classes/ReportMetric.php:66
private $displayFormatter;
/**
* __construct a new metric instance.
* @param string $code Specifies the metric referral code.
* @param string $databaseColumnName Specifies the column name in the data source table.
* @param string $displayName Specifies the metric name used in reports.
* @param string $aggregateFunction Specifies the aggregate function for the metric.
* @param ?array $intlFormatOptions Client-side formatting options, compatible with the Intl.NumberFormat() constructor options argument.
* Skip the argument to use the default formatting options.
*/
public function __construct(string $code, string $databaseColumnName, string $displayName, string $aggregateFunction, ?array $intlFormatOptions = null)
{
if (!strlen($code)) {
throw new SystemException('The metric code cannot be empty.');
}
if (!preg_match('/^[a-z][a-z0-9_]+$/i', $code)) {
throw new SystemException('The metric code can only contain Latin letters, numbers and underscore. The first character must be a letter');
}
if (!strlen($databaseColumnName)) {
throw new SystemException('The database column name cannot be empty.');
}
if (!strlen($displayName)) {
throw new SystemException('The display name cannot be empty.');
}
if (!strlen($aggregateFunction)) {
throw new SystemException('The aggregate function cannot be empty.');
}
$knownAggregateFunctions = [
self::AGGREGATE_SUM,
self::AGGREGATE_AVG,
self::AGGREGATE_MIN,View on GitHub (pinned to b608633a7e)
Solutions
- Normalize the code: strip non-alphanumerics, convert to underscores, prefix a letter if it starts with a digit, and ensure length >= 2 (e.g. 'total_amount', 'm1')
- Build codes from stable keys rather than free-form labels
- Cover metric definitions with a small unit test that runs the regex before release
Example fix
// before
$metric = new ReportMetric('1st-quarter-sales', 'oc_amount', 'Q1 Sales', 'sum');
// after
$metric = new ReportMetric('q1_sales', 'oc_amount', 'Q1 Sales', 'sum');
// helper for dynamic codes
$code = preg_replace('/[^a-zA-Z0-9_]+/', '_', $label); // spaces/hyphens -> underscore
$code = preg_replace('/^[^a-zA-Z]+/', '', $code) ?: 'metric'; // drop leading digits
$code = substr($code, 0, 1) . max(substr($code, 1), 'x'); // guarantee >= 2 chars Defensive patterns
Strategy: type-guard
Validate before calling
$code = preg_replace('/[^a-zA-Z0-9_]+/', '_', (string) $rawCode);
$code = preg_replace('/^[^a-zA-Z]+/', '', $code);
if (strlen($code) < 2) {
$code = str_pad($code, 2, 'x');
}
if (!ReportMetric::isValidCode($code)) { // see typeGuard
throw new InvalidArgumentException("Invalid metric code '{$rawCode}'");
} Type guard
function isValidMetricCode(string $code): bool
{
return (bool) preg_match('/^[a-z][a-z0-9_]+$/i', $code);
} Prevention
- Derive codes from stable machine keys, never free-form labels
- Watch the two-character minimum — single-letter codes like 'x' are rejected
- Filter hyphens/spaces to underscores and strip leading digits when generating codes dynamically
When it happens
Trigger: new ReportMetric('1total', ...) (leading digit), new ReportMetric('total-amount', ...) (hyphen), new ReportMetric('total amount', ...) (space), or even new ReportMetric('x', ...) — a single-character code fails because the pattern requires one letter PLUS at least one more character.
Common situations: Machine-generated codes starting with digits; codes derived from translated or human-readable labels containing spaces/hyphens; very short auto-generated codes ('x', 'a').
Related errors
- The dimension field code must have the oc_field_ prefix
- The dimension metric is already registered:
- The metric code cannot be empty.
- Unknown dimension type:
- Date dimensions cannot have fields.
AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21).
Data as JSON: /api/errors/6d93e940b8a8aa54.
Report an issue: GitHub.