Repository: meilisearch/meilisearch-go

Issue:
[v1.26] Add tasks custom metadata
## Description

[Meilisearch v1.26](https://github.com/meilisearch/meilisearch/releases/tag/v1.26.0) allows attaching custom metadata to document addition or update tasks.

We want to update this SDK's methods for documents addition and update to accept an optional metadata string.

```
POST /indexes/{indexUid}/documents
PUT /indexes/{indexUid}/documents
DELETE /indexes/{indexUid}/documents/{documentId}
POST /indexes/{indexUid}/documents/delete-batch
POST /indexes/{indexUid}/documents/delete
POST /indexes/{indexUid}/documents/edit
DELETE /indexes/{indexUid}/documents
```

For more information, read the [usage guide](https://github.com/meilisearch/meilisearch/releases/tag/v1.26.0).

## Tasks

- [ ] Update all methods that interact with the listed API endpoints
- [ ] Add test cases

Discussion:

Solution patch:
diff --git a/examples/add_documents/main.go b/examples/add_documents/main.go
index d45f083..9b708a1 100644
--- a/examples/add_documents/main.go
+++ b/examples/add_documents/main.go
@@ -53,7 +53,7 @@ func main() {
 		{ID: 3, Name: "Charlie Brown", Email: "charlie@example.com", Role: "moderator", Active: false, JoinDate: "2023-03-10"},
 	}
 
-	task, err := index.AddDocuments(users, meilisearch.StringPtr("id"))
+	task, err := index.AddDocuments(users, &meilisearch.DocumentOptions{PrimaryKey: meilisearch.StringPtr("id")})
 	if err != nil {
 		log.Fatalf("Failed to add documents: %v", err)
 	}
@@ -127,7 +127,7 @@ func main() {
 
 	// 6. Delete documents
 	fmt.Println("\n6. Deleting documents:")
-	task, err = index.DeleteDocument("4")
+	task, err = index.DeleteDocument("4", nil)
 	if err != nil {
 		log.Fatalf("Failed to delete document: %v", err)
 	}
@@ -139,7 +139,7 @@ func main() {
 
 	// 7. Delete multiple documents
 	fmt.Println("\n7. Deleting multiple documents:")
-	task, err = index.DeleteDocuments([]string{"1", "3"})
+	task, err = index.DeleteDocuments([]string{"1", "3"}, nil)
 	if err != nil {
 		log.Fatalf("Failed to delete documents: %v", err)
 	}
diff --git a/examples/chat_stream/main.go b/examples/chat_stream/main.go
index 8e4406e..893c5eb 100644
--- a/examples/chat_stream/main.go
+++ b/examples/chat_stream/main.go
@@ -55,7 +55,7 @@ func main() {
 
 	fmt.Println("\n--- Interactive Chat Session ---")
 	fmt.Println("Type your questions (or 'quit' to exit):")
-	
+
 	// Simple interactive loop
 	reader := bufio.NewReader(os.Stdin)
 	for {
@@ -75,7 +75,7 @@ func main() {
 func performChatStream(client meilisearch.ServiceManager, workspaceID string, query string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
 	defer cancel()
-	
+
 	// Create a chat completion query with streaming enabled
 	chatQuery := &meilisearch.ChatCompletionQuery{
 		Model: "gpt-3.5-turbo",
@@ -97,10 +97,14 @@ func performChatStream(client meilisearch.ServiceManager, workspaceID string, qu
 	if err != nil {
 		return fmt.Errorf("failed to start chat stream: %w", err)
 	}
-	defer func() { if closeErr := stream.Close(); closeErr != nil { log.Printf("Error closing stream: %v", closeErr) } }()
-	
+	defer func() {
+		if closeErr := stream.Close(); closeErr != nil {
+			log.Printf("Error closing stream: %v", closeErr)
+		}
+	}()
+
 	fmt.Print("Assistant: ")
-	
+
 	for {
 		hasNext := stream.Next()
 		if !hasNext {
@@ -112,9 +116,9 @@ func performChatStream(client meilisearch.ServiceManager, workspaceID string, qu
 			}
 			break
 		}
-		
+
 		chunk := stream.Current()
-		
+
 		// Print the streaming content from choices
 		if chunk != nil && len(chunk.Choices) > 0 {
 			if chunk.Choices[0].Delta.Content != nil && *chunk.Choices[0].Delta.Content != "" {
diff --git a/examples/create_index_settings/main.go b/examples/create_index_settings/main.go
index 5e9b2cb..e17b3ea 100644
--- a/examples/create_index_settings/main.go
+++ b/examples/create_index_settings/main.go
@@ -50,39 +50,39 @@ func main() {
 	settings := &meilisearch.Settings{
 		// Searchable attributes - fields that are searched
 		SearchableAttributes: []string{"title", "content", "author", "tags"},
-		
+
 		// Displayed attributes - fields returned in search results
 		DisplayedAttributes: []string{"id", "title", "author", "publish_date", "category", "summary"},
-		
+
 		// Filterable attributes - fields that can be used in filters
 		FilterableAttributes: []string{"category", "author", "publish_date", "status", "featured"},
-		
+
 		// Sortable attributes - fields that can be used for sorting
 		SortableAttributes: []string{"publish_date", "title", "author"},
-		
+
 		// Ranking rules - control result relevance
 		RankingRules: []string{
 			"words",
-			"typo", 
+			"typo",
 			"proximity",
 			"attribute",
 			"sort",
 			"exactness",
 		},
-		
+
 		// Stop words - words ignored during search
 		StopWords: []string{"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by"},
-		
+
 		// Synonyms - alternative words
 		Synonyms: map[string][]string{
 			"programming": {"coding", "development"},
 			"javascript":  {"js", "ecmascript"},
 			"golang":      {"go"},
 		},
-		
+
 		// Distinct attribute - deduplicate results
 		DistinctAttribute: stringPtr("title"),
-		
+
 		// Typo tolerance settings
 		TypoTolerance: &meilisearch.TypoTolerance{
 			Enabled: true,
@@ -91,7 +91,7 @@ func main() {
 				TwoTypos: 9,
 			},
 		},
-		
+
 		// Pagination settings
 		Pagination: &meilisearch.Pagination{
 			MaxTotalHits: 1000,
@@ -102,7 +102,7 @@ func main() {
 	if err != nil {
 		log.Fatalf("Failed to update settings: %v", err)
 	}
-	
+
 	if err := waitForTask(client, settingsTask.TaskUID); err != nil {
 		log.Fatalf("Failed to wait for settings update: %v", err)
 	}
@@ -114,7 +114,7 @@ func main() {
 	if err != nil {
 		log.Fatalf("Failed to get settings: %v", err)
 	}
-	
+
 	fmt.Printf("Searchable attributes: %v\n", currentSettings.SearchableAttributes)
 	fmt.Printf("Filterable attributes: %v\n", currentSettings.FilterableAttributes)
 	fmt.Printf("Sortable attributes: %v\n", currentSettings.SortableAttributes)
@@ -143,4 +143,4 @@ func getenv(key, def string) string {
 		return v
 	}
 	return def
-}
\ No newline at end of file
+}
diff --git a/examples/search/main.go b/examples/search/main.go
index 2036919..f1239ae 100644
--- a/examples/search/main.go
+++ b/examples/search/main.go
@@ -35,7 +35,7 @@ func main() {
 	}
 
 	fmt.Printf("Adding %d movies to the index...\n", len(movies))
-	task, err := index.AddDocuments(movies, meilisearch.StringPtr("id"))
+	task, err := index.AddDocuments(movies, &meilisearch.DocumentOptions{PrimaryKey: meilisearch.StringPtr("id")})
 	if err != nil {
 		log.Fatalf("Failed to add documents: %v", err)
 	}
diff --git a/helper.go b/helper.go
index 4b2e230..3fb15e8 100644
--- a/helper.go
+++ b/helper.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"context"
 	"encoding/csv"
-	"encoding/json"
 	"fmt"
 	"net/url"
 	"regexp"
@@ -104,10 +103,25 @@ func transformStringToMap(primaryKey *string) (options map[string]string) {
 }
 
 func transformCsvDocumentsQueryToMap(options *CsvDocumentsQuery) map[string]string {
-	var optionsMap map[string]string
-	data, _ := json.Marshal(options)
-	_ = json.Unmarshal(data, &optionsMap)
-	return optionsMap
+	if options == nil {
+		return nil
+	}
+
+	m := make(map[string]string)
+
+	if options.PrimaryKey != "" {
+		m["primaryKey"] = options.PrimaryKey
+	}
+
+	if options.CsvDelimiter != "" {
+		m["csvDelimiter"] = options.CsvDelimiter
+	}
+
+	if options.TaskCustomMetadata != "" {
+		m["customMetadata"] = options.TaskCustomMetadata
+	}
+
+	return m
 }
 
 func generateQueryForOptions(options map[string]string) (urlQuery string) {
@@ -153,3 +167,22 @@ func joinString(vals []string) string {
 
 	return strings.Join(vals, ",")
 }
+func transformDocumentOptionsToMap(opts *DocumentOptions) map[string]string {
+	if opts == nil {
+		return nil
+	}
+
+	m := make(map[string]string)
+
+	// Handle Primary Key
+	if opts.PrimaryKey != nil {
+		m["primaryKey"] = *opts.PrimaryKey
+	}
+
+	// Handle Custom Metadata (This works even with `json:"-"`)
+	if opts.TaskCustomMetadata != "" {
+		m["customMetadata"] = opts.TaskCustomMetadata
+	}
+
+	return m
+}
diff --git a/index_document.go b/index_document.go
index a125fb4..ad46ffb 100644
--- a/index_document.go
+++ b/index_document.go
@@ -13,20 +13,20 @@ import (
 	"strings"
 )
 
-func (i *index) AddDocuments(documentsPtr interface{}, primaryKey *string) (*TaskInfo, error) {
-	return i.AddDocumentsWithContext(context.Background(), documentsPtr, primaryKey)
+func (i *index) AddDocuments(documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.AddDocumentsWithContext(context.Background(), documentsPtr, opts)
 }
 
-func (i *index) AddDocumentsWithContext(ctx context.Context, documentsPtr interface{}, primaryKey *string) (*TaskInfo, error) {
-	return i.addDocuments(ctx, documentsPtr, contentTypeJSON, transformStringToMap(primaryKey))
+func (i *index) AddDocumentsWithContext(ctx context.Context, documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.addDocuments(ctx, documentsPtr, contentTypeJSON, transformDocumentOptionsToMap(opts))
 }
 
-func (i *index) AddDocumentsInBatches(documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.AddDocumentsInBatchesWithContext(context.Background(), documentsPtr, batchSize, primaryKey)
+func (i *index) AddDocumentsInBatches(documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.AddDocumentsInBatchesWithContext(context.Background(), documentsPtr, batchSize, opts)
 }
 
-func (i *index) AddDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.saveDocumentsInBatches(ctx, documentsPtr, batchSize, i.AddDocumentsWithContext, primaryKey)
+func (i *index) AddDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.saveDocumentsInBatches(ctx, documentsPtr, batchSize, i.AddDocumentsWithContext, opts)
 }
 
 func (i *index) AddDocumentsCsv(documents []byte, options *CsvDocumentsQuery) (*TaskInfo, error) {
@@ -62,30 +62,29 @@ func (i *index) AddDocumentsCsvFromReader(documents io.Reader, options *CsvDocum
 func (i *index) AddDocumentsCsvFromReaderWithContext(ctx context.Context, documents io.Reader, options *CsvDocumentsQuery) (resp *TaskInfo, err error) {
 	return i.addDocumentsFromReader(ctx, documents, contentTypeCSV, transformCsvDocumentsQueryToMap(options))
 }
-
-func (i *index) AddDocumentsNdjson(documents []byte, primaryKey *string) (*TaskInfo, error) {
-	return i.AddDocumentsNdjsonWithContext(context.Background(), documents, primaryKey)
+func (i *index) AddDocumentsNdjson(documents []byte, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.AddDocumentsNdjsonWithContext(context.Background(), documents, opts)
 }
 
-func (i *index) AddDocumentsNdjsonWithContext(ctx context.Context, documents []byte, primaryKey *string) (*TaskInfo, error) {
+func (i *index) AddDocumentsNdjsonWithContext(ctx context.Context, documents []byte, opts *DocumentOptions) (*TaskInfo, error) {
 	// []byte avoids JSON conversion in Client.sendRequest()
-	return i.addDocumentsFromReader(ctx, bytes.NewReader(documents), contentTypeNDJSON, transformStringToMap(primaryKey))
+	return i.addDocumentsFromReader(ctx, bytes.NewReader(documents), contentTypeNDJSON, transformDocumentOptionsToMap(opts))
 }
 
-func (i *index) AddDocumentsNdjsonInBatches(documents []byte, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.AddDocumentsNdjsonInBatchesWithContext(context.Background(), documents, batchSize, primaryKey)
+func (i *index) AddDocumentsNdjsonInBatches(documents []byte, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.AddDocumentsNdjsonInBatchesWithContext(context.Background(), documents, batchSize, opts)
 }
 
-func (i *index) AddDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchSize int, primaryKey *string) ([]TaskInfo, error) {
+func (i *index) AddDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
 	// Reuse io.Reader implementation
-	return i.AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx, bytes.NewReader(documents), batchSize, primaryKey)
+	return i.AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx, bytes.NewReader(documents), batchSize, opts)
 }
 
-func (i *index) AddDocumentsNdjsonFromReaderInBatches(documents io.Reader, batchSize int, primaryKey *string) (resp []TaskInfo, err error) {
-	return i.AddDocumentsNdjsonFromReaderInBatchesWithContext(context.Background(), documents, batchSize, primaryKey)
+func (i *index) AddDocumentsNdjsonFromReaderInBatches(documents io.Reader, batchSize int, opts *DocumentOptions) (resp []TaskInfo, err error) {
+	return i.AddDocumentsNdjsonFromReaderInBatchesWithContext(context.Background(), documents, batchSize, opts)
 }
 
-func (i *index) AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx context.Context, documents io.Reader, batchSize int, primaryKey *string) (resp []TaskInfo, err error) {
+func (i *index) AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx context.Context, documents io.Reader, batchSize int, opts *DocumentOptions) (resp []TaskInfo, err error) {
 	// NDJSON files supposed to contain a valid JSON document in each line, so
 	// it's safe to split by lines.
 	// Lines are read and sent continuously to avoid reading all content into
@@ -105,7 +104,7 @@ func (i *index) AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx context.Con
 			}
 		}
 
-		resp, err := i.AddDocumentsNdjsonWithContext(ctx, b.Bytes(), primaryKey)
+		resp, err := i.AddDocumentsNdjsonWithContext(ctx, b.Bytes(), opts)
 		if err != nil {
 			return nil, err
 		}
@@ -156,34 +155,34 @@ func (i *index) AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx context.Con
 	return responses, nil
 }
 
-func (i *index) AddDocumentsNdjsonFromReader(documents io.Reader, primaryKey *string) (resp *TaskInfo, err error) {
-	return i.AddDocumentsNdjsonFromReaderWithContext(context.Background(), documents, primaryKey)
+func (i *index) AddDocumentsNdjsonFromReader(documents io.Reader, opts *DocumentOptions) (resp *TaskInfo, err error) {
+	return i.AddDocumentsNdjsonFromReaderWithContext(context.Background(), documents, opts)
 }
 
-func (i *index) AddDocumentsNdjsonFromReaderWithContext(ctx context.Context, documents io.Reader, primaryKey *string) (resp *TaskInfo, err error) {
+func (i *index) AddDocumentsNdjsonFromReaderWithContext(ctx context.Context, documents io.Reader, opts *DocumentOptions) (resp *TaskInfo, err error) {
 	// Using io.Reader would avoid JSON conversion in Client.sendRequest(), but
 	// read content to memory anyway because of problems with streamed bodies
 	data, err := io.ReadAll(documents)
 	if err != nil {
 		return nil, fmt.Errorf("could not read documents: %w", err)
 	}
-	return i.addDocuments(ctx, data, contentTypeNDJSON, transformStringToMap(primaryKey))
+	return i.addDocuments(ctx, data, contentTypeNDJSON, transformDocumentOptionsToMap(opts))
 }
 
-func (i *index) UpdateDocuments(documentsPtr interface{}, primaryKey *string) (*TaskInfo, error) {
-	return i.UpdateDocumentsWithContext(context.Background(), documentsPtr, primaryKey)
+func (i *index) UpdateDocuments(documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.UpdateDocumentsWithContext(context.Background(), documentsPtr, opts)
 }
 
-func (i *index) UpdateDocumentsWithContext(ctx context.Context, documentsPtr interface{}, primaryKey *string) (*TaskInfo, error) {
-	return i.updateDocuments(ctx, documentsPtr, contentTypeJSON, transformStringToMap(primaryKey))
+func (i *index) UpdateDocumentsWithContext(ctx context.Context, documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.updateDocuments(ctx, documentsPtr, contentTypeJSON, transformDocumentOptionsToMap(opts))
 }
 
-func (i *index) UpdateDocumentsInBatches(documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.UpdateDocumentsInBatchesWithContext(context.Background(), documentsPtr, batchSize, primaryKey)
+func (i *index) UpdateDocumentsInBatches(documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.UpdateDocumentsInBatchesWithContext(context.Background(), documentsPtr, batchSize, opts)
 }
 
-func (i *index) UpdateDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.saveDocumentsInBatches(ctx, documentsPtr, batchSize, i.UpdateDocumentsWithContext, primaryKey)
+func (i *index) UpdateDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.saveDocumentsInBatches(ctx, documentsPtr, batchSize, i.UpdateDocumentsWithContext, opts)
 }
 
 func (i *index) UpdateDocumentsCsv(documents []byte, options *CsvDocumentsQuery) (*TaskInfo, error) {
@@ -203,20 +202,20 @@ func (i *index) UpdateDocumentsCsvInBatchesWithContext(ctx context.Context, docu
 	return i.updateDocumentsCsvFromReaderInBatches(ctx, bytes.NewReader(documents), batchSize, options)
 }
 
-func (i *index) UpdateDocumentsNdjson(documents []byte, primaryKey *string) (*TaskInfo, error) {
-	return i.UpdateDocumentsNdjsonWithContext(context.Background(), documents, primaryKey)
+func (i *index) UpdateDocumentsNdjson(documents []byte, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.UpdateDocumentsNdjsonWithContext(context.Background(), documents, opts)
 }
 
-func (i *index) UpdateDocumentsNdjsonWithContext(ctx context.Context, documents []byte, primaryKey *string) (*TaskInfo, error) {
-	return i.updateDocuments(ctx, documents, contentTypeNDJSON, transformStringToMap(primaryKey))
+func (i *index) UpdateDocumentsNdjsonWithContext(ctx context.Context, documents []byte, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.updateDocuments(ctx, documents, contentTypeNDJSON, transformDocumentOptionsToMap(opts))
 }
 
-func (i *index) UpdateDocumentsNdjsonInBatches(documents []byte, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.UpdateDocumentsNdjsonInBatchesWithContext(context.Background(), documents, batchSize, primaryKey)
+func (i *index) UpdateDocumentsNdjsonInBatches(documents []byte, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.UpdateDocumentsNdjsonInBatchesWithContext(context.Background(), documents, batchSize, opts)
 }
 
-func (i *index) UpdateDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchSize int, primaryKey *string) ([]TaskInfo, error) {
-	return i.updateDocumentsNdjsonFromReaderInBatches(ctx, bytes.NewReader(documents), batchSize, primaryKey)
+func (i *index) UpdateDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchSize int, opts *DocumentOptions) ([]TaskInfo, error) {
+	return i.updateDocumentsNdjsonFromReaderInBatches(ctx, bytes.NewReader(documents), batchSize, opts)
 }
 
 func (i *index) UpdateDocumentsByFunction(req *UpdateDocumentByFunctionRequest) (*TaskInfo, error) {
@@ -234,6 +233,11 @@ func (i *index) UpdateDocumentsByFunctionWithContext(ctx context.Context, req *U
 		acceptedStatusCodes: []int{http.StatusAccepted},
 		functionName:        "UpdateDocumentsByFunction",
 	}
+	if req.TaskCustomMetadata != "" {
+		r.withQueryParams = map[string]string{
+			"customMetadata": req.TaskCustomMetadata,
+		}
+	}
 	if err := i.client.executeRequest(ctx, r); err != nil {
 		return nil, err
 	}
@@ -291,17 +295,18 @@ func (i *index) GetDocumentsWithContext(ctx context.Context, param *DocumentsQue
 	return nil
 }
 
-func (i *index) DeleteDocument(identifier string) (*TaskInfo, error) {
-	return i.DeleteDocumentWithContext(context.Background(), identifier)
+func (i *index) DeleteDocument(identifier string, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.DeleteDocumentWithContext(context.Background(), identifier, opts)
 }
 
-func (i *index) DeleteDocumentWithContext(ctx context.Context, identifier string) (*TaskInfo, error) {
+func (i *index) DeleteDocumentWithContext(ctx context.Context, identifier string, opts *DocumentOptions) (*TaskInfo, error) {
 	resp := new(TaskInfo)
 	req := &internalRequest{
 		endpoint:            "/indexes/" + i.uid + "/documents/" + identifier,
 		method:              http.MethodDelete,
 		withRequest:         nil,
 		withResponse:        resp,
+		withQueryParams:     transformDocumentOptionsToMap(opts),
 		acceptedStatusCodes: []int{http.StatusAccepted},
 		functionName:        "DeleteDocument",
 	}
@@ -311,11 +316,11 @@ func (i *index) DeleteDocumentWithContext(ctx context.Context, identifier string
 	return resp, nil
 }
 
-func (i *index) DeleteDocuments(identifiers []string) (*TaskInfo, error) {
-	return i.DeleteDocumentsWithContext(context.Background(), identifiers)
+func (i *index) DeleteDocuments(identifiers []string, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.DeleteDocumentsWithContext(context.Background(), identifiers, opts)
 }
 
-func (i *index) DeleteDocumentsWithContext(ctx context.Context, identifiers []string) (*TaskInfo, error) {
+func (i *index) DeleteDocumentsWithContext(ctx context.Context, identifiers []string, opts *DocumentOptions) (*TaskInfo, error) {
 	resp := new(TaskInfo)
 	req := &internalRequest{
 		endpoint:            "/indexes/" + i.uid + "/documents/delete-batch",
@@ -323,6 +328,7 @@ func (i *index) DeleteDocumentsWithContext(ctx context.Context, identifiers []st
 		contentType:         contentTypeJSON,
 		withRequest:         identifiers,
 		withResponse:        resp,
+		withQueryParams:     transformDocumentOptionsToMap(opts),
 		acceptedStatusCodes: []int{http.StatusAccepted},
 		functionName:        "DeleteDocuments",
 	}
@@ -332,11 +338,11 @@ func (i *index) DeleteDocumentsWithContext(ctx context.Context, identifiers []st
 	return resp, nil
 }
 
-func (i *index) DeleteDocumentsByFilter(filter interface{}) (*TaskInfo, error) {
-	return i.DeleteDocumentsByFilterWithContext(context.Background(), filter)
+func (i *index) DeleteDocumentsByFilter(filter interface{}, opts *DocumentOptions) (*TaskInfo, error) {
+	return i.DeleteDocumentsByFilterWithContext(context.Background(), filter, opts)
 }
 
-func (i *index) DeleteDocumentsByFilterWithContext(ctx context.Context, filter interface{}) (*TaskInfo, error) {
+func (i *index) DeleteDocumentsByFilterWithContext(ctx context.Context, filter interface{}, opts *DocumentOptions) (*TaskInfo, error) {
 	resp := new(TaskInfo)
 	req := &internalRequest{
 		endpoint:    "/indexes/" + i.uid + "/documents/delete",
@@ -346,6 +352,7 @@ func (i *index) DeleteDocumentsByFilterWithContext(ctx context.Context, filter i
 			"filter": filter,
 		},
 		withResponse:        resp,
+		withQueryParams:     transformDocumentOptionsToMap(opts),
 		acceptedStatusCodes: []int{http.StatusAccepted},
 		functionName:        "DeleteDocumentsByFilter",
 	}
@@ -355,17 +362,18 @@ func (i *index) DeleteDocumentsByFilterWithContext(ctx context.Context, filter i
 	return resp, nil
 }
 
-func (i *index) DeleteAllDocuments() (*TaskInfo, error) {
-	return i.DeleteAllDocumentsWithContext(context.Background())
+func (i *index) DeleteAllDocuments(opts *DocumentOptions) (*TaskInfo, error) {
+	return i.DeleteAllDocumentsWithContext(context.Background(), opts)
 }
 
-func (i *index) DeleteAllDocumentsWithContext(ctx context.Context) (*TaskInfo, error) {
+func (i *index) DeleteAllDocumentsWithContext(ctx context.Context, opts *DocumentOptions) (*TaskInfo, error) {
 	resp := new(TaskInfo)
 	req := &internalRequest{
 		endpoint:            "/indexes/" + i.uid + "/documents",
 		method:              http.MethodDelete,
 		withRequest:         nil,
 		withResponse:        resp,
+		withQueryParams:     transformDocumentOptionsToMap(opts),
 		acceptedStatusCodes: []int{http.StatusAccepted},
 		functionName:        "DeleteAllDocuments",
 	}
@@ -489,7 +497,7 @@ func (i *index) saveDocumentsFromReaderInBatches(ctx context.Context, documents
 	return responses, nil
 }
 
-func (i *index) saveDocumentsInBatches(ctx context.Context, documentsPtr interface{}, batchSize int, documentFunc func(ctx context.Context, documentsPtr interface{}, primaryKey *string) (resp *TaskInfo, err error), primaryKey *string) (resp []TaskInfo, err error) {
+func (i *index) saveDocumentsInBatches(ctx context.Context, documentsPtr interface{}, batchSize int, documentFunc func(ctx context.Context, documentsPtr interface{}, opts *DocumentOptions) (resp *TaskInfo, err error), opts *DocumentOptions) (resp []TaskInfo, err error) {
 	arr := reflect.ValueOf(documentsPtr)
 	lenDocs := arr.Len()
 	numBatches := int(math.Ceil(float64(lenDocs) / float64(batchSize)))
@@ -503,7 +511,7 @@ func (i *index) saveDocumentsInBatches(ctx context.Context, documentsPtr interfa
 
 		batch := arr.Slice(j*batchSize, end).Interface()
 
-		respID, err := documentFunc(ctx, batch, primaryKey)
+		respID, err := documentFunc(ctx, batch, opts)
 		if err != nil {
 			return nil, err
 		}
@@ -547,7 +555,7 @@ func (i *index) updateDocumentsCsvFromReaderInBatches(ctx context.Context, docum
 	return i.saveDocumentsFromReaderInBatches(ctx, documents, batchSize, i.UpdateDocumentsCsvWithContext, options)
 }
 
-func (i *index) updateDocumentsNdjsonFromReaderInBatches(ctx context.Context, documents io.Reader, batchSize int, primaryKey *string) (resp []TaskInfo, err error) {
+func (i *index) updateDocumentsNdjsonFromReaderInBatches(ctx context.Context, documents io.Reader, batchSize int, opts *DocumentOptions) (resp []TaskInfo, err error) {
 	// NDJSON files supposed to contain a valid JSON document in each line, so
 	// it's safe to split by lines.
 	// Lines are read and sent continuously to avoid reading all content into
@@ -567,7 +575,7 @@ func (i *index) updateDocumentsNdjsonFromReaderInBatches(ctx context.Context, do
 			}
 		}
 
-		resp, err := i.UpdateDocumentsNdjsonWithContext(ctx, b.Bytes(), primaryKey)
+		resp, err := i.UpdateDocumentsNdjsonWithContext(ctx, b.Bytes(), opts)
 		if err != nil {
 			return nil, err
 		}
diff --git a/index_interface.go b/index_interface.go
index 5866cd3..6acc196 100644
--- a/index_interface.go
+++ b/index_interface.go
@@ -64,16 +64,16 @@ type DocumentManager interface {
 	DocumentReader
 
 	// AddDocuments adds multiple documents to the index.
-	AddDocuments(documentsPtr interface{}, primaryKey *string) (*TaskInfo, error)
+	AddDocuments(documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error)
 
 	// AddDocumentsWithContext adds multiple documents to the index using the provided context for cancellation.
-	AddDocumentsWithContext(ctx context.Context, documentsPtr interface{}, primaryKey *string) (*TaskInfo, error)
+	AddDocumentsWithContext(ctx context.Context, documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error)
 
 	// AddDocumentsInBatches adds documents to the index in batches of specified size.
-	AddDocumentsInBatches(documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	AddDocumentsInBatches(documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// AddDocumentsInBatchesWithContext adds documents to the index in batches of specified size using the provided context for cancellation.
-	AddDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	AddDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// AddDocumentsCsv adds documents from a CSV byte array to the index.
 	AddDocumentsCsv(documents []byte, options *CsvDocumentsQuery) (*TaskInfo, error)
@@ -100,40 +100,40 @@ type DocumentManager interface {
 	AddDocumentsCsvFromReaderWithContext(ctx context.Context, documents io.Reader, options *CsvDocumentsQuery) (*TaskInfo, error)
 
 	// AddDocumentsNdjson adds documents from a NDJSON byte array to the index.
-	AddDocumentsNdjson(documents []byte, primaryKey *string) (*TaskInfo, error)
+	AddDocumentsNdjson(documents []byte, opts *DocumentOptions) (*TaskInfo, error)
 
 	// AddDocumentsNdjsonWithContext adds documents from a NDJSON byte array to the index using the provided context for cancellation.
-	AddDocumentsNdjsonWithContext(ctx context.Context, documents []byte, primaryKey *string) (*TaskInfo, error)
+	AddDocumentsNdjsonWithContext(ctx context.Context, documents []byte, opts *DocumentOptions) (*TaskInfo, error)
 
 	// AddDocumentsNdjsonInBatches adds documents from a NDJSON byte array to the index in batches of specified size.
-	AddDocumentsNdjsonInBatches(documents []byte, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	AddDocumentsNdjsonInBatches(documents []byte, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// AddDocumentsNdjsonInBatchesWithContext adds documents from a NDJSON byte array to the index in batches of specified size using the provided context for cancellation.
-	AddDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	AddDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// AddDocumentsNdjsonFromReader adds documents from a NDJSON reader to the index.
-	AddDocumentsNdjsonFromReader(documents io.Reader, primaryKey *string) (*TaskInfo, error)
+	AddDocumentsNdjsonFromReader(documents io.Reader, opts *DocumentOptions) (*TaskInfo, error)
 
 	// AddDocumentsNdjsonFromReaderWithContext adds documents from a NDJSON reader to the index using the provided context for cancellation.
-	AddDocumentsNdjsonFromReaderWithContext(ctx context.Context, documents io.Reader, primaryKey *string) (*TaskInfo, error)
+	AddDocumentsNdjsonFromReaderWithContext(ctx context.Context, documents io.Reader, opts *DocumentOptions) (*TaskInfo, error)
 
 	// AddDocumentsNdjsonFromReaderInBatches adds documents from a NDJSON reader to the index in batches of specified size.
-	AddDocumentsNdjsonFromReaderInBatches(documents io.Reader, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	AddDocumentsNdjsonFromReaderInBatches(documents io.Reader, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// AddDocumentsNdjsonFromReaderInBatchesWithContext adds documents from a NDJSON reader to the index in batches of specified size using the provided context for cancellation.
-	AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx context.Context, documents io.Reader, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	AddDocumentsNdjsonFromReaderInBatchesWithContext(ctx context.Context, documents io.Reader, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// UpdateDocuments updates multiple documents in the index.
-	UpdateDocuments(documentsPtr interface{}, primaryKey *string) (*TaskInfo, error)
+	UpdateDocuments(documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error)
 
 	// UpdateDocumentsWithContext updates multiple documents in the index using the provided context for cancellation.
-	UpdateDocumentsWithContext(ctx context.Context, documentsPtr interface{}, primaryKey *string) (*TaskInfo, error)
+	UpdateDocumentsWithContext(ctx context.Context, documentsPtr interface{}, opts *DocumentOptions) (*TaskInfo, error)
 
 	// UpdateDocumentsInBatches updates documents in the index in batches of specified size.
-	UpdateDocumentsInBatches(documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	UpdateDocumentsInBatches(documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// UpdateDocumentsInBatchesWithContext updates documents in the index in batches of specified size using the provided context for cancellation.
-	UpdateDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, primaryKey *string) ([]TaskInfo, error)
+	UpdateDocumentsInBatchesWithContext(ctx context.Context, documentsPtr interface{}, batchSize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// UpdateDocumentsCsv updates documents in the index from a CSV byte array.
 	UpdateDocumentsCsv(documents []byte, options *CsvDocumentsQuery) (*TaskInfo, error)
@@ -148,16 +148,16 @@ type DocumentManager interface {
 	UpdateDocumentsCsvInBatchesWithContext(ctx context.Context, documents []byte, batchsize int, options *CsvDocumentsQuery) ([]TaskInfo, error)
 
 	// UpdateDocumentsNdjson updates documents in the index from a NDJSON byte array.
-	UpdateDocumentsNdjson(documents []byte, primaryKey *string) (*TaskInfo, error)
+	UpdateDocumentsNdjson(documents []byte, opts *DocumentOptions) (*TaskInfo, error)
 
 	// UpdateDocumentsNdjsonWithContext updates documents in the index from a NDJSON byte array using the provided context for cancellation.
-	UpdateDocumentsNdjsonWithContext(ctx context.Context, documents []byte, primaryKey *string) (*TaskInfo, error)
+	UpdateDocumentsNdjsonWithContext(ctx context.Context, documents []byte, opts *DocumentOptions) (*TaskInfo, error)
 
 	// UpdateDocumentsNdjsonInBatches updates documents in the index from a NDJSON byte array in batches of specified size.
-	UpdateDocumentsNdjsonInBatches(documents []byte, batchsize int, primaryKey *string) ([]TaskInfo, error)
+	UpdateDocumentsNdjsonInBatches(documents []byte, batchsize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// UpdateDocumentsNdjsonInBatchesWithContext updates documents in the index from a NDJSON byte array in batches of specified size using the provided context for cancellation.
-	UpdateDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchsize int, primaryKey *string) ([]TaskInfo, error)
+	UpdateDocumentsNdjsonInBatchesWithContext(ctx context.Context, documents []byte, batchsize int, opts *DocumentOptions) ([]TaskInfo, error)
 
 	// UpdateDocumentsByFunction update documents by using function
 	UpdateDocumentsByFunction(req *UpdateDocumentByFunctionRequest) (*TaskInfo, error)
@@ -166,28 +166,28 @@ type DocumentManager interface {
 	UpdateDocumentsByFunctionWithContext(ctx context.Context, req *UpdateDocumentByFunctionRequest) (*TaskInfo, error)
 
 	// DeleteDocument deletes a single document from the index by identifier.
-	DeleteDocument(identifier string) (*TaskInfo, error)
+	DeleteDocument(identifier string, opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteDocumentWithContext deletes a single document from the index by identifier using the provided context for cancellation.
-	DeleteDocumentWithContext(ctx context.Context, identifier string) (*TaskInfo, error)
+	DeleteDocumentWithContext(ctx context.Context, identifier string, opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteDocuments deletes multiple documents from the index by identifiers.
-	DeleteDocuments(identifiers []string) (*TaskInfo, error)
+	DeleteDocuments(identifiers []string, opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteDocumentsWithContext deletes multiple documents from the index by identifiers using the provided context for cancellation.
-	DeleteDocumentsWithContext(ctx context.Context, identifiers []string) (*TaskInfo, error)
+	DeleteDocumentsWithContext(ctx context.Context, identifiers []string, opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteDocumentsByFilter deletes documents from the index by filter.
-	DeleteDocumentsByFilter(filter interface{}) (*TaskInfo, error)
+	DeleteDocumentsByFilter(filter interface{}, opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteDocumentsByFilterWithContext deletes documents from the index by filter using the provided context for cancellation.
-	DeleteDocumentsByFilterWithContext(ctx context.Context, filter interface{}) (*TaskInfo, error)
+	DeleteDocumentsByFilterWithContext(ctx context.Context, filter interface{}, opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteAllDocuments deletes all documents from the index.
-	DeleteAllDocuments() (*TaskInfo, error)
+	DeleteAllDocuments(opts *DocumentOptions) (*TaskInfo, error)
 
 	// DeleteAllDocumentsWithContext deletes all documents from the index using the provided context for cancellation.
-	DeleteAllDocumentsWithContext(ctx context.Context) (*TaskInfo, error)
+	DeleteAllDocumentsWithContext(ctx context.Context, opts *DocumentOptions) (*TaskInfo, error)
 }
 
 type DocumentReader interface {
diff --git a/types.go b/types.go
index 9dd7c13..8652b80 100644
--- a/types.go
+++ b/types.go
@@ -301,19 +301,20 @@ type Stats struct {
 //
 // Documentation: https://www.meilisearch.com/docs/learn/advanced/asynchronous_operations
 type Task struct {
-	Status      TaskStatus          `json:"status"`
-	UID         int64               `json:"uid,omitempty"`
-	TaskUID     int64               `json:"taskUid,omitempty"`
-	IndexUID    string              `json:"indexUid"`
-	Type        TaskType            `json:"type"`
-	Error       meilisearchApiError `json:"error,omitempty"`
-	TaskNetwork TaskNetwork         `json:"network,omitempty"`
-	Duration    string              `json:"duration,omitempty"`
-	EnqueuedAt  time.Time           `json:"enqueuedAt"`
-	StartedAt   time.Time           `json:"startedAt,omitempty"`
-	FinishedAt  time.Time           `json:"finishedAt,omitempty"`
-	Details     Details             `json:"details,omitempty"`
-	CanceledBy  int64               `json:"canceledBy,omitempty"`
+	Status         TaskStatus          `json:"status"`
+	UID            int64               `json:"uid,omitempty"`
+	TaskUID        int64               `json:"taskUid,omitempty"`
+	IndexUID       string              `json:"indexUid"`
+	Type           TaskType            `json:"type"`
+	Error          meilisearchApiError `json:"error,omitempty"`
+	TaskNetwork    TaskNetwork         `json:"network,omitempty"`
+	Duration       string              `json:"duration,omitempty"`
+	EnqueuedAt     time.Time           `json:"enqueuedAt"`
+	StartedAt      time.Time           `json:"startedAt,omitempty"`
+	FinishedAt     time.Time           `json:"finishedAt,omitempty"`
+	Details        Details             `json:"details,omitempty"`
+	CanceledBy     int64               `json:"canceledBy,omitempty"`
+	CustomMetadata string              `json:"customMetadata,omitempty"`
 }
 
 // TaskNetwork indicates information about a task network
@@ -651,9 +652,23 @@ type SimilarDocumentResult struct {
 	EstimatedTotalHits int64  `json:"estimatedTotalHits,omitempty"`
 }
 
+// DocumentOptions is the options struct for adding or updating documents (JSON/NDJSON)
+// and deleting documents.
+type DocumentOptions struct {
+	PrimaryKey *string `json:"primaryKey,omitempty"`
+	// TaskCustomMetadata is the custom metadata to add to the task.
+	// This string will be associated with the task and visible in the task details.
+	// It is optional.
+	TaskCustomMetadata string `json:"-"`
+}
+
 type CsvDocumentsQuery struct {
 	PrimaryKey   string `json:"primaryKey,omitempty"`
 	CsvDelimiter string `json:"csvDelimiter,omitempty"`
+	// TaskCustomMetadata is the custom metadata to add to the task.
+	// This string will be associated with the task and visible in the task details.
+	// It is optional.
+	TaskCustomMetadata string `json:"-"`
 }
 
 type DocumentsResult struct {
@@ -667,6 +682,10 @@ type UpdateDocumentByFunctionRequest struct {
 	Filter   string                 `json:"filter,omitempty"`
 	Function string                 `json:"function"`
 	Context  map[string]interface{} `json:"context,omitempty"`
+	// TaskCustomMetadata is the custom metadata to add to the task.
+	// This string will be associated with the task and visible in the task details.
+	// It is optional.
+	TaskCustomMetadata string `json:"-"`
 }
 
 // ExperimentalFeaturesResult represents the experimental features result from the API.



Test patch:
diff --git a/helper_test.go b/helper_test.go
index 26343c9..da084f5 100644
--- a/helper_test.go
+++ b/helper_test.go
@@ -1,12 +1,13 @@
 package meilisearch
 
 import (
-	"github.com/stretchr/testify/assert"
 	"net/url"
 	"reflect"
 	"strconv"
 	"testing"
 	"time"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func (req *internalRequest) init() {
@@ -154,3 +155,124 @@ func TestJoinString(t *testing.T) {
 		})
 	}
 }
+func TestTransformDocumentOptionsToMap(t *testing.T) {
+	primaryKey := "id"
+	tests := []struct {
+		name     string
+		input    *DocumentOptions
+		expected map[string]string
+	}{
+		{
+			name:     "Nil input",
+			input:    nil,
+			expected: nil,
+		},
+		{
+			name:     "Empty input",
+			input:    &DocumentOptions{},
+			expected: map[string]string{},
+		},
+		{
+			name: "With PrimaryKey",
+			input: &DocumentOptions{
+				PrimaryKey: &primaryKey,
+			},
+			expected: map[string]string{
+				"primaryKey": "id",
+			},
+		},
+		{
+			name: "With TaskCustomMetadata",
+			input: &DocumentOptions{
+				TaskCustomMetadata: "meta-123",
+			},
+			expected: map[string]string{
+				"customMetadata": "meta-123",
+			},
+		},
+		{
+			name: "With Both",
+			input: &DocumentOptions{
+				PrimaryKey:         &primaryKey,
+				TaskCustomMetadata: "meta-123",
+			},
+			expected: map[string]string{
+				"primaryKey":     "id",
+				"customMetadata": "meta-123",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := transformDocumentOptionsToMap(tt.input)
+			assert.Equal(t, tt.expected, got)
+		})
+	}
+}
+
+func TestTransformCsvDocumentsQueryToMap(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CsvDocumentsQuery
+		expected map[string]string
+	}{
+		{
+			name:     "Nil input",
+			input:    nil,
+			expected: nil,
+		},
+		{
+			name:     "Empty input",
+			input:    &CsvDocumentsQuery{},
+			expected: map[string]string{},
+		},
+		{
+			name: "With PrimaryKey",
+			input: &CsvDocumentsQuery{
+				PrimaryKey: "id",
+			},
+			expected: map[string]string{
+				"primaryKey": "id",
+			},
+		},
+		{
+			name: "With CsvDelimiter",
+			input: &CsvDocumentsQuery{
+				CsvDelimiter: ",",
+			},
+			expected: map[string]string{
+				"csvDelimiter": ",",
+			},
+		},
+		{
+			name: "With TaskCustomMetadata",
+			input: &CsvDocumentsQuery{
+				TaskCustomMetadata: "meta-csv",
+			},
+			expected: map[string]string{
+				"customMetadata": "meta-csv",
+			},
+		},
+		{
+			name: "With All",
+			input: &CsvDocumentsQuery{
+				PrimaryKey:         "id",
+				CsvDelimiter:       ";",
+				TaskCustomMetadata: "meta-csv-all",
+			},
+			expected: map[string]string{
+				"primaryKey":     "id",
+				"csvDelimiter":   ";",
+				"customMetadata": "meta-csv-all",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := transformCsvDocumentsQueryToMap(tt.input)
+			assert.Equal(t, tt.expected, got)
+		})
+	}
+}
diff --git a/integration/features_test.go b/integration/features_test.go
index eb0bf82..70877fb 100644
--- a/integration/features_test.go
+++ b/integration/features_test.go
@@ -69,7 +69,7 @@ func TestUpdate_ExperimentalFeatures(t *testing.T) {
 			ef.SetCompositeEmbedders(true)
 			ef.SetChatCompletions(true)
 			ef.SetMultiModal(true)
-			
+
 			gotResp, err := ef.Update()
 			require.NoError(t, err)
 
diff --git a/integration/index_document_test.go b/integration/index_document_test.go
index 45c3b30..e9e404d 100644
--- a/integration/index_document_test.go
+++ b/integration/index_document_test.go
@@ -516,7 +516,7 @@ func TestIndex_AddDocumentsWithPrimaryKey(t *testing.T) {
 			i := c.Index(tt.args.UID)
 			t.Cleanup(cleanup(c))
 
-			gotResp, err := i.AddDocuments(tt.args.documentsPtr, &tt.args.primaryKey)
+			gotResp, err := i.AddDocuments(tt.args.documentsPtr, &meilisearch.DocumentOptions{PrimaryKey: &tt.args.primaryKey})
 			require.NoError(t, err)
 			require.GreaterOrEqual(t, gotResp.TaskUID, tt.resp.wantResp.TaskUID)
 			require.Equal(t, tt.resp.wantResp.Status, gotResp.Status)
@@ -668,7 +668,7 @@ func TestIndex_AddOrUpdateDocumentsInBatches(t *testing.T) {
 			i := c.Index(tt.args.UID)
 			t.Cleanup(cleanup(c))
 
-			gotResp, err := i.AddDocumentsInBatches(tt.args.documentsPtr, tt.args.batchSize, &tt.args.primaryKey)
+			gotResp, err := i.AddDocumentsInBatches(tt.args.documentsPtr, tt.args.batchSize, &meilisearch.DocumentOptions{PrimaryKey: &tt.args.primaryKey})
 
 			require.NoError(t, err)
 			for i := 0; i < 2; i++ {
@@ -1261,7 +1261,7 @@ func TestIndex_DeleteAllDocuments(t *testing.T) {
 			t.Cleanup(cleanup(c))
 
 			setUpBasicIndex(tt.args.client, tt.args.UID)
-			gotResp, err := i.DeleteAllDocuments()
+			gotResp, err := i.DeleteAllDocuments(&meilisearch.DocumentOptions{})
 			require.NoError(t, err)
 			require.GreaterOrEqual(t, gotResp.TaskUID, tt.wantResp.TaskUID)
 			require.Equal(t, tt.wantResp.Status, gotResp.Status)
@@ -1409,7 +1409,7 @@ func TestIndex_DeleteOneDocument(t *testing.T) {
 
 			testWaitForTask(t, i, gotAddResp)
 
-			gotResp, err := i.DeleteDocument(tt.args.identifier)
+			gotResp, err := i.DeleteDocument(tt.args.identifier, nil)
 			require.NoError(t, err)
 			require.GreaterOrEqual(t, gotResp.TaskUID, tt.wantResp.TaskUID)
 			require.Equal(t, tt.wantResp.Status, gotResp.Status)
@@ -1523,7 +1523,7 @@ func TestIndex_DeleteDocuments(t *testing.T) {
 
 			testWaitForTask(t, i, gotAddResp)
 
-			gotResp, err := i.DeleteDocuments(tt.args.identifier)
+			gotResp, err := i.DeleteDocuments(tt.args.identifier, nil)
 			require.NoError(t, err)
 			require.GreaterOrEqual(t, gotResp.TaskUID, tt.wantResp.TaskUID)
 			require.Equal(t, tt.wantResp.Status, gotResp.Status)
@@ -1721,7 +1721,7 @@ func TestIndex_DeleteDocumentsByFilter(t *testing.T) {
 				testWaitForTask(t, i, gotTask)
 			}
 
-			gotResp, err := i.DeleteDocumentsByFilter(tt.args.filterToDelete)
+			gotResp, err := i.DeleteDocumentsByFilter(tt.args.filterToDelete, nil)
 			require.NoError(t, err)
 			require.GreaterOrEqual(t, gotResp.TaskUID, tt.wantResp.TaskUID)
 			require.Equal(t, tt.wantResp.Status, gotResp.Status)
@@ -1770,3 +1770,176 @@ func TestIndex_UpdateDocumentsByFunction(t *testing.T) {
 		testWaitForTask(t, idx, task)
 	})
 }
+func TestIndex_DocumentOperationsWithCustomMetadata(t *testing.T) {
+	sv := setup(t, "")
+	t.Cleanup(cleanup(sv))
+
+	// Setup a basic index
+	indexUID := "TestCustomMetadata"
+	_, err := sv.CreateIndex(&meilisearch.IndexConfig{Uid: indexUID, PrimaryKey: "id"})
+	require.NoError(t, err)
+	i := sv.Index(indexUID)
+
+	filterableAttributes := []interface{}{"id"}
+
+	task, err := i.UpdateFilterableAttributes(&filterableAttributes)
+	require.NoError(t, err)
+	testWaitForTask(t, i, task)
+
+	// Define common test data
+	documents := []map[string]interface{}{
+		{"id": "1", "title": "Document 1"},
+		{"id": "2", "title": "Document 2"},
+	}
+
+	tests := []struct {
+		name           string
+		action         func(t *testing.T) *meilisearch.TaskInfo
+		expectMetadata string
+	}{
+		{
+			name: "AddDocuments with Metadata",
+			action: func(t *testing.T) *meilisearch.TaskInfo {
+				meta := "meta-add-docs"
+				task, err := i.AddDocuments(documents, &meilisearch.DocumentOptions{
+					TaskCustomMetadata: meta,
+				})
+				require.NoError(t, err)
+				return task
+			},
+			expectMetadata: "meta-add-docs",
+		},
+		{
+			name: "UpdateDocuments with Metadata",
+			action: func(t *testing.T) *meilisearch.TaskInfo {
+				meta := "meta-update-docs"
+				updateDocs := []map[string]interface{}{
+					{"id": "1", "title": "Updated Document 1"},
+				}
+				task, err := i.UpdateDocuments(updateDocs, &meilisearch.DocumentOptions{
+					TaskCustomMetadata: meta,
+				})
+				require.NoError(t, err)
+				return task
+			},
+			expectMetadata: "meta-update-docs",
+		},
+		{
+			name: "DeleteDocument (Single) with Metadata",
+			action: func(t *testing.T) *meilisearch.TaskInfo {
+				meta := "meta-delete-one"
+				task, err := i.DeleteDocument("1", &meilisearch.DocumentOptions{
+					TaskCustomMetadata: meta,
+				})
+				require.NoError(t, err)
+				return task
+			},
+			expectMetadata: "meta-delete-one",
+		},
+		{
+			name: "DeleteDocuments (Batch) with Metadata",
+			action: func(t *testing.T) *meilisearch.TaskInfo {
+				meta := "meta-delete-batch"
+				task, err := i.DeleteDocuments([]string{"2"}, &meilisearch.DocumentOptions{
+					TaskCustomMetadata: meta,
+				})
+				require.NoError(t, err)
+				return task
+			},
+			expectMetadata: "meta-delete-batch",
+		},
+		{
+			name: "AddDocumentsNdjson with Metadata",
+			action: func(t *testing.T) *meilisearch.TaskInfo {
+				meta := "meta-ndjson"
+				ndjson := []byte(`{"id": "3", "title": "Ndjson Doc"}`)
+				task, err := i.AddDocumentsNdjson(ndjson, &meilisearch.DocumentOptions{
+					TaskCustomMetadata: meta,
+				})
+				require.NoError(t, err)
+				return task
+			},
+			expectMetadata: "meta-ndjson",
+		},
+		{
+			name: "DeleteAllDocuments with Metadata",
+			action: func(t *testing.T) *meilisearch.TaskInfo {
+				meta := "meta-delete-all"
+				task, err := i.DeleteAllDocuments(&meilisearch.DocumentOptions{
+					TaskCustomMetadata: meta,
+				})
+				require.NoError(t, err)
+				return task
+			},
+			expectMetadata: "meta-delete-all",
+		},
+	}
+
+	// Run Standard Document Option Tests
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// 1. Execute Action
+			taskInfo := tt.action(t)
+
+			// 2. Wait for task to be processed (ensures storage)
+			testWaitForTask(t, i, taskInfo)
+
+			// 3. Fetch the full task details from the engine
+			task, err := sv.GetTask(taskInfo.TaskUID)
+			require.NoError(t, err)
+
+			// 4. Verify the metadata matches
+			require.Equal(t, tt.expectMetadata, task.CustomMetadata, "CustomMetadata should be persisted and retrievable from the Task object")
+		})
+	}
+
+	// Special Case: UpdateDocumentsByFunction
+	t.Run("UpdateDocumentsByFunction with Metadata", func(t *testing.T) {
+		// Ensure we have a doc to update
+		setupTask, _ := i.AddDocuments([]map[string]interface{}{{"id": "99", "title": "Function Doc"}}, nil)
+		testWaitForTask(t, i, setupTask)
+
+		meta := "meta-function-update"
+
+		// Enable feature
+		exp := sv.ExperimentalFeatures()
+		exp.SetEditDocumentsByFunction(true)
+		_, err := exp.Update()
+		require.NoError(t, err)
+
+		// Perform Update
+		taskInfo, err := i.UpdateDocumentsByFunction(&meilisearch.UpdateDocumentByFunctionRequest{
+			Filter:             "id = 99",
+			Function:           "doc.title = \"Updated Function\"",
+			TaskCustomMetadata: meta,
+		})
+		require.NoError(t, err)
+
+		// Wait and Verify
+		testWaitForTask(t, i, taskInfo)
+
+		task, err := sv.GetTask(taskInfo.TaskUID)
+		require.NoError(t, err)
+		require.Equal(t, meta, task.CustomMetadata)
+	})
+
+	// Special Case: DeleteDocumentsByFilter
+	t.Run("DeleteDocumentsByFilter with Metadata", func(t *testing.T) {
+		// Ensure we have a doc to delete
+		setupTask, _ := i.AddDocuments([]map[string]interface{}{{"id": "99", "title": "Filter Doc"}}, nil)
+		testWaitForTask(t, i, setupTask)
+
+		meta := "meta-delete-filter"
+		// Note: "id" was made filterable at the top of the test function
+		taskInfo, err := i.DeleteDocumentsByFilter("id = 99", &meilisearch.DocumentOptions{
+			TaskCustomMetadata: meta,
+		})
+		require.NoError(t, err)
+
+		testWaitForTask(t, i, taskInfo)
+
+		task, err := sv.GetTask(taskInfo.TaskUID)
+		require.NoError(t, err)
+		require.Equal(t, meta, task.CustomMetadata)
+	})
+}