PHPackages                             coresh/module-customer-attribute - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. coresh/module-customer-attribute

ActiveMagento2-module[API Development](/categories/api)

coresh/module-customer-attribute
================================

Magento 2 module that adds immutable UUID support for customers and exposes it through GraphQL.

1.1.2(1mo ago)131proprietaryPHPPHP ^8.2 || ^8.3 || ^8.4

Since Jun 11Pushed 1mo agoCompare

[ Source](https://github.com/coresh/module-customer-attribute)[ Packagist](https://packagist.org/packages/coresh/module-customer-attribute)[ RSS](/packages/coresh-module-customer-attribute/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (1)Dependencies (5)Versions (4)Used By (0)

Coresh\_CustomerAttribute Documentation
=======================================

[](#coresh_customerattribute-documentation)

Overview
--------

[](#overview)

The **Coresh\_CustomerAttribute** module adds a stable customer `uuid` attribute to Magento 2 / Adobe Commerce.

The module automatically assigns UUIDs to existing and new customers, enforces UUID uniqueness, displays UUIDs in the Admin customer grid, prevents manual UUID changes, and exposes UUID through GraphQL only for authenticated customers.

Compatibility
-------------

[](#compatibility)

- Magento Open Source 2.4.7+
- Adobe Commerce 2.4.7+
- Magento 2.4.8 / 2.4.9 compatible
- PHP 8.2+
- Composer-installable Magento 2 module

Installation
------------

[](#installation)

### 1. Install the module

[](#1-install-the-module)

Using Composer:

```
composer require coresh/module-customer-attribute
```

If installing manually, place the module in:

```
app/code/Coresh/CustomerAttribute

```

### 2. Enable the module

[](#2-enable-the-module)

```
bin/magento module:enable Coresh_CustomerAttribute
```

### 3. Run Magento setup

[](#3-run-magento-setup)

```
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush
bin/magento indexer:reset customer_grid
bin/magento indexer:reindex customer_grid
```

### 4. Verify module status

[](#4-verify-module-status)

```
bin/magento module:status Coresh_CustomerAttribute
```

Expected result:

```
Coresh_CustomerAttribute

```

Database Verification
---------------------

[](#database-verification)

### Verify the UUID column on `customer_entity`

[](#verify-the-uuid-column-on-customer_entity)

```
SHOW COLUMNS FROM customer_entity LIKE 'uuid';
```

Expected result:

```
uuid | varchar(36)

```

### Verify UUID values for customers

[](#verify-uuid-values-for-customers)

```
SELECT entity_id, email, uuid
FROM customer_entity
ORDER BY entity_id DESC
LIMIT 10;
```

Expected result: each customer should have a UUID value.

### Verify UUID uniqueness

[](#verify-uuid-uniqueness)

```
SELECT uuid, COUNT(*) AS total
FROM customer_entity
WHERE uuid IS NOT NULL AND uuid  ''
GROUP BY uuid
HAVING total > 1;
```

Expected result:

```
Empty set

```

### Verify UUID in the customer grid index

[](#verify-uuid-in-the-customer-grid-index)

```
SHOW COLUMNS FROM customer_grid_flat LIKE 'uuid';
```

Expected result:

```
uuid | varchar(255)

```

Then verify values:

```
SELECT entity_id, email, uuid
FROM customer_grid_flat
ORDER BY entity_id DESC
LIMIT 10;
```

Admin Verification
------------------

[](#admin-verification)

Open Magento Admin:

```
Customers -> All Customers

```

Verify:

```
- The UUID column is visible in the customer grid.
- UUID values are displayed.
- UUID is not editable in the customer edit form.
- Customer grid reindex completes successfully.

```

If the UUID column is not visible immediately, run:

```
bin/magento cache:flush
bin/magento indexer:reset customer_grid
bin/magento indexer:reindex customer_grid
```

GraphQL API Access
------------------

[](#graphql-api-access)

The module extends the existing GraphQL `Customer` object with the `uuid` field.

UUID is available only through authenticated customer GraphQL requests.

### GraphQL query

[](#graphql-query)

```
query {
  customer {
    email
    uuid
  }
}
```

GraphQL Testing with curl
-------------------------

[](#graphql-testing-with-curl)

Set test variables:

```
BASE_URL="https://example.com"
EMAIL="customer@example.com"
PASSWORD="Pa5Sw0rd123!"
```

### 1. Verify guest access is rejected

[](#1-verify-guest-access-is-rejected)

```
curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  --data-binary '{
    "query": "query { customer { email uuid } }"
  }' | jq
```

Expected result: the request should be rejected with an authorization error.

The response must not expose `uuid`.

### 2. Generate customer token

[](#2-generate-customer-token)

```
TOKEN=$(curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  --data-binary "{
    \"query\": \"mutation { generateCustomerToken(email: \\\"$EMAIL\\\", password: \\\"$PASSWORD\\\") { token } }\"
  }" | jq -r '.data.generateCustomerToken.token')

echo "$TOKEN"
```

Expected result: a customer bearer token is returned.

### 3. Verify authenticated UUID access

[](#3-verify-authenticated-uuid-access)

```
curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  --data-binary '{
    "query": "query { customer { email uuid } }"
  }' | jq
```

Expected result:

```
{
  "data": {
    "customer": {
      "email": "customer@example.com",
      "uuid": "550e8400-e29b-41d4-a716-446655440000"
    }
  }
}
```

### 4. Verify UUID format

[](#4-verify-uuid-format)

```
curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  --data-binary '{
    "query": "query { customer { uuid } }"
  }' | jq -r '.data.customer.uuid'
```

Expected format:

```
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

```

Example:

```
550e8400-e29b-41d4-a716-446655440000

```

Testing Procedures
------------------

[](#testing-procedures)

### 1. PHP syntax check

[](#1-php-syntax-check)

```
find vendor/coresh/module-customer-attribute -name "*.php" -print0 \
  | xargs -0 -n1 php -l
```

Expected result:

```
No syntax errors detected

```

### 2. Composer validation

[](#2-composer-validation)

```
composer validate vendor/coresh/module-customer-attribute/composer.json --strict
```

### 3. Magento dependency injection compilation

[](#3-magento-dependency-injection-compilation)

```
bin/magento setup:di:compile
```

Expected result: compilation completes without errors.

### 4. Magento setup and index verification

[](#4-magento-setup-and-index-verification)

```
bin/magento setup:upgrade
bin/magento cache:flush
bin/magento indexer:reset customer_grid
bin/magento indexer:reindex customer_grid
```

Expected result: setup and customer grid reindex complete successfully.

### 5. Unit tests

[](#5-unit-tests)

```
vendor/bin/phpunit vendor/coresh/module-customer-attribute/Test/Unit --display-all-issues
```

Expected result:

```
OK (4 tests, 13 assertions)

```

### 6. Magento Coding Standard

[](#6-magento-coding-standard)

If Magento Coding Standard is installed:

```
vendor/bin/phpcs vendor/coresh/module-customer-attribute --standard=Magento2
```

If the Magento test ruleset is available:

```
vendor/bin/phpcs vendor/coresh/module-customer-attribute \
  --standard=dev/tests/static/testsuite/Magento/Test/Php/_files/phpcs/ruleset.xml
```

### 7. Integration tests

[](#7-integration-tests)

Integration tests require a configured Magento integration testing environment.

Check that the integration test configuration exists:

```
ls -la dev/tests/integration/etc/install-config-mysql.php
```

Run integration tests:

```
vendor/bin/phpunit \
  -c dev/tests/integration/phpunit.xml.dist \
  vendor/coresh/module-customer-attribute/Test/Integration
```

Functional Acceptance Checklist
-------------------------------

[](#functional-acceptance-checklist)

```
[OK] Module is installed and enabled.
[OK] setup:upgrade completes successfully.
[OK] setup:di:compile completes successfully.
[OK] customer_entity.uuid exists.
[OK] customer_entity.uuid has a unique index.
[OK] Existing customers have UUID values.
[OK] New customers receive UUID values automatically.
[OK] Existing UUIDs are not overwritten.
[OK] Duplicate UUIDs do not exist.
[OK] customer_grid_flat.uuid exists after customer_grid reindex.
[OK] UUID is visible in the Admin customer grid.
[OK] UUID is not editable in the Admin customer form.
[OK] Guest GraphQL request does not expose UUID.
[OK] Authenticated GraphQL customer query returns UUID.
[OK] Unit tests pass.
[OK] PHP syntax check passes.
[OK] Magento Coding Standard is checked.

```

Rollback Notes
--------------

[](#rollback-notes)

To disable the module:

```
bin/magento module:disable Coresh_CustomerAttribute
bin/magento setup:upgrade
bin/magento cache:flush
```

The module adds persistent database schema changes, including the `customer_entity.uuid` column. Removing database columns should be handled carefully and only after confirming that no external integrations depend on UUID values.

Recommended safe rollback approach:

```
1. Disable the module.
2. Keep existing UUID data unless permanent removal is required.
3. Take a database backup before removing schema changes.
4. Remove schema only through a controlled deployment or migration process.

```

Architecture
============

[](#architecture)

It automatically:

- assigns unique UUIDs to existing customers;
- assigns UUIDs to new customers;
- prevents manual UUID changes;
- displays UUIDs in the Admin customer grid;
- exposes UUIDs through GraphQL only for authenticated customers.

Its main purpose is to give each customer a stable, unique, secure external identifier without exposing the internal `customer_id`.

Installation
------------

[](#installation-1)

During `bin/magento setup:upgrade`, the module creates the customer UUID attribute metadata, updates the customer grid configuration, and assigns UUIDs to existing customer records.

Module Overview
---------------

[](#module-overview)

The architecture uses both **Data Patches** and a **Plugin** because they solve two separate Magento lifecycle problems.

A **Data Patch** handles installation and upgrade-time work. It is the right place to create the customer attribute metadata, configure the attribute for the Admin customer grid, and backfill UUIDs for existing customer records during `bin/magento setup:upgrade`.

A **Plugin** handles runtime behavior. It is responsible for assigning a UUID when a new customer is saved and protecting an existing UUID from being changed through Admin, REST API, GraphQL, imports, or custom code.

These two mechanisms do not replace each other.

```
Patch  = installation / upgrade-time setup
Plugin = runtime customer save protection

```

Why Data Patches Are Used
-------------------------

[](#why-data-patches-are-used)

The assessment requires UUIDs to be assigned to existing customers when the module is installed. That is installation-time data work, so a Data Patch is the correct Magento mechanism.

`db_schema.xml` can create the physical database column and unique index:

```
customer_entity.uuid
UNIQUE INDEX(uuid)

```

However, `db_schema.xml` cannot perform business/data operations such as:

```
- creating customer attribute metadata;
- enabling customer grid visibility;
- assigning UUIDs to existing customers;
- preserving existing UUIDs;
- processing existing customers in batches.

```

That is why the module uses:

```
db_schema.xml = database structure
Data Patch    = installation-time data and metadata setup

```

Using old `InstallData`, `UpgradeData`, `InstallSchema`, or `UpgradeSchema` scripts would be less suitable for a Magento 2.4.7+ implementation. Declarative schema and patches are the modern, production-ready approach.

Why a Plugin Is Used
--------------------

[](#why-a-plugin-is-used)

The Plugin is used because UUID assignment and UUID immutability must be enforced every time a customer is saved.

Customer records can be created or updated through multiple entry points:

```
- Admin customer form;
- storefront registration;
- REST API;
- GraphQL-related flows;
- imports;
- custom modules;
- programmatic CustomerRepository saves.

```

The Plugin enforces the rule at the save layer:

```
If the customer is new:
    generate a UUID

If the customer already exists and already has a UUID:
    preserve the existing UUID

If the customer exists but the UUID is missing:
    generate a UUID

```

This is stronger than relying only on the Admin UI.

Hiding or disabling the UUID field in Admin is not enough because the value could still be changed through direct POST requests, API calls, imports, or custom code. The save layer must protect the UUID.

Can the Module Be Implemented Without Data Patches?
---------------------------------------------------

[](#can-the-module-be-implemented-without-data-patches)

Technically, yes, but it would be weaker for this assessment.

One alternative is a CLI command:

```
bin/magento customer:uuid:backfill
```

That can be useful for very large production databases because it gives more control and can show progress.

However, by itself, a CLI command does not satisfy the requirement that existing customers receive UUIDs during module installation. Someone may forget to run it, and customers may temporarily have empty UUID values.

A stronger enterprise version could include both:

```
Data Patch  = automatic installation-time backfill
CLI command = optional controlled re-run/backfill tool

```

A cron-based or lazy backfill would be even weaker because UUIDs would not be available immediately after installation.

Can the Module Be Implemented Without a Plugin?
-----------------------------------------------

[](#can-the-module-be-implemented-without-a-plugin)

Yes, but another reliable runtime mechanism would still be required.

Possible alternatives include:

```
- observer on customer_save_before;
- customer attribute backend model;
- custom service contract used by all customer creation flows.

```

An observer can work, but it is less explicit than a CustomerRepository plugin and can become hidden business logic if not designed carefully.

A backend model can also work, but in this architecture the UUID is stored as a static column on `customer_entity`, not as a normal EAV varchar value. Immutability and collision retry handling are clearer in a dedicated service plus plugin.

A DI preference would be a poor choice because it is more invasive, creates a higher conflict risk with other modules, and is harder to maintain during Magento upgrades.

A database trigger would also be a poor Magento solution because it hides business logic outside the application layer, is harder to test, and is less suitable for Adobe Commerce Cloud-style deployments.

Practical Conclusion
--------------------

[](#practical-conclusion)

For this assessment, the best implementation is:

```
Use Data Patch: yes
Use Plugin: yes

```

The Data Patch prepares the system during installation:

```
- creates customer attribute metadata;
- configures grid visibility;
- backfills existing customers;
- keeps backfill idempotent.

```

The Plugin protects runtime behavior:

```
- assigns UUIDs to new customers;
- preserves existing UUIDs;
- prevents Admin/API/GraphQL/custom code from overwriting UUIDs.

```

This separation is important:

```
installation lifecycle ≠ runtime lifecycle

```

The most production-ready architecture is therefore:

```
Declarative schema
+ Data Patches
+ Customer save Plugin
+ dedicated UUID services
+ GraphQL resolver
+ Admin grid configuration

```

This design satisfies the assessment requirements while keeping the module upgrade-safe, testable, and aligned with Magento extension architecture.

Check metadata:
---------------

[](#check-metadata)

```
SELECT ea.attribute_id,
              ea.attribute_code,
              ea.backend_type,
              ea.is_unique,
              cea.is_visible,
              cea.is_used_in_grid,
              cea.is_visible_in_grid,
              cea.is_filterable_in_grid,
              cea.is_searchable_in_grid
       FROM eav_attribute ea
       JOIN customer_eav_attribute cea
         ON cea.attribute_id = ea.attribute_id
       WHERE ea.entity_type_id = (
           SELECT entity_type_id
           FROM eav_entity_type
           WHERE entity_type_code = 'customer'
       )
       AND ea.attribute_code = 'uuid';
+--------------+----------------+--------------+-----------+------------+-----------------+--------------------+-----------------------+-----------------------+
| attribute_id | attribute_code | backend_type | is_unique | is_visible | is_used_in_grid | is_visible_in_grid | is_filterable_in_grid | is_searchable_in_grid |
+--------------+----------------+--------------+-----------+------------+-----------------+--------------------+-----------------------+-----------------------+
|          831 | uuid           | static       |         1 |          1 |               1 |                  1 |                     1 |                     1 |
+--------------+----------------+--------------+-----------+------------+-----------------+--------------------+-----------------------+-----------------------+
1 row in set (0.000 sec)

```

Check source of truth:
----------------------

[](#check-source-of-truth)

```
SELECT entity_id, email, uuid
     FROM customer_entity
     ORDER BY entity_id DESC
     LIMIT 10;
+-----------+---------------------------+--------------------------------------+
| entity_id | email                     | uuid                                 |
+-----------+---------------------------+--------------------------------------+
|       491 | ************************* | 3ce1458a-c109-4e64-b4ea-f3881fd2332e |
|       490 | ************************* | 34a12bfb-a61f-410c-9d59-9be725cd739f |
|       487 | ************************* | e30a6707-1fa5-4519-8ae3-e98dbe9555af |
+-----------+---------------------------+--------------------------------------+

```

Check Grid:
-----------

[](#check-grid)

```
 SELECT entity_id, email, uuid
       FROM customer_grid_flat
       ORDER BY entity_id DESC
       LIMIT 10;
+-----------+---------------------------+--------------------------------------+
| entity_id | email                     | uuid                                 |
+-----------+---------------------------+--------------------------------------+
|       491 | ************************* | 3ce1458a-c109-4e64-b4ea-f3881fd2332e |
|       490 | ************************* | 34a12bfb-a61f-410c-9d59-9be725cd739f |
|       487 | ************************* | e30a6707-1fa5-4519-8ae3-e98dbe9555af |
+-----------+---------------------------+--------------------------------------+

```

### GraphQL query Guest request:

[](#graphql-query-guest-request)

```
BASE_URL="https://example.com"
EMAIL="test@example.com"
PASSWORD="************************"

curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  --data-binary '{
    "query": "query { customer { email uuid } }"
  }' | jq
```

### GraphQL query Guest request result:

[](#graphql-query-guest-request-result)

```
{
  "errors": [
    {
      "message": "The current customer isn't authorized.",
      "locations": [
        {
          "line": 1,
          "column": 9
        }
      ],
      "path": [
        "customer"
      ],
      "extensions": {
        "category": "graphql-authorization"
      }
    }
  ],
  "data": {
    "customer": null
  }
}

```

### Get Customer Token:

[](#get-customer-token)

```
TOKEN=$(curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  --data-binary "{
    \"query\": \"mutation { generateCustomerToken(email: \\\"$EMAIL\\\", password: \\\"$PASSWORD\\\") { token } }\"
  }" | jq -r '.data.generateCustomerToken.token')

echo "$TOKEN"
```

### Authenticated request:

[](#authenticated-request)

```
curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  --data-binary '{
    "query": "query { customer { email uuid } }"
  }' | jq

```

### Authenticated request result:

[](#authenticated-request-result)

```
{
  "data": {
    "customer": {
      "email": "test@example.com",
      "uuid": "e30a6707-1fa5-4519-8ae3-e98dbe9555af"
    }
  }
}

```

### Check UUID format request:

[](#check-uuid-format-request)

```
curl -s -X POST "$BASE_URL/graphql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  --data-binary '{
    "query": "query { customer { uuid } }"
  }' | jq -r '.data.customer.uuid'

```

### Check UUID format request result:

[](#check-uuid-format-request-result)

```
e30a6707-1fa5-4519-8ae3-e98fbe95f5af

```

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance90

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

46d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/9606476?v=4)[Dmitrii D](/maintainers/coresh)[@coresh](https://github.com/coresh)

---

Top Contributors

[![coresh](https://avatars.githubusercontent.com/u/9606476?v=4)](https://github.com/coresh "coresh (15 commits)")

### Embed Badge

![Health badge](/badges/coresh-module-customer-attribute/health.svg)

```
[![Health](https://phpackages.com/badges/coresh-module-customer-attribute/health.svg)](https://phpackages.com/packages/coresh-module-customer-attribute)
```

###  Alternatives

[mollie/magento2

Mollie Payment Module for Magento 2

1131.9M16](/packages/mollie-magento2)[buckaroo/magento2

Buckaroo Magento 2 extension

32420.3k8](/packages/buckaroo-magento2)[loki/magento2-components

Core module for defining Alpine.js components with advanced AJAX features

1011.8k26](/packages/loki-magento2-components)[imi/magento2-friendly-captcha

Friendly Captcha integration for Magento2

19131.4k](/packages/imi-magento2-friendly-captcha)[elgentos/module-prismicio

Magento 2 - Prismic.io integration

39119.0k4](/packages/elgentos-module-prismicio)[graycore/magento2-graphql-introspection-cache

1017.3k](/packages/graycore-magento2-graphql-introspection-cache)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
