Cannot get form file field to work on Ajax submit in Svelte Kit App. Tried lots of fixes but can't seem to get it to work

I have a form that almost works. The form is on a protected SSR route with a hidden dummy form so Netlify knows it exists. I have tried so many iterations of fixes that I have found that I am worried that I am going to eat up the rest of the form submissions for the month just fumbling around in the dark. Here is the details on the project:

All the fields submit and post correctly except the file upload. I haven’t been trying to upload big files. I haven’t gotten any errors or feedback from Netlify when I submit. I have some debugging console.log’s happening on the https://www.guestcrafter.com/info-we-need route and I can see that my file input is in the formData.

I’ve made sure my dummy form is perfectly matched up with the actual form… Here is the dummy form:

<script context="module">
	export const prerender = true
</script>

<form
    action="/form/"
    name="info"
    data-netlify="true"
    hidden
    enctype="multipart/form-data"
>
    <input type="hidden" name="form-name" value="info" />
    <input type="text" required tabindex="0" name="Name" />
    <input type="text" required tabindex="0" name="Location" />
    <input type="text" required tabindex="0" name="PrimaryURL" />
    <input type="text" required tabindex="0" name="SecondaryURL" />
    <textarea required tabindex="0" name="Biography" maxlength="1000" />
    <textarea required tabindex="0" name="TechSetup" maxlength="500" />
    <input type="email" required tabindex="0" name="Email" />
    <input type="tel" required tabindex="0" name="Phone" />
    <input type="file" tabindex="0" name="Picture" accept=".png, .jpeg, .jpg" />
    <button type="submit">
        Submit Form
    </button>
</form>

Here is the main form with all the JS:

<script>
    let visible = true
    let sending = false
    import { slide } from "svelte/transition"

    let data = {
        Name: undefined,
        Location: undefined,
        PrimaryURL: undefined,
        SecondaryURL: undefined,
        Biography: undefined,
        TechSetup: undefined,
        Email: undefined,
        Phone: undefined
    }

    let form
    let headShot

    const encode = () => {
        const formData = new FormData()
        formData.append('form-name', 'info')
        Object.keys(data).forEach((key)=>{
            formData.append(key, data[key])
        })
        formData.append('Picture', [headShot.files[0]])
        console.log([...formData])
        return formData
    }

    const handleSubmit = event => {
        sending = true                                      // button formatting
        // send the form data
        fetch("/form/", {
            method: "POST",
            headers: { 'Content-Type': 'multipart/form-data' },
            body: encode()
        }).then(() => (visible = false)).catch(error => alert(error))
    }
</script>

{#if visible}
    <form
        action="/form/"
        on:submit|preventDefault={handleSubmit}
        out:slide={{duration: 300}}
        method="POST" data-netlify="true"
        bind:this={form}
        name="info"
        enctype="multipart/form-data"
    >
        <label>
            <header>Display Name</header>
            <input type="text" required tabindex="0" name="Name" bind:value={data.Name} />
            <footer>Example: Mary Gunn or Mary G.</footer>
        </label>
        <label>
            <header>Location</header>
            <input type="text" required tabindex="0" name="Location" bind:value={data.Location}/>
            <footer>Example: Texas or Kansas City (can be general)</footer>
        </label>
        <label>
            <header>Head Shot</header>
            <input type="file" tabindex="0" name="Picture" accept=".png, .jpeg, .jpg" bind:this={headShot} />
            <footer>Your favorite profile pic is great</footer>
        </label>
        <label>
            <header>Primary Link URL</header>
            <input type="text" required tabindex="0" name="PrimaryURL" bind:value={data.PrimaryURL} />
            <footer>Your top link (youtube channel, blog, instagram etc)</footer>
        </label>
        <label>
            <header>Secondary Link URL</header>
            <input type="text" required tabindex="0" name="SecondaryURL" bind:value={data.SecondaryURL}/>
            <footer>Your secondary link (youtube channel, blog, instagram etc)</footer>
        </label>
        <label>
            <header>Short Bio</header>
            <textarea required tabindex="0" name="Biography" maxlength="1000" bind:value={data.Biography} />
            <footer>Used on our website. 1000 characters max.</footer>
        </label>
        <label>
            <header>Tech Setup</header>
            <textarea required tabindex="0" name="TechSetup" maxlength="500" bind:value={data.TechSetup} />
            <footer>Breifly describe your tech setup for making videos. Do you use your phone? Web cam? Etc.</footer>
        </label>

        <h2>Private Contact Info</h2>
        <p>We won't share this information with anyone. We like to have this information just-in-case.</p>
        <label>
            <header>Your Email</header>
            <input type="email" required tabindex="0" name="Email" bind:value={data.Email} />
            <footer>Best email for personal contact.</footer>
        </label>
        <label>
            <header>Phone Number</header>
            <input type="tel" required tabindex="0" name="Phone" bind:value={data.Phone} />
            <footer>Best phone to reach you. (We sometimes text to remind about the show.)</footer>
        </label>
        <input type="hidden" name="form-name" value="info" />
        <button type="submit">
            {#if sending}
                Sending...
            {:else}
                Submit Form
            {/if}
        </button>
        
    </form>
{:else}
    <p in:slide={{delay: 300, duration: 300}}>Thank you for sending in your info. We will reach out if we have any questions.</p>
{/if}

I have tried many many iterations of how I setup my formData and how I posted it. my encode() function builds and returns formData. My ‘Picture’ field is appended last, just like on the dummy form, and I have tried many different formats based on examples I have found here on the forum. None have worked.

Here is the encode function by itself:

    const encode = () => {
        const formData = new FormData()
        formData.append('form-name', 'info')
        Object.keys(data).forEach((key)=>{
            formData.append(key, data[key])
        })
        formData.append('Picture', [headShot.files[0]])
        console.log([...formData])
        return formData
    }

I have also tried encoding the post body as a string and just sending raw formData. Both have no problem posting the text fields to the admin. Neither post the file upload. For example, this works (sans the file upload)

body: new URLSearchParams(encode()).toString()

This also works the same way:

body: encode()

As for my ‘Picture’ form data, I have tried putting the file in an array like this (saw it as an example of a working ajax submit here on the forum)

formData.append('Picture', [headShot.files[0]])

and I have also tried with out the array bracket wrapper:

formData.append('Picture', headShot.files[0])

The file is in the formData when I log it. So I know the .append is working.

I feel like I’m not formatting it correctly. But I am in the dark with out any feedback from the Netlify form software.

If you look at how many times I’ve built my site in the past 48 hours you’ll understand that I might be a little frustrated.

If you want me to provide any other info / code / reasoning / anything please just ask. I would love to solve this and put it behind me.

I’ll even create an example repo for future SvelteKit users once we get it figured out.

Thanks.

Hi @Jovian,

I believe you’re not the only one who’s unable to get file uploads working with AJAX based forms. I had drafted a not-so-friendly solution here:

I call it not-so-friendly because you’d have to rely on functions and would have to make use of an external database to store data.

The thread is slightly different though - it talks about specifically being able to use files inside Functions, but the solution here I believe is the same.

Just before you try this, could you try submitting the file without AJAX - that is from the static version of the form? If that works fine, you’d have to rely on the above solution.

Yes, if I get rid of the javascript functionality and just use the default form action the picture does register in the backend.

I would love to have full control over this with ajax but I can build a work around using this technique. Thanks.