All Posts

Validating distinct data in requests

May 8th, 2026 2 min read

Learn how Laravel's distinct validation rule can help ensure uniqueness across array items in your form requests, preventing ambiguous references before they cause problems downstream.

In building out a role tree from an array of role objects passed via API, I needed to ensure that each person and business reference appeared exactly once.

As I was planning out the process, I noticed that Laravel has a validation rule that handles this exact case: distinct.

class CreateRoleTreeRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            // ...
            'people.*.reference' => [
                'required_with:roles',
                'string',
                'distinct', 
            ],
            'businesses.*.reference' => [
                'required_with:roles',
                'string',
                'distinct', 
            ],
            'roles' => [
                'nullable',
                'array',
            ],
        ];
    }
}

When passing the roles array, we need some way to be able to reference the relationships between these entities.

But from the API consumer perspective, none of these records exist in our system yet, though we still need some way to relate them all together after they have been created in our system.

The expectation is that the API consumer will have some unique identifier on their side to mark each record, then use that identifier to mark the relationship between items.

{
  "people": [
    {
      "reference": "person-1",
      "name": "Michael Dyrynda"
    }
  ],
  "businesses": [
    {
      "reference": "business-1",
      "name": "Laracon Australia"
    }
  ],
  "roles": [
    {
      "type": "people",
      "reference": "person-1",
      "parent": {
        "type": "businesses",
        "reference": "business-1"
      },
      "role": "Director"
    }
  ]
}

We can't make any assumptions about our API consumers' data model, and it may be that they somehow end up sending the same record as a reference for different businesses, sending duplicate references for what should be distinct records.

{
  "people": [
    {
      "reference": "person-1",
      "name": "Michael Dyrynda"
    },
    {
      "reference": "person-1",
      "name": "Michael Dyrynda"
    }
  ],
  "businesses": [
    {
      "reference": "business-1",
      "name": "Laracon Australia"
    },
    {
      "reference": "business-2",
      "name": "Laravel News"
    }
  ],
  "roles": [
    {
      "type": "people",
      "reference": "person-1",
      "parent": {
        "type": "businesses",
        "reference": "business-1"
      },
      "role": "Director"
    },
    {
      "type": "people",
      "reference": "person-1",
      "parent": {
        "type": "businesses",
        "reference": "business-2"
      },
      "role": "Podcast Host"
    }
  ]
}

In this instance, a clear error will be returned to the consumer:

// 422 Unprocessable Entity
{
  "message": "The people.0.reference field has a duplicate value. (and 1 more error)",
  "errors": {
    "people.0.reference": [
      "The people.0.reference field has a duplicate value."
    ],
    "people.1.reference": [
      "The people.1.reference field has a duplicate value."
    ]
  }
}

It's worth noting that distinct uses loose comparisons by default, so if you need to differentiate between 123 and "123", you can use strict parameter.

[
    'people.*.reference' => [
        'distinct:strict',
    ],
]

You can also account for differences in capitilisation using the ignore_case parameter.

[
    'people.*.reference' => [
        'distinct:ignore_case',
    ],
]

The distinct rule catches this at the validation layer, before we'd end up with ambiguous references that silently corrupt the role tree.

Share this article
M

Written by Michael Dyrynda

Principal Engineer, Laravel enthusiast, and open source contributor. I write about web development, PHP, and the problems I solve along the way.