Custom login forms
This is one of the 4 ways to invite a vendor on Garnet.
If you want to build your own login UI instead of using the Garnet login iframe, you can use simple HTML forms to authenticate users.
TIP
The code examples below are minimal working examples. You will need to add your own CSS styling to make them production-ready.
To adapt the form to your marketplace:
- Replace
your-storeby your Shopify store id. - If you are using a custom domain, replace
your-store.garnet.centerwith your custom domain in all forms below. - Edit the email notifications to point to your custom pages.
For password recovery, see password recovery. Every endpoint is described in the API reference.
Example of a custom designed onboarding
Example of a custom login form using HTML and CSS:

Login form
html
<form method="POST" action="https://your-store.garnet.center/auth/public/login" target="_blank">
<label for="email">Email:</label>
<input type="email" name="email" required />
<label for="password">Password:</label>
<input type="password" name="password" required />
<input type="hidden" name="redirect" value="/" />
<button>Submit</button>
</form>Vendor registration
Let new vendors sign up from your own page. The form follows how new vendors join, set in Admin Panel > Vendors > Applications:
Application: post to/api3/public/v2/applications. The application goes to theApplicationstab; accepting it creates the vendor.Open registration: post to/api3/public/v2/vendors. The vendor is created right away.
Either way the vendor receives the invitation email with a link to set their password. The email and vendor name must not already exist on the marketplace.
html
<form id="registrationForm">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
<label for="vendor">Vendor Name:</label>
<input type="text" id="vendor" name="vendor" required />
<!-- Optional profile fields: name="profile[<metafield key>]", data-name is the question shown in Garnet -->
<label for="phone">Phone:</label>
<input type="tel" id="phone" name="profile[phone]" data-name="Phone" />
<label for="instagram">Instagram:</label>
<input type="text" id="instagram" name="profile[instagram]" data-name="Instagram" placeholder="@username" />
<!-- Bot protection: honeypot field (must stay empty) -->
<input type="text" name="website" style="display:none" tabindex="-1" autocomplete="off" />
<button type="submit">Register</button>
<p id="error"></p>
</form>
<script>
const api = 'https://your-store.garnet.center/api3/public';
// Fetch a CSRF token when the page loads
let csrfToken;
fetch(`${api}/csrf-token`)
.then((res) => res.json())
.then((data) => (csrfToken = data.token));
const form = document.getElementById('registrationForm');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = new FormData(form);
const metafields = [...form.querySelectorAll('[name^="profile["]')]
.filter((input) => input.value.trim())
.map((input) => ({ key: input.name.slice(8, -1), name: input.dataset.name, value: input.value.trim() }));
const body = { email: data.get('email'), vendor: data.get('vendor'), website: data.get('website'), csrfToken, metafields };
// `v2/applications` with Application, `v2/vendors` with Open registration
const res = await fetch(`${api}/v2/applications`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (!res.ok) document.getElementById('error').textContent = (await res.json()).message;
else form.textContent = 'Thank you, check your inbox.';
});
</script>The profile fields are optional. They are stored in the vendor's metaobject, so each key must exactly match one of your vendor profile metafield keys (e.g., profile[facebook], profile[company_name]), and its value must be valid for that field, or the API returns a 400 error.
If you are using advanced metafields (like image upload), you will need to write additional logic to convert the user image into a valid value for Shopify metafields.
Bot protection
The registration form includes built-in bot protection: a CSRF token with a time check ensures the form was loaded before submission, and a hidden honeypot field catches automated bots. No external service or configuration is needed.
Important: The CSRF token must be fetched when the page loads, not when the form is submitted. The server enforces a minimum delay between token creation and form submission to block bots. If you fetch the token on submit, the request will be rejected with "Please wait before submitting".