Frontbacked Docs

Schema and Validation

Use Frontbacked.state.check(path, schema, check?) to validate browser state before calling APIs such as signUp, createPost, updatePost, or updateUserData.

Client validation is for fast feedback: it catches missing values, wrong file types, oversized files, and invalid form shapes before the user waits on a request. FRL schemas still protect saved data, so use both: FQL validation for friendly forms, FRL validation for the final data contract.

Reuse FRL Schemas

When a theme has FRL rules, use Frontbacked.schemas.post(...) or Frontbacked.schemas.profile(...) to start from that saved-data contract and adjust only what the browser form needs.

const productFormSchema = Frontbacked.schemas.post("products", {
  action: "create",
  omit: ["status"],
  add: {
    agreeToTerms: {
      type: "boolean",
      accepted: true,
      messages: { accepted: "Accept the terms to publish this product." }
    }
  },
  feedback: { to: "formErrors" }
});

const result = Frontbacked.state.check("productForm", productFormSchema);

Use pick for small forms, omit for fields the page should not submit, patch for field-specific browser messages, and add for browser-only checks such as terms acceptance.

Basic Form Validation

<!-- {STATE}
  state = {
    "signup": {
      "name": "#signupName.oninput.target.value",
      "email": "#signupEmail.oninput.target.value",
      "password": "#signupPassword.oninput.target.value",
      "confirmPassword": "#signupConfirmPassword.oninput.target.value",
      "terms(false)": "#signupTerms.onchange.target.checked"
    },
    "signupStatus": "#signupForm.onsubmit.handleSignup()"
  }
-->

<form id="signupForm">
  <input id="signupName" placeholder="Name">
  <input id="signupEmail" placeholder="Email">
  <input id="signupPassword" type="password" placeholder="Password">
  <input id="signupConfirmPassword" type="password" placeholder="Confirm password">
  <label><input id="signupTerms" type="checkbox"> I accept the terms</label>
  <button>Create account</button>
</form>
const signupSchema = {
  name: { type: "string", required: true, trim: true, min: 2, max: 80 },
  email: { type: "email", required: true, trim: true },
  password: { type: "string", required: true, min: 8, max: 128 },
  confirmPassword: {
    type: "string",
    required: true,
    sameAs: "signup.password",
    messages: { sameAs: "Passwords must match." }
  },
  terms: {
    type: "boolean",
    accepted: true,
    messages: { accepted: "Please accept the terms to continue." }
  }
};

async function handleSignup() {
  const result = Frontbacked.state.check("signup", signupSchema);
  if (!result.ok) return null;

  return await Frontbacked.signUp({
    email: result.values.email,
    password: result.values.password,
    name: result.values.name
  });
}

Frontbacked.functions.define({ handleSignup });

result.values contains normalized values. In the example above, name is trimmed and email is trimmed and lowercased.

Validate Post Data Before Upload

File rules work alongside normal string, number, object, and array rules.

<!-- {STATE}
  state = {
    "articleForm": {
      "title": "#articleTitle.oninput.target.value",
      "category('market')": "#articleCategory.onchange.target.value",
      "body": "#articleBody.oninput.target.value",
      "cover": "#articleCover.onchange.target.files[0]",
      "video": "#articleVideo.onchange.target.files[0]",
      "tags": []
    },
    "articlePublishStatus": "#publishArticleForm.onsubmit.publishArticle()"
  }
-->

<form id="publishArticleForm">
  <input id="articleTitle">
  <select id="articleCategory">
    <option value="market">Market</option>
    <option value="education">Education</option>
    <option value="news">News</option>
  </select>
  <textarea id="articleBody"></textarea>
  <input id="articleCover" type="file" accept="image/jpeg,image/png,image/webp">
  <input id="articleVideo" type="file" accept="video/mp4,video/webm">
  <button>Publish</button>
</form>
const articleSchema = {
  title: { type: "string", required: true, trim: true, min: 3, max: 140 },
  category: { type: "enum", enums: ["market", "education", "news"], required: true },
  body: { type: "string", required: true, min: 20, max: 50000 },
  cover: {
    type: "file",
    required: true,
    maxSize: 7000000,
    mimeTypes: ["image/jpeg", "image/png", "image/webp"]
  },
  video: {
    type: "file",
    maxSize: 500000000,
    maxDurationSeconds: 300,
    mimeTypes: ["video/mp4", "video/webm", "video/quicktime"]
  },
  tags: {
    type: "array",
    max: 10,
    of: { type: "string", trim: true, min: 1, max: 40 }
  }
};

async function publishArticle() {
  const result = Frontbacked.state.check("articleForm", articleSchema);
  if (!result.ok) return null;

  return await Frontbacked.createPost({
    type: "articles",
    post: result.values,
    onUploadProgress(progress) {
      Frontbacked.state.set(`uploads.${progress.sessionId}`, progress);
    }
  });
}

Frontbacked.functions.define({ publishArticle });

maxSize and mimeTypes can be checked immediately from the selected file. maxDurationSeconds keeps your client schema aligned with the FRL video playback cap. If an uploaded video is longer than the allowed duration, Frontbacked prepares playback from the beginning up to that limit.

Nested Objects

Use fields for nested objects:

const profileSchema = {
  displayName: { type: "string", required: true, trim: true, max: 80 },
  location: {
    type: "object",
    fields: {
      city: { type: "string", trim: true, max: 80 },
      country: { type: "string", trim: true, max: 80 }
    }
  },
  socials: {
    type: "object",
    fields: {
      website: { type: "string", trim: true, max: 200 },
      x: { type: "string", trim: true, max: 80 }
    }
  }
};

const result = Frontbacked.state.check("profile", profileSchema);
if (result.ok) {
  await Frontbacked.updateUserData({
    data: result.values,
    merge: true
  });
}

Validate One Field

Use the optional check object when validating several fields separately, such as on blur:

const check = { hasError: false, errors: [] };

Frontbacked.state.check("signup.email", signupSchema.email, check);
Frontbacked.state.check("signup.password", signupSchema.password, check);

if (check.hasError) {
  console.log(check.errors);
}

Custom Rules

Use validate when a field needs theme-specific logic:

const schema = {
  coupon: {
    type: "string",
    trim: true,
    validate(value) {
      if (!value) return true;
      return /^SAVE-[A-Z0-9]{6}$/.test(value) || "Coupon codes look like SAVE-ABC123.";
    }
  }
};

Feedback Options

By default, Frontbacked places messages near matching inputs when it can. You can override where a message goes:

const schema = {
  avatar: {
    type: "file",
    maxSize: 3000000,
    mimeTypes: ["image/jpeg", "image/png"],
    feedback: { to: "toast", duration: 4000 },
    messages: {
      maxSize: "Use an image smaller than 3MB.",
      mimeTypes: "Use a JPEG or PNG image."
    }
  }
};

Useful feedback.to values:

ValueBehavior
"alert"Show a Frontbacked alert.
"toast"Show a temporary toast.
"formErrors"Write messages to the form error area.
"customFunction()"Call a global function with the message payload.

Schema Keys

KeyPurpose
typestring, email, number, boolean, object, array, file, datetime, or post.
requiredRequire a present value. Empty strings, empty arrays, and missing files fail.
trim, lowercase, uppercaseNormalize string values before validation returns. email lowercases by default.
min, maxString length, number value, or array length limit.
patternRegular expression for string values.
enumAllowed values.
sameAsRequire the value to equal another state path.
acceptedRequire a boolean value to be true, useful for terms checkboxes.
maxSizeMaximum file size in bytes.
mimeTypesAllowed file MIME types.
maxDurationSecondsMaximum prepared video playback duration in seconds.
fieldsNested object schema.
of or itemsArray item schema.
messagesOptional custom messages by failed rule, such as required, email, min, or max.
feedbackMessage destination options.
validateCustom validation function that receives (value, state).
allowFalseTreat boolean false as valid.
allowEmptyStringTreat an empty string as valid.
allowEmptyArrayTreat an empty array as valid.