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, Frontbacked stores it for future authenticated requests and updates $auth. username is optional and can be checked first with Frontbacked.checkUsernameAvailability(username). 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.
If the active theme declares required fields in its FRL user schema, include them in data. Frontbacked validates those fields before creating the account. Missing required data stops the signup, so the user and any related records from after create are not partially saved.
Sign-Up And Sign-In Pages
Site admins can turn sign up and sign in on or off independently. Mark custom auth pages and links so Frontbacked can keep the theme in sync with those choices, including during same-site navigation:
<body f-auth-page="signup">
<!-- sign-up form -->
</body>
<a href="/join" f-auth-link="signup">Create an account</a>
<a href="/welcome-back" f-auth-link="signin">Sign in</a>
Standard routes such as /signup, /register, /signin, and /login are recognized automatically. Use f-auth-page="signup|signin" and f-auth-link="signup|signin" for custom route names. When the matching option is unavailable, links stay hidden and the auth page cannot be used.
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.
| Value | Storage |
|---|---|
true | localStorage, survives browser restart. |
false | sessionStorage, clears with the browser session. |
If rememberMe is omitted, it defaults to true.
Provider Sign-In Buttons
Add f-auth-provider to a clickable element to start a provider sign-in flow for the current site. The attribute value is the provider id, such as google or github.
<div class="auth-options" aria-label="Sign in options">
<button type="button" class="provider-button" f-auth-provider="google" f-auth-return-to="/dashboard">
<svg class="provider-logo" viewBox="0 0 18 18" aria-hidden="true">
<path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.91c1.7-1.57 2.69-3.87 2.69-6.62Z"></path>
<path fill="#34A853" d="M9 18c2.43 0 4.47-.81 5.95-2.18l-2.91-2.26c-.8.54-1.83.86-3.04.86a5.37 5.37 0 0 1-5.04-3.71H.96v2.33A9 9 0 0 0 9 18Z"></path>
<path fill="#FBBC05" d="M3.96 10.71A5.41 5.41 0 0 1 3.68 9c0-.59.1-1.17.28-1.71V4.96H.96A9 9 0 0 0 0 9c0 1.45.35 2.83.96 4.04l3-2.33Z"></path>
<path fill="#EA4335" d="M9 3.58c1.32 0 2.51.45 3.44 1.35l2.58-2.58A8.64 8.64 0 0 0 9 0 9 9 0 0 0 .96 4.96l3 2.33A5.37 5.37 0 0 1 9 3.58Z"></path>
</svg>
<span>Continue with Google</span>
</button>
<button type="button" class="provider-button" f-auth-provider="github" f-auth-return-to="/dashboard">
<span>Continue with GitHub</span>
</button>
</div>
<div class="auth-divider"><span>or sign in with email</span></div>
<form class="auth-form">
<!-- Email and password fields -->
</form>
Use the same flag on account creation pages and update the visible label:
<button type="button" f-auth-provider="github" f-auth-mode="signup" f-auth-return-to="/dashboard">
<span>Sign up with GitHub</span>
</button>
Frontbacked shows provider buttons only when that provider is available for the site. If a provider is turned off, the button stays hidden so the email/password form can remain the visible fallback.
If a theme uses an unsupported provider id, Frontbacked renders a visible setup error inside the element. This catches mistakes such as f-auth-provider="facebook" before the theme ships.
Frontbacked infers whether the visitor is signing in or signing up from the page class or path. Use f-auth-mode="signup" or f-auth-mode="signin" only when a custom route cannot be inferred cleanly. f-auth-return-to should be a local path that the visitor can open after sign-in.
f-google-auth still works as a backwards-compatible alias for f-auth-provider="google".
After the provider flow finishes, Frontbacked completes sign-in and updates $auth automatically. Theme developers do not need to write provider-specific sign-in code.
In local development, provider flows use simulated accounts so you can try sign-in, admin-only pages, and fb-admin access without connecting real provider credentials. Published sites use the real provider flow when the provider is configured and enabled.
The same flow can be started from JavaScript:
Frontbacked.signInWithProvider("google", { returnTo: "/dashboard" });
Frontbacked.signUpWithProvider("github", { returnTo: "/dashboard" });
Frontbacked.signInWithGoogle(options?) and Frontbacked.signUpWithGoogle(options?) are Google-specific aliases. Custom callback pages can use Frontbacked.completeProviderAuth({ provider, ticket, rememberMe }), though most themes should let the normal provider flow finish automatically.
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.requestPasswordReset({
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.state.get("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.state.get("password.currentPassword"),
newPassword: Frontbacked.state.get("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.state.get("profile.phone"),
country: Frontbacked.state.get("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.
Create a Post
Use createPost for new posts.
const result = await Frontbacked.createPost({
type: "products",
post: {
title: Frontbacked.state.get("form.title"),
price: Frontbacked.state.get("form.price"),
image: Frontbacked.state.get("form.image")
}
});
Options:
| Option | Required | Purpose |
|---|---|---|
type | yes | FRL post type, such as products. |
post | yes | Post 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.createPost({
type: "products",
post: {
title: Frontbacked.state.get("form.title"),
image: Frontbacked.state.get("form.image")
},
onUploadProgress(progress) {
Frontbacked.state.set(`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.
Frontbacked.uploadPost(...) remains available as a compatibility alias for Frontbacked.createPost(...).
Update a Post
Use updatePost for existing posts.
const result = await Frontbacked.updatePost({
id: Frontbacked.state.get("product.id"),
type: "products",
post: {
title: Frontbacked.state.get("form.title"),
price: Frontbacked.state.get("form.price")
},
merge: true
});
Options:
| Option | Required | Purpose |
|---|---|---|
id | yes | Existing post id. |
type | no | Post type when Frontbacked needs it. |
post | yes | Patch or replacement data. |
merge | no | Defaults 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 editableIf in FRL to protect fields that must not change through merged updates.
Delete a Post
const result = await Frontbacked.deletePost({
id: Frontbacked.state.get("product.id")
});
Frontbacked runs the post type's allow delete 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.createPost({
type: "products",
post: {
title: Frontbacked.state.get("form.title"),
price: Frontbacked.state.get("form.price"),
image: Frontbacked.state.get("form.image"),
referenceId: "$id"
}
});
return response.ok ? "Saved" : (response.error || "Could not save");
}
Frontbacked.functions.define({ toNumber, 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.state.set("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.state.check(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.state.check("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.state.check("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.state.check("form.firstName", window.authSchemas.signUp.firstName, check);
const emailResult = Frontbacked.state.check("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 check | Example message |
|---|---|
required | Please enter your first name. |
min | First name must be at least 2 characters. |
max | First name must be 60 characters or fewer. |
email | Please enter a valid email address. |
sameAs | Confirm password must match password. |
accepted | Please accept terms. |
maxSize | Avatar 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:
| Value | Result |
|---|---|
"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 Frontbacked.state.check() 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"
>
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="off" 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. Older themes may still use f-loading-disabled="true" for the same behavior.
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.