Auth and Posts API

frontbacked.js exposes helper methods for authentication and post writes. These methods use the same endpoint configured in Frontbacked.init().

Frontbacked.init({ endpoint: "/api" });

Sign Up

const result = await Frontbacked.signUp({
  email: "ada@example.com",
  password: "secret-password",
  name: "Ada",
  data: {
    plan: "starter"
  },
  emailVerification: {
    redirectTo: "/dashboard"
  }
});

When signup succeeds and Frontbacked returns a token, the runtime stores it for future authenticated requests and updates $auth. emailVerification.redirectTo is optional. When provided, the verification email link redirects there after the user clicks it and the token is accepted.

In local development, if the response contains localEmail.sent, Frontbacked shows a bottom notification with a link to the local mailbox.

Send Email Verification

const result = await Frontbacked.sendEmailVerification({
  email: "ada@example.com",
  redirectTo: "/dashboard"
});

This sends or resends a verification email for the authenticated user. Frontbacked first checks the current $auth.emailVerified value and returns without sending a request when it is already true. Frontbacked also refuses to send for already verified users or for an email that does not belong to the current user.

Sign In

const result = await Frontbacked.signIn({
  email: "ada@example.com",
  password: "secret-password",
  rememberMe: true
});

rememberMe controls where the auth token is stored.

ValueStorage
truelocalStorage, survives browser restart.
falsesessionStorage, clears with the browser session.

If rememberMe is omitted, it defaults to true.

Logout

const ok = await Frontbacked.confirm("Do you want to sign out?", "Sign out?");
if (ok) Frontbacked.signOut();

Frontbacked.signOut() and Frontbacked.logout() both clear the token from localStorage and sessionStorage. Frontbacked.confirm(message, title?) returns true when the user confirms and false when the user cancels.

Password Reset

const result = await Frontbacked.passwordReset({
  email: "ada@example.com",
  path: "/password-reset"
});

The method sends { email, path } to /api/password-reset. path is the local page the reset email link should open. The response uses a generic message so account existence is not leaked.

Confirm Password Reset

const params = new URLSearchParams(location.search);

const result = await Frontbacked.confirmPasswordReset({
  email: params.get("email") || "",
  token: params.get("token") || "",
  newPassword: Frontbacked.getState("reset.newPassword")
});

This completes a password reset after the user clicks the reset email link. Frontbacked validates the token before storing the new password.

Update Password

const result = await Frontbacked.updatePassword({
  currentPassword: Frontbacked.getState("password.currentPassword"),
  newPassword: Frontbacked.getState("password.newPassword")
});

The method sends { currentPassword, newPassword } to /api/password. Frontbacked verifies the current password before storing the new one.

Update User Data

Use updateUserData when a signed-in user edits their own profile data stored in the user data JSON.

const result = await Frontbacked.updateUserData({
  data: {
    phone: Frontbacked.getState("profile.phone"),
    country: Frontbacked.getState("profile.country")
  },
  merge: true
});

merge defaults to true. When it is true, Frontbacked merges the submitted fields into the existing user data; when it is false, the submitted object replaces the existing user data. Frontbacked validates the final value with the theme's FRL auth block.

Upload a Post

Use uploadPost for new posts.

const result = await Frontbacked.uploadPost({
  type: "products",
  post: {
    title: Frontbacked.getState("form.title"),
    price: Frontbacked.getState("form.price"),
    image: Frontbacked.getState("form.image")
  }
});

Options:

OptionRequiredPurpose
typeyesFRL post type, such as products.
postyesPost data to validate and store.

If post contains File or Blob values at any depth, Frontbacked uploads each file with a resumable flow and saves the post as JSON with file metadata. File bytes are not included in the post JSON body.

Track upload progress with onUploadProgress:

const result = await Frontbacked.uploadPost({
  type: "products",
  post: {
    title: Frontbacked.getState("form.title"),
    image: Frontbacked.getState("form.image")
  },
  onUploadProgress(progress) {
    Frontbacked.setState(`uploads.${progress.sessionId}`, progress);
  }
});

Use the Upload File guide for nested files, progress shape, and Frontbacked.uploads.resume().

Post IDs are generated by Frontbacked. Theme code cannot supply a custom ID when creating a post. If you need to copy the generated post ID into a normal field on the same post, set that field to the exact string "$id" on create; Frontbacked replaces it with the generated ID before storing the post.

Update a Post

Use updatePost for existing posts.

const result = await Frontbacked.updatePost({
  id: Frontbacked.getState("product.id"),
  type: "products",
  post: {
    title: Frontbacked.getState("form.title"),
    price: Frontbacked.getState("form.price")
  },
  merge: true
});

Options:

OptionRequiredPurpose
idyesExisting post id.
typenoPost type when Frontbacked needs it.
postyesPatch or replacement data.
mergenoDefaults to true. When true, Frontbacked can merge the submitted fields with the stored post before validation.

Frontbacked still runs FRL on the final post. Use strict schemas, immutable, and immutableUnless in FRL to protect fields that must not change through merged updates.

Delete a Post

const result = await Frontbacked.deletePost({
  id: Frontbacked.getState("product.id")
});

Frontbacked runs the post type's delete require[...] rule before deleting.

Form Submit Example

<head>
  <!-- {STATE}
    state = {
      "form": {
        "title": "#title.oninput.target.value",
        "price": "#price.oninput.target.value.toNumber()",
        "image": "#image.onchange.target.files[0]",
        "status": "#productForm.onsubmit.saveProduct()"
      },
      "submitLabel": "$state.form.status || 'Save product'"
    }
  -->
</head>
<body>
  <form id="productForm">
    <input id="title" name="title">
    <input id="price" name="price">
    <input id="image" type="file">
    <button f="true" f-text="$state.submitLabel">Save product</button>
  </form>

  <script>
    function toNumber(value) {
      return Number(value || 0);
    }

    async function saveProduct(event) {
      const response = await Frontbacked.uploadPost({
        type: "products",
        post: {
          title: Frontbacked.getState("form.title"),
          price: Frontbacked.getState("form.price"),
          image: Frontbacked.getState("form.image"),
          referenceId: "$id"
        }
      });

      return response.ok ? "Saved" : (response.error || "Could not save");
    }

    window.toNumber = toNumber;
    window.saveProduct = saveProduct;
  </script>
</body>

For FQL onsubmit bindings, Frontbacked prevents the default browser reload before your handler runs. Frontbacked will also lock the triggering element while its network request is pending.

Error Handling

Auth and post methods return Frontbacked JSON responses. Check ok, error, and any domain-specific fields the response includes.

const result = await Frontbacked.signIn({ email, password, rememberMe });

if (!result.ok) {
  Frontbacked.setState("form.error", result.error || "Unable to sign in");
}

When FRL rejects a write, the response tells the frontend which rule or validation failed closely enough to guide the user.

Checking State Before Submit

Use Frontbacked.checkState(path, schema, check?) when a handler needs validated state before it can continue. It can check one field or a whole object. When a field fails, Frontbacked shows a message and returns a result with ok: false.

window.authSchemas = {
  signUp: {
    firstName: {
      type: "string",
      required: true,
      trim: true,
      min: 2,
      max: 60
    },
    email: {
      type: "email",
      required: true,
      trim: true
    },
    password: {
      type: "string",
      required: true,
      min: 8
    },
    confirmPassword: {
      type: "string",
      required: true,
      sameAs: "form.password",
      messages: {
        sameAs: "Passwords do not match."
      }
    },
    terms: {
      type: "boolean",
      accepted: true
    },
    avatar: {
      type: "file",
      maxSize: 3000000,
      mimeTypes: ["image/jpeg", "image/png", "image/webp"]
    }
  }
};

Then check the whole state object:

async function handleSignUp(event) {
  const result = Frontbacked.checkState("form", window.authSchemas.signUp);
  if (!result.ok) return;

  const { firstName, email, password } = result.values;
  return Frontbacked.signUp({
    email,
    password,
    name: firstName,
    emailVerification: {
      redirectTo: "/dashboard"
    }
  });
}

To check one field, pass that field's schema:

const result = Frontbacked.checkState("form.firstName", window.authSchemas.signUp.firstName);
if (!result.ok) return;

const firstName = result.values.firstName;
// Same value:
const firstNameAgain = result.value;

The optional third argument lets you collect errors across several checks:

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

const firstNameResult = Frontbacked.checkState("form.firstName", window.authSchemas.signUp.firstName, check);
const emailResult = Frontbacked.checkState("form.email", window.authSchemas.signUp.email, check);

if (check.hasError) return;

const firstName = firstNameResult.value;
const email = emailResult.value;

If a field schema has no messages object, or a message for the failed rule is missing, Frontbacked generates one from the field name and rule. firstName becomes first name, user_email becomes user email, and billingAddress.line1 becomes billing address line 1.

Generated examples:

Failed checkExample message
requiredPlease enter your first name.
minFirst name must be at least 2 characters.
maxFirst name must be 60 characters or fewer.
emailPlease enter a valid email address.
sameAsConfirm password must match password.
acceptedPlease accept terms.
maxSizeAvatar must be 3 MB or smaller.

Customize only the messages you care about:

confirmPassword: {
  type: "string",
  required: true,
  sameAs: "form.password",
  messages: {
    sameAs: "Passwords do not match."
  }
}

If feedback.to is not provided, Frontbacked places the message near the first event source for that state. For multiple sources, the first source is used. You can also choose a destination from the schema:

email: {
  type: "email",
  required: true,
  feedback: {
    to: "toast",
    duration: 4000
  }
}

firstName: {
  type: "string",
  required: true,
  feedback: {
    to: "formErrors"
  }
}

Use feedback.to to choose where the error goes:

ValueResult
"alert"Built-in high z-index alert with an OK button.
"toast"Built-in toast. Use duration to control how long it stays visible.
"formErrors" or "#formErrors"Insert the message into that UI element.
"showFormError" or "showFormError()"Call a function on window with (message, payload).

All checkState failures are error messages. Use okText to change the alert button text. The built-in alert, toast, and inline messages can be styled with CSS variables such as --fb-feedback-font, --fb-feedback-error-bg, --fb-feedback-error-text, --fb-feedback-button-bg, and --fb-feedback-z-index.

Automatic Loading State

When a Frontbacked network request starts inside an FQL event handler, Frontbacked locks the triggering element until the request finishes. While locked, repeated calls from the same element are ignored, the element gets a frontbacked-loading class, and Frontbacked disables the element when the browser supports disabling it.

<button
  id="signInSubmit"
  type="submit"
  f-loading-text="Signing in..."
  f-loading-attr-aria-busy="true"
  f-loading-disabled="true"
>
  Sign in
</button>

f-loading-text temporarily replaces the element's text while the request is pending. f-loading-attr-X temporarily sets any HTML attribute, where X is the attribute name. For example, f-loading-attr-aria-busy="true" sets aria-busy="true" while loading and restores the previous value afterward.

Add f-loading-disabled="true" when you want Frontbacked to leave that trigger's UI completely alone. With this attribute present, Frontbacked still sends the request, but it does not add frontbacked-loading, does not change text, does not set loading attributes, does not disable the element, and does not block re-entrant requests from that trigger.

Style the loading state in the theme:

.frontbacked-loading {
  cursor: wait;
  opacity: 0.75;
}

This works for buttons, selects, inputs, and any other element that triggers an FQL event. If the event is a form submit and the browser exposes a submitter button, Frontbacked locks that submitter; otherwise it locks the event-bound element.