Velo Error: Backend function 'is not a function' despite correct export/import (TypeError on invocation)

Message:

Hello Velo Community,

I urgently need help with a persistent Velo issue on my website. I’m trying to implement a custom registration process for three different user roles (Mitglied, Investor, Mentor). The registration should use the wix-members-backend API, and additional custom fields should be saved in the CRM (wix-crm-backend).

To achieve this, I have three forms on a page (“Register”) and a backend web module (memberRegistration.web.js). The frontend code calls an exported function (registerAndEnhanceMember) from this backend module.

Problem:

Although importing the backend function into the frontend code seems to work (no more “Cannot find module” errors after I corrected the path to backend/memberRegistration.web), I consistently get a TypeError at runtime when calling the function:

TypeError: (...)registerAndEnhanceMember) is not a function

This error occurs after the frontend validation succeeds and the code attempts to execute await registerAndEnhanceMember(memberData);.

Here is the relevant console log sequence when clicking the submit button (example for “Mitglied” role):

Frontend: Registrierungs-Seite bereit.
DEBUG: Submit Button 1 (Mitglied) wurde geklickt!
Frontend: Mitglied-Submit geklickt.
Frontend: getFormData für Mitglied gestartet.
Frontend: Validierung für Mitglied erfolgreich.
Frontend: Rufe Backend registerAndEnhanceMember für [Email Address] auf.
Frontend: Kritischer Fehler beim Aufruf der Backend-Funktion: TypeError: (0 , backend_memberRegistration_web__WEBPACK_IMPORTED_MODULE_0__.registerAndEnhanceMember) is not a function

(Note: The error trace above might reference registrationService.web from a previous test, but the issue is identical with memberRegistration.web)

Troubleshooting Steps Already Taken (Without Success):

  1. Checked Filename/Location: The backend file is exactly named memberRegistration.web.js and located directly in the Backend folder.
  2. Checked Export: The function in memberRegistration.web.js is correctly exported using export async function registerAndEnhanceMember(...).
  3. Checked Import: The frontend import statement is import { registerAndEnhanceMember } from 'backend/memberRegistration.web';. The .web extension was necessary to resolve the initial “Cannot find module” error.
  4. Saved & Published: The website has been saved and fully published multiple times after changes to both frontend and backend code.
  5. Reloaded Editor: The Wix Editor was reloaded multiple times (including hard refreshes).
  6. Simplified Backend Code: I replaced the content of memberRegistration.web.js with a minimal test function that only does a console.log and returns true (see simplified code below) – the ... is not a function error persisted.
  7. Tested Default Export: I tried using export default in the backend and the corresponding import funcName from... in the frontend – the ... is not a function error persisted.
  8. Checked Permissions: The web module permissions are set to allow invocation (“Anyone”).

Code Snippets:

1. Frontend Code (Page Code on page ‘Register’):
(This is the version without visual feedback, using alert(), and the working import path)

// Page Code for the Registration Page

import { registerAndEnhanceMember } from 'backend/memberRegistration.web';

$w.onReady(function () {
    console.log("Frontend: Registrierungs-Seite bereit.");

    // --- Event Listener for Mitglied Form ---
    $w('#submit-one').onClick(async () => {
        console.log("DEBUG: Submit Button 1 (Mitglied) wurde geklickt!");
        console.log("Frontend: Mitglied-Submit geklickt.");
        const memberData = getFormData({
            formName: "Mitglied", roleName: "Mitglied",
            emailInput: $w('#mail-input-one'), usernameInput: $w('#username-input-one'),
            passwordInput: $w('#passwort-input-one'), passwordConfirmInput: $w('#conf-pass-input-one'),
            firstNameInput: $w('#first-name-input-one'), lastNameInput: $w('#last-name-input-one'),
            datenschutzCheckbox: $w('#datenschutz-in-one'), agbCheckbox: $w('#agb-in-one'), nutzungCheckbox: $w('#nutzungsbedingungen-in-one'),
            customFields: {}
        });
        if (memberData) { await submitRegistration(memberData, $w('#submit-one')); }
    });

    // --- Event Listener for Investor Form ---
    $w('#submit-two').onClick(async () => {
        console.log("Frontend: Investor-Submit geklickt.");
        const memberData = getFormData({
            formName: "Investor", roleName: "Investor",
            emailInput: $w('#mail-input-two'), usernameInput: $w('#username-input-two'),
            passwordInput: $w('#passwort-input-two'), passwordConfirmInput: $w('#pass-conf-input-two'),
            firstNameInput: $w('#first-name-input-two'), lastNameInput: $w('#last-name-input-two'),
            datenschutzCheckbox: $w('#datenschutz-in-two'), agbCheckbox: $w('#agb-in-two'), nutzungCheckbox: $w('#nutzungsbedingungen-in-two'),
            customFields: { firmenname: $w('#compname-input-one').value, budget: parseFloat($w('#budget-input-one').value) || null, investmentInteresse: $w('#your-descri-input-one').value }
        });
        if (memberData) { await submitRegistration(memberData, $w('#submit-two')); }
    });

    // --- Event Listener for Mentor Form ---
    $w('#submit-three').onClick(async () => {
        console.log("Frontend: Mentor-Submit geklickt.");
        const memberData = getFormData({
            formName: "Mentor", roleName: "Mentor",
            emailInput: $w('#mail-input-three'), usernameInput: $w('#username-input-three'),
            passwordInput: $w('#pass-in-three'), passwordConfirmInput: $w('#pass-conf-in-three'),
            firstNameInput: $w('#first-name-in-three'), lastNameInput: $w('#last-name-in-three'),
            datenschutzCheckbox: $w('#datenschutz-in-three'), agbCheckbox: $w('#agb-in-three'), nutzungCheckbox: $w('#nutzungsbedingungen-in-three'),
             customFields: { fachgebiete: $w('#division-in-one').value, linkedinProfil: $w('#linkedin-in-one').value, vorstellung: $w('#represent-in-one').value }
        });
         if (memberData) { await submitRegistration(memberData, $w('#submit-three')); }
    });

}); // End of $w.onReady

function getFormData(config) {
    console.log(`Frontend: getFormData for ${config.formName} started.`);
    const email = config.emailInput.value;
    const username = config.usernameInput.value;
    const password = config.passwordInput.value;
    const passwordConfirm = config.passwordConfirmInput.value;
    const firstName = config.firstNameInput.value;
    const lastName = config.lastNameInput.value;
    const datenschutz = config.datenschutzCheckbox.checked;
    const agb = config.agbCheckbox.checked;
    const nutzung = config.nutzungCheckbox.checked;
    let errorMessage = "";
    if (!email || !username || !password || !passwordConfirm || !firstName || !lastName) { errorMessage = "Please fill in all required fields (Email, Username, Password, Name)."; }
    else if (!email.includes('@') || !email.includes('.')) { errorMessage = "Please enter a valid email address."; config.emailInput.updateValidityIndication?.(); }
    else if (password.length < 6) { errorMessage = "Password must be at least 6 characters long."; config.passwordInput.updateValidityIndication?.(); }
    else if (password !== passwordConfirm) { errorMessage = "Passwords do not match."; config.passwordInput.updateValidityIndication?.(); config.passwordConfirmInput.updateValidityIndication?.(); }
    else if (!datenschutz || !agb || !nutzung) { errorMessage = "Please agree to the Privacy Policy, Terms & Conditions, and Terms of Use."; }
    if (errorMessage) {
        console.error(`Frontend Validation Error (${config.formName}):`, errorMessage);
        alert(`Validation Error: ${errorMessage}`);
        return null;
    }
    console.log(`Frontend: Validation for ${config.formName} successful.`);
    return {
        email: email, password: password, roleName: config.roleName,
        firstName: firstName, lastName: lastName, customData: config.customFields || {}
    };
}

async function submitRegistration(memberData, submitButton) {
    submitButton.disable();
    console.log(`Frontend: Calling backend registerAndEnhanceMember for ${memberData.email}`);
    try {
        const result = await registerAndEnhanceMember(memberData); // <-- ERROR OCCURS HERE
        if (result.success) {
            console.log("Frontend: Registration successful in backend:", result.member);
            alert("Registration successful!");
        } else {
            console.error("Frontend: Registration failed in backend:", result.error);
            console.error("User-friendly error message:", result.userErrorMessage);
            alert(`Registration failed: ${result.userErrorMessage || "Unknown error"}`);
        }
    } catch (error) {
        console.error("Frontend: Critical error calling backend function:", error);
        alert("A technical problem occurred. Please try again later.");
    } finally {
        submitButton.enable();
    }
}

2. Backend Code (memberRegistration.web.js):
(This is the functional version with role assignment commented out, which shows no errors in the editor but fails to be called correctly)

// backend/memberRegistration.web.js

import { authentication } from 'wix-members-backend';
// import wixUsersBackend from 'wix-users-backend'; // Role assignment commented out
import { contacts } from 'wix-crm-backend';

// --- Configuration ---
const ROLE_IDS = { /* ... Your Role IDs ... */ };
const CUSTOM_FIELDS = { /* ... Your Custom Field Keys ... */ };

// --- Main Registration Function ---
export async function registerAndEnhanceMember(memberData) {
    const { email, password, roleName, firstName, lastName, customData } = memberData;
    try {
        // 1. Register member
        console.log(`Backend: Attempting registration for ${email} (Role ${roleName} will NOT be assigned)`);
        const registrationResult = await authentication.register(email, password, {
            contactInfo: { firstName: firstName, lastName: lastName }
        });
        const memberId = registrationResult.member._id;
        const contactId = registrationResult.member.contactId;
        console.log(`Backend: Member registered: ${memberId}, Contact ID: ${contactId}`);

        // 2. Role assignment is commented out
        /*
        const roleId = ROLE_IDS[roleName];
        if (roleId) { ... }
        */

        // 3. Update CRM Contact
        const updatePayload = { name: { first: firstName, last: lastName }, customFields: {} };
        let hasCustomFields = false;
        for (const key in customData) { /* ... Code to populate customFields ... */
             if (Object.prototype.hasOwnProperty.call(customData, key) && CUSTOM_FIELDS[key] && customData[key] !== null && customData[key] !== undefined && customData[key] !== '') {
                updatePayload.customFields[CUSTOM_FIELDS[key]] = customData[key];
                hasCustomFields = true;
            }
        }
        if (CUSTOM_FIELDS.rolle) { updatePayload.customFields[CUSTOM_FIELDS.rolle] = roleName; hasCustomFields = true; }

        if (hasCustomFields) {
             console.log(`Backend: Attempting to get contact ${contactId} to retrieve revision.`);
             const contactToUpdate = await contacts.getContact(contactId, { suppressAuth: true });
             const revision = contactToUpdate.revision;
             console.log(`Backend: Revision ${revision} for contact ${contactId} obtained.`);
             await contacts.updateContact({ contactId: contactId, revision: revision }, updatePayload, { suppressAuth: true });
             console.log(`Backend: Contact ${contactId} with revision ${revision} updated.`);
        } else { /* ... no update needed ... */ }
        return { success: true, member: registrationResult.member };

    } catch (error) { /* ... Error handling ... */
        console.error("Backend: Severe error in registerAndEnhanceMember:", error);
        console.log("Error details:", JSON.stringify(error, Object.getOwnPropertyNames(error)));
        let userErrorMessage = "Registration failed...";
        /* ... specific error messages ... */
        return { success: false, error: error.message || 'Unknown error', userErrorMessage: userErrorMessage };
    }
}

3. Backend Code (Simplified Test Version):
(Even using this minimal code in memberRegistration.web.js resulted in the ... is not a function error)

// backend/memberRegistration.web.js (SIMPLIFIED TEST VERSION with NAMED Export)

// Export a very simple function with the expected name
export async function registerAndEnhanceMember(memberData) {

    console.log("--- BACKEND NAMED EXPORT TEST ---");
    console.log("registerAndEnhanceMember was called!");
    console.log("Received data:", JSON.stringify(memberData));
    console.log("--- BACKEND NAMED EXPORT TEST END ---");

    // Return a simple success object
    return {
        success: true,
        member: { _id: 'test-member-id', contactId: 'test-contact-id', revision: 123 }
    };
}

My Request:

Could you please investigate why the function registerAndEnhanceMember exported from backend/memberRegistration.web.js is not recognized as a function at runtime in the frontend, even though the import path seems correct and even a minimal, correctly exported function fails in the same way? Could there be an issue with the Velo build process, module resolution, or a specific conflict on my site?

Link to Editor:

[PLEASE INSERT YOUR EDITOR LINK HERE]

Thank you very much for your help!


Hi, @user5510 !!

Hello. The issue you’re currently facing is a very common and simple misunderstanding, so there’s no need to worry. It should be easy to resolve. The reason you’re unable to call the backend function you defined is because it needs to be written in the .web.js format, but you’ve mistakenly written it in the .jsw format. :innocent:

I’ve resolved a similar issue in the past, so it would be helpful if you could check the details through this link. :blush: