Problem/Motivation

Currently, the CRM User Contact Mapping system only provides display name override functionality. Site builders need the ability to map additional fields from person contacts to user entities, allowing them to expose CRM contact data through the user entity interface. This would enable:

  • Displaying contact information (phone numbers, addresses, dates, etc.) on user profiles
  • Allowing users to edit their contact information through their user account forms
  • Flexible field configuration per site, choosing which fields to expose and how
  • Customization of field widgets and formatters for optimal user experience

Steps to reproduce

N/A - This is a new feature request.

Proposed resolution

Add a field mapping configuration system to the User Contact Mapping settings form that allows site builders to:

  1. Configure Field Mappings: For each field available on the person contact type, site builders can:
    • Enable or disable individual field mappings
    • Set a human-readable label/name for the mapped field
    • Define a machine name for the exposed field handle (prefixed with "crm__") so it can be referenced consistently in displays
    • Choose whether the field should render on user edit forms (system fields such as id, uuid, created, and changed are never offered as form options)
    • Choose whether the field should render on user view pages (system fields can still be exposed here)
    • Indicate if the contact field is computed (e.g., the age field) so the UI treats it as read-only view-only data
    • Exclude system-only fields such as id, uuid, created, and changed from the list of form mapping options so they cannot be mapped
  2. Field Display Configuration: Site builders can independently control:
    • Form display: Which fields appear on user edit forms and which widgets are used
    • View display: Which fields appear on user view pages and which formatters are used
  3. Flexible Field Selection: Site builders can mix and match different fields for different user views, allowing customization per site needs
  4. Widget and Formatter Selection: Site builders can choose the appropriate field widget (for forms) and formatter (for views) that best works for their particular user display requirements
  5. Access Control via Plugins: Build a plugin system that governs access control approaches, so site builders can choose or extend how mapped fields behave.
    • User Field Plugin: Treats the mapped field like a normal user field, honoring Drupal's native user entity permissions. This is the simplest default plugin.
    • Contact Field Plugin: Treats access as though the field belonged to the mapped contact, reusing contact edit permissions.

    The plugin system should be extensible so other projects can introduce new access control plugins without modifying the main CRM project. The default plugin selection should be configurable per mapped field, and the default plugin should mimic native user field behavior for simplicity.

Access Control Considerations:

One of the critical design decisions for this feature is how access to mapped fields should be governed. Two approaches are provided by default, with site builders able to choose per field:

  1. User Entity Permissions (Default):
    • Fields are treated as standard user entity fields
    • If a user can edit the user entity, they can edit these mapped fields
    • Simplest approach, good for fields that users should be able to edit themselves
    • No additional permission configuration needed
  2. Contact Entity Permissions:
    • Users must have permission to edit the mapped contact entity to edit these fields
    • Provides stricter control, ensuring contact data integrity
    • Useful when contact data should only be modified by users with CRM permissions
    • Requires checking the user's access to the specific mapped contact

This flexible approach allows site builders to choose the appropriate access control method based on their security requirements and use case. The plugin system is extensible, allowing other modules to provide additional access control strategies for specific use cases. The default should be "User Entity Permissions" to maintain simplicity and backward compatibility.

Technical Implementation:

  • Add global enable_mapped_fields_on_form setting to show or hide mapped fields on user forms
  • Extend the User Contact Mapping settings form to include a field mapping configuration section
  • Dynamically discover all fields available on the person contact type
  • Define computed field wrappers that expose the contact fields on the user (using the configured "crm__" handles)
  • Implement field value computation: values are always computed on-the-fly from the mapped contact entity with no caching (Drupal's entity cache will handle performance)
  • Implement form submission handler that:
    • Loads the mapped contact entity for the user
    • Compares submitted field values with current contact field values
    • Only updates and saves the contact entity if any mapped field values have changed
    • Handles validation and error reporting for contact field updates
  • Integrate with Drupal's Field UI to allow widget/formatter configuration
  • Implement access control service to check field edit permissions based on configured method
  • Ensure computed fields (like age) can be mapped, but treat them as read-only so they only appear in view displays and skip form integration

Remaining tasks

  • ✅ Design and implement the field mapping configuration UI in the settings form
  • ✅ Implement field discovery for person contact type fields
  • ✅ Define computed field metadata/wrappers for each mapped contact field
  • ✅ Implement field value computation logic that always reads from the mapped contact (no caching layer needed)
  • ✅ Add form display integration (hook_form_user_form_alter enhancement) that renders the computed fields
  • ✅ Add form submission handler that:
    • Detects changes to mapped contact fields
    • Loads the user's mapped contact entity
    • Updates only changed values on the contact
    • Saves the contact entity when necessary
  • ✅ Add view display integration (hook_entity_view_alter or similar) to render the computed fields
  • ✅ Add validation to ensure machine names are valid and unique
  • ✅ Add configuration schema for field mappings
  • ✅ Implement plugin-based access control system for mapped fields:
    • Create a plugin manager plus plugin interfaces for access control
    • Provide default plugins for user-based access and contact-based access
    • Allow third-party modules to register additional access control plugins
    • Add access checks to form and view renderings that delegate to the selected plugin
  • ✅ Ensure the configuration/form builder never exposes id, uuid, created, or changed as form-display options while still allowing view exposure
  • ✅ Write tests for field mapping functionality
  • ✅ Write tests for access control scenarios
  • ✅ Update documentation (docs restructured to docs/user-mapping/ with entity.md, field-mapping.md, event.md, index.md)
  • ✅ Post-update hook crm_post_update_add_enable_mapped_fields_on_form_setting for existing installs

User interface changes

  • Menu structure: "User Contact Mappings" now points to the settings form (crm.user_contact_mapping.settings); the entity list is a secondary tab at /admin/config/crm/user/list
  • Settings Form Enhancement: Add a new "Field Mapping" section to /admin/config/crm/user/settings that includes:
    • Global "Show mapped fields on user forms" checkbox (enable_mapped_fields_on_form) to enable/disable mapped fields on user registration and profile forms
    • A table or list of all person contact fields
    • For each field: Enabled checkbox, Label input, Machine name input, Form display checkbox, View display checkbox, Access control plugin selector (User Entity Access / Contact Entity Access)
    • Validation and help text for machine name format
    • Help text explaining each access control option
    • Exclude system-only fields such as id, uuid, created, and changed from form mapping options while still allowing them to be exposed on user views if desired
  • User Edit Forms: Mapped fields will appear in the CRM section (or appropriate location) on user edit forms when form display is enabled; computed fields will be excluded from forms because they cannot be edited directly.
  • User View Pages: Mapped fields will appear on user view pages when view display is enabled; computed fields (like age) are treated as read-only view-only fields
  • Field UI Integration: Mapped fields will be available in Field UI for widget and formatter configuration at:
    • /admin/config/people/accounts/form-display (for form widgets)
    • /admin/config/people/accounts/display (for view formatters)

API changes

  • UserFieldMappingService (crm.user_field_mapping): Handles field discovery for person contact types, mapping retrieval, and mapped contact lookup
  • UserContactDisplaySyncService (crm.user_contact_display_sync): Syncs field display configuration between contact and user entity displays
  • UserContactFieldValuesStorage (crm.user_contact_field_values_storage): Handles reading/writing field values between user forms and the mapped contact entity
  • UserFieldAccessManager (plugin.manager.crm_user_field_access): Plugin manager for access control strategies; plugins use #[UserFieldAccess] PHP attribute for discovery
  • Computed field item lists: MappedContactFieldItemList for standard fields; MappedEntityReferenceFieldItemList for entity reference fields (emails, addresses, telephones)
  • UserContactMappingSettingsConfigSubscriber: Event subscriber that syncs Field UI display config when field mappings change
  • Configuration Schema: Extended crm.user_contact_mapping.settings with crm.user_field_mapping for field mapping configuration
  • Form Alter Hook Enhancement: Extend hook_form_user_form_alter implementation to:
    • Add mapped fields to user forms with proper access checks
    • Add custom form submission handler that detects changes and updates the contact entity
  • View Alter Hook: Implement hook to add mapped fields to user view displays with proper access checks
  • Access Control Plugin Manager: Plugin manager (plugin.manager.crm_user_field_access) handles field access checks:
    • Plugins discovered via #[UserFieldAccess] attribute with id, label, description
    • Default plugins: user_entity (UserEntityFieldAccess), contact_entity (ContactEntityFieldAccess)
    • Third-party modules can add plugins by extending UserFieldAccessBase
    • Return access results and caching hints for form/view hooks
  • Field Access Hook: Implement hook_entity_field_access or use field access handlers to enforce access control on mapped fields

Data model changes

  • Configuration Storage: Store field mapping configuration in crm.user_contact_mapping.settings config entity with structure:
    • enable_mapped_fields_on_form: Global boolean to show mapped fields on user registration and profile forms (default: true)
    • Field mappings array containing: enabled, contact_field_name, user_field_label, user_field_machine_name, form_display, view_display, access_control_plugin
    • Access control plugin IDs: user_entity or contact_entity (third-party modules can register additional plugins via #[UserFieldAccess] attribute)
  • Exposed Field Handles: Computed wrappers expose contact data on the user. Their machine names use the crm__{configured_machine_name} convention, but no additional field storage is added to the user tables.
  • Field Storage: Data originates from CRM contact fields and lives in the contact tables; the user-side exposures compute values on-the-fly from the mapped contact entity. No data duplication occurs.
  • Value Computation: All field values are computed on demand from the mapped contact entity each time they are accessed. Drupal's entity caching layer handles performance optimization.
  • Form Submission Flow: When user forms are submitted, mapped field values are compared against the current contact field values. Only if changes are detected is the contact entity loaded, updated, and saved.
  • Computed Fields: Computed values (like age, derived from start_date/end_date) are stored only in the contact system. They can be mapped as view-only fields on the user but not rendered on forms or stored separately.
  • No Schema Changes: No direct database schema changes needed - uses Drupal's standard field storage system for contact entities only

Issue fork crm-3568006

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

svendecabooter created an issue. See original summary.

svendecabooter’s picture

Adding related issue in primary_entity_reference, when widgets by that module are used on the the user registration form.

svendecabooter’s picture

Add related issue to use #config_target, which, if committed first, would trigger updates in the MR of this issue.

svendecabooter’s picture

Status: Active » Needs review

The MR is now ready for review:
- Go to /admin/config/crm/user/settings
- Enable "Include Person contact form in user registration" option
- Optionally configure and set a different form mode for the Contact entity (Person bundle), to show less fields on the user registration form.
- Log out and go to /user/register
- Create an account and fill in the extra Contact entity form fields
- Log back in as admin, and check the newly created Contact entity, and its saved fields.

bluegeek9 made their first commit to this issue’s fork.

bluegeek9 changed the visibility of the branch 1.0.x to hidden.

anjuelsaphilip2020’s picture

Status: Needs review » Reviewed & tested by the community
StatusFileSize
new68.38 KB
new34.03 KB

Hi, I’ve verified and tested MR !175 and successfully applied the patch on CRM 1.0.x-dev. The changes are working as expected.
Following steps I followed:

  1. Go to Configuration → CRM → User Settings
  2. Enable Allow Contact entity creation upon user registration
  3. Save configuration
  4. Log out and go to /user/register
  5. Submit the registration form
  6. Observe the person contact fields are displayed on the registration form, and a person contact is created upon user registration.

Testing Result: After applying the patch, Person contact fields show on the registration form, and a Person contact is created when a user registers.
Attaching screenshots for reference.
Thanks

bluegeek9’s picture

Title: Allow Contact entity creation upon user registration » Surface person fields on the mapped user
Issue summary: View changes
Status: Reviewed & tested by the community » Active

I appreciate you taking the initiative on this. I want to go in a slightly different direction. I want to expose the contact field onto the mapped user. I update the issue summary with roughly how I think it can be implemented, but it should not be treated as gospel.

bluegeek9 changed the visibility of the branch 3568006-allow-contact-entity to hidden.

svendecabooter’s picture

Assigned: Unassigned » svendecabooter

svendecabooter’s picture

Status: Active » Needs work

Thanks for the feedback. The alternative approach seems like a good way to go about this.
I have now created a new branch and (draft) MR to implement this.

Still todo:
- Add support for primary_entity_reference fields, since they seem to pose problems currently
- Add functional tests

svendecabooter’s picture

svendecabooter’s picture

Status: Needs work » Needs review

I think this is ready for review

bluegeek9’s picture

I have done an initial review. You did good work.

It is a large MR just by the nature of the issue.

I am preparing to present CRM to FL Drupal Camp, so the MR may not be merged immediately.

I encourage others to review the MR.

jdleonard’s picture

Status: Needs review » Needs work

This is impressive and will be incredibly powerful.

I proposed some changes in a somewhat superficial (code-only) review of the MR. I have not yet played with the feature.

An issue summary update of remaining tasks would also be helpful.

svendecabooter’s picture

Issue summary: View changes
bluegeek9’s picture

Computed fields are on the form display. The same is true for id and other internal fields

https://mr187-rlkfw4mjuusafstk8z8ghvrbnamrvb75.tugboatqa.com/admin/confi...

Add the age field to the view display. The settings form is correct, only the view checkbox is enabled.

https://mr187-rlkfw4mjuusafstk8z8ghvrbnamrvb75.tugboatqa.com/admin/confi...

Go to the User form display. You see both fields.

========================

When adding fields to the user in this way, should we check each contact display against each user display? If the person has the same display name as the user, we copy the field settings. If there is not a matching display the field is hidden. I am not suggesting the two be linked in some way, only which default settings to use when adding these types of fields.

svendecabooter’s picture

The issue with computed and system fields being form configurable should be fixed.
Using the same field settings on the user entity, as currently set on the contact entity, is probably a good idea.
Maybe we can add that in a followup finetuning issue? Or do we want this issue to cover the full scope?

svendecabooter’s picture

Issue summary: View changes
svendecabooter’s picture

Potential follow-up issues raised in this one - please let me know if they should be handled within this issue anyway:

  • \Drupal\crm\Form\UserContactMappingSettingsForm::INTERNAL_FIELDS - provide a way for modules to declare additional fields as excluded
  • Using the same field settings on the user entity, as currently set on the contact entity
svendecabooter’s picture

Assigned: svendecabooter » Unassigned
Status: Needs work » Needs review

I have addressed all feedback in the MR and this ticket.
I have added remarks where I thought no changes were needed. Please discuss if you do not agree.

jdleonard’s picture

I agree with all of your responses Sven, thank you! Sorry for the noise on the return type definitions; I thought I had run into trouble there previously...

I think both potential follow up issues are suitable for follow up issues.

It'll be a while until I can properly review this feature, but it needn't wait on me.

bluegeek9’s picture

I changed the behavior from neutral to forbidden when the plugin is not found or throws an exception.
I also changed == to ===

bluegeek9’s picture

I split the user mapping documentation into multiple pages.

bluegeek9’s picture

Assigned: Unassigned » bluegeek9
Status: Needs review » Active
bluegeek9’s picture

Assigned: bluegeek9 » Unassigned
Issue summary: View changes
Status: Active » Needs review

I am happy with everything except the description text on the setting page, which can be changed later.

bluegeek9’s picture

Status: Needs review » Fixed
//www.flaticon.com/free-icons/thank-you Thank you for your contribution! Your continued support makes this project sustainable.
There are multiple ways to show appreciation for the work contributed to this project including:
  • Triage issues and adding more context to existing issues.
  • Flagging CRM as a favorite on the project page to help others discover it and show your support.
  • Review the Developer Docs for accuracy and clarity.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • bluegeek9 committed 33064c35 on 1.0.x
    feat: #3568006 Surface person fields on the mapped user
    
svendecabooter’s picture

What's the additional "Show mapped fields on user forms" boolean field for?
Each mapped field already has the "Form" checkbox which takes care of that, doesn't it?
It's not clear to me how these relate to each other.

jdleonard’s picture

I don't think this was necessarily the intent, but I created #3575071: Separate form for field mapping to make better use of this checkbox.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.