Schema and Validation

Use Frontbacked.checkState(path, schema, check?) to validate browser state before calling APIs such as signUp, uploadPost, 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.

Basic Form Validation

<!-- {STATE}
signup: {
  name: "",
  email: "",
  password: "",
  confirmPassword: "",
  terms: false
}
-->

<form id="signupForm">
  <input f-model="signup.name" placeholder="Name">
  <input f-model="signup.email" placeholder="Email">
  <input f-model="signup.password" type="password" placeholder="Password">
  <input f-model="signup.confirmPassword" type="password" placeholder="Confirm password">
  <label><input f-model="signup.terms" 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." }
  }
};

document.querySelector("#signupForm").addEventListener("submit", async (event) => {
  event.preventDefault();

  const result = Frontbacked.checkState("signup", signupSchema);
  if (!result.ok) return;

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

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}
articleForm: {
  title: "",
  category: "market",
  body: "",
  cover: null,
  video: null,
  tags: []
}
-->

<input f-model="articleForm.title">
<select f-model="articleForm.category">
  <option value="market">Market</option>
  <option value="education">Education</option>
  <option value="news">News</option>
</select>
<textarea f-model="articleForm.body"></textarea>
<input f-model="articleForm.cover" type="file" accept="image/jpeg,image/png,image/webp">
<input f-model="articleForm.video" type="file" accept="video/mp4,video/webm">
const articleSchema = {
  title: { type: "string", required: true, trim: true, min: 3, max: 140 },
  category: { type: "string", enum: ["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.checkState("articleForm", articleSchema);
  if (!result.ok) return;

  await Frontbacked.uploadPost({
    type: "articles",
    post: result.values,
    onUploadProgress(progress) {
      Frontbacked.setState(`uploads.${progress.sessionId}`, progress);
    }
  });
}

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.checkState("profile", profileSchema);
if (result.ok) {
  await Frontbacked.updateUserData(result.values);
}

Validate One Field

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

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

Frontbacked.checkState("signup.email", signupSchema.email, check);
Frontbacked.checkState("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, or datetime.
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.
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.