Skip to content

CreateFormData Service

A utility function for converting JavaScript objects into FormData instances with support for nested objects, arrays, files, and proper serialization. Essential for handling complex form submissions that include file uploads alongside regular data.


Function Signature

typescript
function createFormData(data: any): FormData;

Parameters

ParameterTypeDescription
dataanyThe object to convert to FormData

Returns

FormData - A properly formatted FormData instance ready for HTTP submission


Features

Automatic Type Handling

  • Files & Blobs: Appended directly without transformation
  • Arrays: Serialized with indexed bracket notation (key[0], key[1])
  • Nested Objects: Flattened with bracket notation (parent[child])
  • Dates: Converted to ISO string format
  • Primitives: Converted to strings

Null/Undefined Handling

  • Skips null and undefined values
  • Prevents empty or invalid entries in FormData

Recursive Processing

  • Handles deeply nested structures
  • Maintains proper key naming for all levels

Usage Examples

Basic Usage

typescript
import { createFormData } from "dolphin-components";

const data = {
  name: "John Doe",
  email: "[email protected]",
  age: 30,
};

const formData = createFormData(data);

// Result:
// formData.get('name') => 'John Doe'
// formData.get('email') => '[email protected]'
// formData.get('age') => '30'

With File Upload

vue
<script setup>
import { ref } from 'vue';
import createFormData from 'dolphin-components';
import axios from 'axios';

const form = ref({
  title: '',
  description: '',
  image: null as File | null
});

const handleFileSelect = (event: Event) => {
  const target = event.target as HTMLInputElement;
  if (target.files?.[0]) {
    form.value.image = target.files[0];
  }
};

const submitForm = async () => {
  const formData = createFormData(form.value);

  await axios.post('/api/posts', formData, {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  });
};
</script>

<template>
  <form @submit.prevent="submitForm">
    <input v-model="form.title" placeholder="Title" />
    <textarea v-model="form.description" placeholder="Description" />
    <input type="file" @change="handleFileSelect" accept="image/*" />
    <button type="submit">Submit</button>
  </form>
</template>

With Nested Objects

typescript
import createFormData from "dolphin-components";

const userData = {
  user: {
    firstName: "John",
    lastName: "Doe",
    address: {
      street: "123 Main St",
      city: "New York",
      country: "USA",
    },
  },
  settings: {
    newsletter: true,
    notifications: false,
  },
};

const formData = createFormData(userData);

// Result:
// formData.get('user[firstName]') => 'John'
// formData.get('user[lastName]') => 'Doe'
// formData.get('user[address][street]') => '123 Main St'
// formData.get('user[address][city]') => 'New York'
// formData.get('user[address][country]') => 'USA'
// formData.get('settings[newsletter]') => 'true'
// formData.get('settings[notifications]') => 'false'

With Arrays

typescript
import createFormData from "dolphin-components";

const orderData = {
  customerId: 123,
  items: [
    { productId: 1, quantity: 2 },
    { productId: 5, quantity: 1 },
    { productId: 8, quantity: 3 },
  ],
  tags: ["express", "gift-wrap"],
};

const formData = createFormData(orderData);

// Result:
// formData.get('customerId') => '123'
// formData.get('items[0][productId]') => '1'
// formData.get('items[0][quantity]') => '2'
// formData.get('items[1][productId]') => '5'
// formData.get('items[1][quantity]') => '1'
// formData.get('items[2][productId]') => '8'
// formData.get('items[2][quantity]') => '3'
// formData.get('tags[0]') => 'express'
// formData.get('tags[1]') => 'gift-wrap'

With Multiple Files

typescript
import createFormData from "dolphin-components";

const galleryData = {
  albumName: "Vacation 2024",
  images: [file1, file2, file3], // Array of File objects
  metadata: {
    isPublic: true,
    location: "Hawaii",
  },
};

const formData = createFormData(galleryData);

// Result:
// formData.get('albumName') => 'Vacation 2024'
// formData.get('images[0]') => File object
// formData.get('images[1]') => File object
// formData.get('images[2]') => File object
// formData.get('metadata[isPublic]') => 'true'
// formData.get('metadata[location]') => 'Hawaii'

With Dates

typescript
import createFormData from "dolphin-components";

const eventData = {
  title: "Team Meeting",
  startDate: new Date("2024-06-15T10:00:00"),
  endDate: new Date("2024-06-15T11:00:00"),
};

const formData = createFormData(eventData);

// Result:
// formData.get('title') => 'Team Meeting'
// formData.get('startDate') => '2024-06-15T10:00:00.000Z'
// formData.get('endDate') => '2024-06-15T11:00:00.000Z'

Complete Form Example with FileUpload

vue
<script setup>
import { ref } from 'vue';
import { FileUpload, createFormData } from 'dolphin-components';
import axios from 'axios';

const formState = ref({
  product: {
    name: '',
    description: '',
    price: 0,
    category: ''
  },
  mainImage: null as File | null,
  additionalImages: [] as File[],
  specifications: [
    { key: '', value: '' }
  ]
});

const addSpecification = () => {
  formState.value.specifications.push({ key: '', value: '' });
};

const submitProduct = async () => {
  const payload = {
    ...formState.value.product,
    mainImage: formState.value.mainImage,
    additionalImages: formState.value.additionalImages,
    specifications: formState.value.specifications.filter(s => s.key && s.value)
  };

  const formData = createFormData(payload);

  try {
    const response = await axios.post('/api/products', formData, {
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    });
    console.log('Product created:', response.data);
  } catch (error) {
    console.error('Failed to create product:', error);
  }
};
</script>

<template>
  <form @submit.prevent="submitProduct" class="space-y-4">
    <div>
      <label>Product Name</label>
      <input v-model="formState.product.name" class="input-text" />
    </div>

    <div>
      <label>Description</label>
      <textarea v-model="formState.product.description" class="input-text" />
    </div>

    <div>
      <label>Price</label>
      <input
        v-model.number="formState.product.price"
        type="number"
        class="input-text"
      />
    </div>

    <div>
      <label>Main Image</label>
      <FileUpload v-model="formState.mainImage" accept="image/*" />
    </div>

    <div>
      <label>Additional Images</label>
      <FileUpload
        v-model="formState.additionalImages"
        :multiple="true"
        :max-files="5"
        accept="image/*"
      />
    </div>

    <div>
      <label>Specifications</label>
      <div
        v-for="(spec, index) in formState.specifications"
        :key="index"
        class="flex gap-2 mb-2"
      >
        <input v-model="spec.key" placeholder="Key" class="input-text flex-1" />
        <input
          v-model="spec.value"
          placeholder="Value"
          class="input-text flex-1"
        />
      </div>
      <button
        type="button"
        @click="addSpecification"
        class="btn btn-outline-secondary"
      >
        Add Specification
      </button>
    </div>

    <button type="submit" class="btn btn-primary">Create Product</button>
  </form>
</template>

How It Works

Processing Logic

typescript
// 1. Null/undefined values are skipped
if (value === null || value === undefined) return;

// 2. Files and Blobs are appended directly
if (value instanceof File || value instanceof Blob) {
  formData.append(key, value);
}

// 3. Arrays are processed with indexed keys
if (Array.isArray(value)) {
  value.forEach((item, index) => {
    append(`${key}[${index}]`, item);
  });
}

// 4. Objects (except Date) are processed recursively
if (typeof value === "object" && !(value instanceof Date)) {
  Object.keys(value).forEach((subKey) => {
    append(`${key}[${subKey}]`, value[subKey]);
  });
}

// 5. Dates are converted to ISO strings
if (value instanceof Date) {
  formData.append(key, value.toISOString());
}

// 6. All other values are stringified
formData.append(key, String(value));

Server-Side Compatibility

PHP/Laravel

Laravel automatically parses bracket notation into arrays:

php
$request->input('items'); // Returns array of items
$request->input('user.firstName'); // Access nested data
$request->file('mainImage'); // Access uploaded file

Node.js/Express

Use middleware like multer for file handling:

javascript
const multer = require("multer");
const upload = multer({ dest: "uploads/" });

app.post("/api/products", upload.any(), (req, res) => {
  console.log(req.body); // Form fields
  console.log(req.files); // Uploaded files
});

Best Practices

Usage Recommendations

  • Use for complex form submissions with mixed data types
  • Combine with FileUpload component for seamless file handling
  • Always set Content-Type: multipart/form-data in HTTP requests

Error Handling

  • The function handles null/undefined gracefully
  • Invalid Date objects will still be converted to ISO strings
  • Circular references in objects should be avoided

Performance Considerations

  • Large files are not copied, just referenced
  • Suitable for forms with moderate data complexity
  • For very large datasets, consider chunked uploads

Common Use Cases

  • File Upload Forms: Combining text data with file uploads
  • E-commerce: Product creation with images and specifications
  • User Profiles: Profile data with avatar upload
  • CMS Systems: Content with media attachments
  • Multi-part Forms: Any form requiring FormData serialization