GraphQL backend for B2B Organizations.
This app exposes admin and storefront GraphQL APIs to create and manage B2B organizations, cost centers, users, and related settings.
Searching organizations by custom field (admin)
Organizations persist custom field values on the Master Data entity organizations as an EAV array: each entry is { name, type, value } (each property may be string or null in Master Data). Definitions for those fields (type, dropdown options, registration flags) are stored in VBase b2b_settings as organizationCustomFields.
Master Data schema
The organizations schema is defined in node/mdSchema.ts (ORGANIZATION_SCHEMA_VERSION, currently v0.0.8) and published through checkConfig when GraphQL queries run (masterdata.createOrUpdateSchema).
For custom-field search, the schema:
| Piece | Detail |
|---|---|
customFields items | Typed object properties: name, type, value (string | null each, same pattern as tradeName) |
v-indexed | Includes customFields so Master Data _where can use customFields.name and customFields.value together (AND) |
Dynamic paths such as customFields.sapeccid=… are not supported — sapeccid is not a top-level schema property. Use getOrganizations with customFieldName and search instead.
Operations after deploy
After deploy, existing organization documents need a real Master Data update (not a no-op). Allow about five minutes for reindexing before custom-field search returns results.
Custom field definitions (admin dropdown)
| Query | Use |
|---|---|
getOrganizationCustomFields | Returns [SettingsCustomField] — definitions only, for populating a search dropdown |
getB2BSettings.organizationCustomFields | Same definitions, as part of full B2B settings |
For the dropdown, use field definition name as both the label and the value sent to getOrganizations.customFieldName (it matches customFields.name on organization documents).
_10query OrganizationCustomFieldDefinitions {_10 getOrganizationCustomFields {_10 name_10 type_10 dropdownValues {_10 value_10 label_10 }_10 }_10}
getOrganizations search
| Argument | Behavior |
|---|---|
search | Search term |
customFieldName | Optional. When set with search, filters where customFields.name = customFieldName and customFields.value = search. When omitted, search matches organization name and trade name (unchanged). |
Search by custom field:
_23query SearchOrganizationsByCustomField(_23 $search: String!_23 $customFieldName: String!_23) {_23 getOrganizations(_23 search: $search_23 customFieldName: $customFieldName_23 page: 1_23 pageSize: 25_23 ) {_23 data {_23 id_23 name_23 customFields {_23 name_23 value_23 }_23 }_23 pagination {_23 total_23 }_23 }_23}
Search by name or trade name (default):
_12query SearchOrganizationsByName($search: String!) {_12 getOrganizations(search: $search, page: 1, pageSize: 25) {_12 data {_12 id_12 name_12 tradeName_12 }_12 pagination {_12 total_12 }_12 }_12}
Related Jira: B2BTEAM-3594.
Creating organizations with cost centers
Two mutations can create an organization together with one or more cost centers. They share DefaultCostCenterInput, but behave differently regarding custom IDs and admin user setup.
| Mutation | Input type | Organization ID | Cost center ID | Admin user |
|---|---|---|---|---|
createOrganization | OrganizationInput | Auto-generated by Master Data | Auto-generated by Master Data | Not attached automatically |
createOrganizationAndCostCentersWithId | NormalizedOrganizationInput | Custom (input.id) | Custom (costCenters[].id / defaultCostCenter.id) | Attached to each cost center |
When to use each mutation
Use createOrganizationAndCostCentersWithId when the integration needs fixed organization and cost center identifiers (for example, syncing with an external ERP or CRM).
Use createOrganization when identifiers should be generated by Master Data. In this flow, do not send id on the organization or on cost centers. Use the costCenterId returned by the mutation when querying or updating the created cost center.
Sending a custom cost center id to createOrganization may cause a 400 response if that identifier already exists in Master Data (The document already exist with id or alternate key.).
Organization status on create
OrganizationInput and NormalizedOrganizationInput accept an optional status field when creating an organization.
| Value | Meaning |
|---|---|
| (omitted) | Defaults to active (backward compatible) |
active | Organization is active |
inactive | Organization is inactive |
on-hold | Organization is on hold |
Canonical values match updateOrganization. Omitting status keeps the historical create behavior. Creating with a non-active status does not send the organization status-changed email (that email only applies when status changes via updateOrganization).
The
statusfield returned bycreateOrganizationandcreateOrganizationAndCostCentersWithIdis the mutation's operation result (often empty), not the organization's lifecycle status. QuerygetOrganizationByIdwith the returnedidto confirm the persisted status.
_10query GetOrganization($id: ID!) {_10 getOrganizationById(id: $id) {_10 id_10 name_10 status_10 }_10}
Cost center addresses
DefaultCostCenterInput supports two ways to send addresses when creating an organization:
| Field | Type | Description |
|---|---|---|
address | AddressInput | Single address (backward compatible) |
addresses | [AddressInput] | Multiple addresses (optional) |
Both fields can coexist in the same input. Resolution order:
- If
addressesis provided and non-empty, it is used. - Otherwise, if
addressis provided, it is stored as a one-item array. - Otherwise, an empty array is stored.
This matches the behavior already available on createCostCenterWithId, which uses CostCenterInput.addresses.
Optional address fields (such as complement) may be omitted in GraphQL. They are normalized to empty strings before persistence when created through organization mutations.
Example: multiple addresses with custom IDs
Use createOrganizationAndCostCentersWithId:
_10mutation CreateOrganizationAndCostCenters($input: NormalizedOrganizationInput!) {_10 createOrganizationAndCostCentersWithId(input: $input) {_10 id_10 href_10 status_10 }_10}
_38{_38 "input": {_38 "id": "org-example-001",_38 "name": "Example Organization",_38 "tradeName": "Example Trade Name",_38 "b2bCustomerAdmin": {_38 "firstName": "Admin",_38 "lastName": "User",_38 "email": "admin@example.com"_38 },_38 "costCenters": [_38 {_38 "id": "cc-example-001",_38 "name": "Main Cost Center",_38 "addresses": [_38 {_38 "addressType": "BillingAddress",_38 "street": "100 Main Street",_38 "city": "Chicago",_38 "state": "IL",_38 "country": "USA",_38 "postalCode": "60601",_38 "complement": ""_38 },_38 {_38 "addressType": "ShippingAddress",_38 "street": "200 Warehouse Ave",_38 "city": "Chicago",_38 "state": "IL",_38 "country": "USA",_38 "postalCode": "60602",_38 "complement": ""_38 }_38 ]_38 }_38 ]_38 }_38}
Example: single address (legacy field)
The singular address field remains supported:
_26{_26 "input": {_26 "id": "org-example-002",_26 "name": "Legacy Organization",_26 "b2bCustomerAdmin": {_26 "firstName": "Admin",_26 "lastName": "User",_26 "email": "admin.legacy@example.com"_26 },_26 "costCenters": [_26 {_26 "id": "cc-example-002",_26 "name": "Main Cost Center",_26 "address": {_26 "addressType": "BillingAddress",_26 "street": "5th Avenue",_26 "city": "New York",_26 "state": "NY",_26 "country": "USA",_26 "postalCode": "10001",_26 "complement": ""_26 }_26 }_26 ]_26 }_26}
Example: multiple addresses without custom IDs
Use createOrganization and omit all id fields:
_10mutation CreateOrganization($input: OrganizationInput!, $notifyUsers: Boolean) {_10 createOrganization(input: $input, notifyUsers: $notifyUsers) {_10 id_10 costCenterId_10 href_10 status_10 }_10}
_36{_36 "notifyUsers": false,_36 "input": {_36 "name": "Auto ID Organization",_36 "b2bCustomerAdmin": {_36 "firstName": "Admin",_36 "lastName": "User",_36 "email": "admin.auto@example.com"_36 },_36 "costCenters": [_36 {_36 "name": "Main Cost Center",_36 "addresses": [_36 {_36 "addressType": "BillingAddress",_36 "street": "Rodeo Drive",_36 "city": "Beverly Hills",_36 "state": "CA",_36 "country": "USA",_36 "postalCode": "90210",_36 "complement": ""_36 },_36 {_36 "addressType": "ShippingAddress",_36 "street": "Sunset Blvd",_36 "city": "Los Angeles",_36 "state": "CA",_36 "country": "USA",_36 "postalCode": "90211",_36 "complement": ""_36 }_36 ]_36 }_36 ]_36 }_36}
Use the returned costCenterId to query the created cost center:
_12query GetCostCenter($id: ID!) {_12 getCostCenterById(id: $id) {_12 id_12 name_12 addresses {_12 addressType_12 street_12 city_12 state_12 }_12 }_12}
Related mutations
createCostCenterWithId— create a cost center with a custom ID andaddresseson an existing organization.updateCostCenter— update cost center fields, includingaddresses.