Math Solvers Schema Framework
Use conditional-logic schema generation to keep Math Solvers markup aligned with real page state and avoid stale or invalid static templates.
When To Use It
Google Search CentralMath Solvers documentationUse on the homepage of a math problem solving site that accepts an expression and returns a solution, such as a step-by-step walkthrough or graph.
Key Implementation Documentation Highlights
- Required: @type as
["MathSolver", "LearningResource"], url, usageInfo (your privacy policy URL) and potentialAction (SolveMathAction). - SolveMathAction requires target containing
{math_expression_string}and mathExpression-input set torequired name=math_expression_string. eduQuestionType is recommended. - Recommended properties: assesses, inLanguage, learningResourceType (the fixed value
Math Solver). - eduQuestionType and assesses take values from Google's problem type list (for example Polynomial Equation, Derivative, Quadratic Equation).
- Put the markup on the homepage, use canonical URLs if the solver lives at several URLs, and keep the solution and step-by-step walkthrough accessible to users who arrive from Google.
- Solvers must be accurate; do not use the markup for promotional content or for pages that are not math solvers. Validate with the Rich Results Test.
Template Approach
const pageData = {
name: "Example Math Solver",
homepage: "https://math.example.com/",
privacyPolicy: "https://math.example.com/privacy",
solveUrl: "https://math.example.com/solve?q={math_expression_string}"
};
const staticTemplate = {
"@context": "https://schema.org",
"@type": ["MathSolver", "LearningResource"],
"name": pageData.name,
"url": pageData.homepage,
"usageInfo": pageData.privacyPolicy,
"potentialAction": {
"@type": "SolveMathAction",
"target": pageData.solveUrl,
"mathExpression-input": "required name=math_expression_string"
},
"learningResourceType": "Math Solver"
};
{
"@context": "https://schema.org",
"@type": [
"MathSolver",
"LearningResource"
],
"name": "Example Math Solver",
"url": "https://math.example.com/",
"usageInfo": "https://math.example.com/privacy",
"potentialAction": {
"@type": "SolveMathAction",
"target": "https://math.example.com/solve?q={math_expression_string}",
"mathExpression-input": "required name=math_expression_string"
},
"learningResourceType": "Math Solver"
}
Conditional-logic Framework
// Shared helpers
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
const ISO_DATETIME = /^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d+)?)?(Z|[+-](0\d|1[0-4]):[0-5]\d)$/;
function isAbsoluteUrl(value) {
if (typeof value !== "string") return false;
try {
const url = new URL(value);
return (url.protocol === "https:" || url.protocol === "http:") && url.hostname !== "";
} catch {
return false;
}
}
function toIsoDate(value) {
const match = typeof value === "string" ? ISO_DATE.exec(value) : null;
if (!match) return null;
const [y, m, d] = match.slice(1).map(Number);
const date = new Date(Date.UTC(y, m - 1, d));
return date.getUTCMonth() === m - 1 && date.getUTCDate() === d ? value : null;
}
// Keeps the original timezone offset; rejects naive or unparseable values.
function toIsoDateTime(value) {
const match = typeof value === "string" ? ISO_DATETIME.exec(value) : null;
return match && toIsoDate(match[1]) ? value : null;
}
// Drops null, undefined, "", [] and {} recursively.
function compact(value) {
if (Array.isArray(value)) {
const items = value.map(compact).filter((item) => item !== undefined);
return items.length ? items : undefined;
}
if (value !== null && typeof value === "object") {
const out = {};
for (const [key, item] of Object.entries(value)) {
const kept = compact(item);
if (kept !== undefined) out[key] = kept;
}
return Object.keys(out).length ? out : undefined;
}
return value === null || value === undefined || value === "" ? undefined : value;
}
// Page gates: indexable, self-canonical, marked-up content visible.
function passesPageGates(page) {
if (!page || page.noindex || page.robotsBlocked) return false;
if (!isAbsoluteUrl(page.url)) return false;
if (page.canonicalUrl && page.canonicalUrl !== page.url) return false;
return page.contentVisible === true;
}
// Math solver helpers
// Google's problem type list (eduQuestionType and assesses), compared case-insensitively.
const PROBLEM_TYPES = [
"Absolute Value Equation", "Algebra", "Arc Length", "Arithmetic", "Biquadratic Equation", "Calculus",
"Characteristic Polynomial", "Circle", "Derivative", "Differential Equation", "Distance", "Eigenvalue",
"Eigenvector", "Ellipse", "Exponential Equation", "Function", "Function Composition", "Geometry",
"Hyperbola", "Inflection Point", "Integral", "Intercept", "Limit", "Line Equation", "Linear Algebra",
"Linear Equation", "Linear Inequality", "Logarithmic Equation", "Logarithmic Inequality", "Matrix",
"Midpoint", "Parabola", "Parallel", "Perpendicular", "Polynomial Equation", "Polynomial Expression",
"Polynomial Inequality", "Quadratic Equation", "Quadratic Expression", "Quadratic Inequality",
"Radical Equation", "Radical Inequality", "Rational Equation", "Rational Expression", "Rational Inequality",
"Slope", "Statistics", "System of Equations", "Trigonometry"
];
const PLACEHOLDER = "{math_expression_string}";
// Map to Google's canonical spelling; drop unknown problem types.
function toProblemTypes(values) {
const out = [];
for (const v of values || []) {
const match = PROBLEM_TYPES.find((t) => t.toLowerCase() === String(v).trim().toLowerCase());
if (match && !out.includes(match)) out.push(match);
}
return out;
}
function isHomepage(url) {
return new URL(url).pathname === "/";
}
function buildSolveMathAction(action) {
if (!action || typeof action.target !== "string" || !action.target.includes(PLACEHOLDER)) return null;
if (!isAbsoluteUrl(action.target.replace(PLACEHOLDER, "x"))) return null;
return {
"@type": "SolveMathAction",
target: action.target,
"mathExpression-input": "required name=math_expression_string",
eduQuestionType: toProblemTypes(action.problemTypes)
};
}
function buildMathSolverSchema(source) {
const page = source.page;
// Markup belongs on the solver site's homepage.
if (!passesPageGates(page) || !isHomepage(page.url)) return null;
// Required: url, usageInfo (privacy policy), potentialAction SolveMathAction.
const actions = (source.actions || []).map(buildSolveMathAction).filter(Boolean);
if (!isAbsoluteUrl(source.privacyPolicyUrl) || !actions.length) return null;
return compact({
"@context": "https://schema.org",
"@type": ["MathSolver", "LearningResource"], // both types, always
name: source.name,
url: page.url,
usageInfo: source.privacyPolicyUrl,
inLanguage: (source.languages || []).filter((l) => /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(l)),
potentialAction: actions,
learningResourceType: "Math Solver",
assesses: toProblemTypes(source.assesses)
}) ?? null;
}
// Usage:
// const source = {
// "page": {
// "url": "https://math.example.com/",
// "canonicalUrl": "https://math.example.com/",
// "noindex": false,
// "contentVisible": true
// },
// "name": "Example Math Solver",
// "privacyPolicyUrl": "https://math.example.com/privacy",
// "languages": [
// "en",
// "es"
// ],
// "actions": [
// {
// "target": "https://math.example.com/solve?q={math_expression_string}",
// "problemTypes": [
// "polynomial equation",
// "Derivative",
// "Quadratic Equation",
// "Matrix multiplication"
// ]
// }
// ],
// "assesses": [
// "Polynomial Equation",
// "Derivative",
// "Quadratic Equation"
// ]
// };
// const schema = buildMathSolverSchema(source);
# Shared helpers
import re
from datetime import date
from urllib.parse import urlparse
ISO_DATE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})$")
ISO_DATETIME = re.compile(r"^(\d{4}-\d{2}-\d{2})T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d+)?)?(Z|[+-](0\d|1[0-4]):[0-5]\d)$")
def is_absolute_url(value):
if not isinstance(value, str):
return False
try:
parts = urlparse(value)
except ValueError:
return False
return parts.scheme in ("http", "https") and bool(parts.hostname)
def to_iso_date(value):
if not isinstance(value, str) or not ISO_DATE.match(value):
return None
try:
date.fromisoformat(value)
except ValueError:
return None
return value
# Keeps the original timezone offset; rejects naive or unparseable values.
def to_iso_datetime(value):
match = ISO_DATETIME.match(value) if isinstance(value, str) else None
return value if match and to_iso_date(match.group(1)) else None
# Drops None, "", [] and {} recursively.
def compact(value):
if isinstance(value, list):
items = [kept for kept in (compact(item) for item in value) if kept is not None]
return items or None
if isinstance(value, dict):
out = {key: kept for key, kept in ((k, compact(v)) for k, v in value.items()) if kept is not None}
return out or None
return None if value is None or value == "" else value
# Page gates: indexable, self-canonical, marked-up content visible.
def passes_page_gates(page):
if not page or page.get("noindex") or page.get("robotsBlocked"):
return False
if not is_absolute_url(page.get("url")):
return False
if page.get("canonicalUrl") and page["canonicalUrl"] != page["url"]:
return False
return page.get("contentVisible") is True
# Math solver helpers
# Google's problem type list (eduQuestionType and assesses), compared case-insensitively.
PROBLEM_TYPES = [
"Absolute Value Equation", "Algebra", "Arc Length", "Arithmetic", "Biquadratic Equation", "Calculus",
"Characteristic Polynomial", "Circle", "Derivative", "Differential Equation", "Distance", "Eigenvalue",
"Eigenvector", "Ellipse", "Exponential Equation", "Function", "Function Composition", "Geometry",
"Hyperbola", "Inflection Point", "Integral", "Intercept", "Limit", "Line Equation", "Linear Algebra",
"Linear Equation", "Linear Inequality", "Logarithmic Equation", "Logarithmic Inequality", "Matrix",
"Midpoint", "Parabola", "Parallel", "Perpendicular", "Polynomial Equation", "Polynomial Expression",
"Polynomial Inequality", "Quadratic Equation", "Quadratic Expression", "Quadratic Inequality",
"Radical Equation", "Radical Inequality", "Rational Equation", "Rational Expression", "Rational Inequality",
"Slope", "Statistics", "System of Equations", "Trigonometry",
]
PLACEHOLDER = "{math_expression_string}"
LANG_CODE = re.compile(r"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$")
# Map to Google's canonical spelling; drop unknown problem types.
def to_problem_types(values):
out = []
for v in values or []:
match = next((t for t in PROBLEM_TYPES if t.lower() == str(v).strip().lower()), None)
if match and match not in out:
out.append(match)
return out
def is_homepage(url):
return urlparse(url).path in ("", "/")
def build_solve_math_action(action):
target = (action or {}).get("target")
if not isinstance(target, str) or PLACEHOLDER not in target:
return None
if not is_absolute_url(target.replace(PLACEHOLDER, "x", 1)):
return None
return {
"@type": "SolveMathAction",
"target": target,
"mathExpression-input": "required name=math_expression_string",
"eduQuestionType": to_problem_types(action.get("problemTypes")),
}
def build_math_solver_schema(source):
page = source.get("page")
# Markup belongs on the solver site's homepage.
if not passes_page_gates(page) or not is_homepage(page["url"]):
return None
# Required: url, usageInfo (privacy policy), potentialAction SolveMathAction.
actions = [a for a in (build_solve_math_action(a) for a in (source.get("actions") or [])) if a]
if not is_absolute_url(source.get("privacyPolicyUrl")) or not actions:
return None
return compact({
"@context": "https://schema.org",
"@type": ["MathSolver", "LearningResource"], # both types, always
"name": source.get("name"),
"url": page["url"],
"usageInfo": source["privacyPolicyUrl"],
"inLanguage": [l for l in (source.get("languages") or []) if LANG_CODE.match(l)],
"potentialAction": actions,
"learningResourceType": "Math Solver",
"assesses": to_problem_types(source.get("assesses")),
})
{
"@context": "https://schema.org",
"@type": [
"MathSolver",
"LearningResource"
],
"name": "Example Math Solver",
"url": "https://math.example.com/",
"usageInfo": "https://math.example.com/privacy",
"inLanguage": [
"en",
"es"
],
"potentialAction": [
{
"@type": "SolveMathAction",
"target": "https://math.example.com/solve?q={math_expression_string}",
"mathExpression-input": "required name=math_expression_string",
"eduQuestionType": [
"Polynomial Equation",
"Derivative",
"Quadratic Equation"
]
}
],
"learningResourceType": "Math Solver",
"assesses": [
"Polynomial Equation",
"Derivative",
"Quadratic Equation"
]
}
Why Conditional Logic Is Better Than Static Templates
- Prevents emitting empty, null, or stale fields that frequently trigger rich result warnings.
- Supports multiple page states (for example: no rating yet, no image yet, no offers yet) without duplicate templates.
- Lets engineering teams centralize schema policy checks and validation in reusable code paths.
- Makes large-scale schema maintenance safer when content models evolve over time.
Property Reference
What Google's Math solver documentation requires and recommends, property by property. These are the same rules the SchemaCDN validator checks. Each name links to its schema.org definition.
| Property | Expects | Notes | |
|---|---|---|---|
@type | Required | Text, can repeat | @type must be ["MathSolver", "LearningResource"]. One of: MathSolver, LearningResource. |
| potentialAction | Required | SolveMathAction, can repeat | The action that leads to a mathematical explanation (for example, a step-by-step solution or graph) of a math expression. |
| potentialAction.mathExpression-input | Required | Text | MathExpression-input must be 'required name=math_expression_string'. One of: required name=math_expression_string. |
| potentialAction.target | Required | EntryPoint or URL | Target must be a URL template containing {math_expression_string}. |
| potentialAction.eduQuestionType | Recommended | Text, can repeat | EduQuestionType values must come from Google's math problem type list. One of: Absolute Value Equation, Algebra, Arc Length, Arithmetic, Biquadratic Equation, Calculus, and 43 more. |
| url | Required | URL | The URL of the math solver. |
| usageInfo | Required | URL | The URL of your privacy policy. |
| assesses | Recommended | Text, can repeat | Problem types solved (from Google's problem type list) |
| inLanguage | Recommended | Text, can repeat | Languages supported by the solver. |
| learningResourceType | Recommended | Text | LearningResourceType should be 'Math Solver'. One of: Math Solver. |