Skip to content

Commit

Permalink
feat(docs): Add more docs/examples for custom fields
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Jan 26, 2021
1 parent 5e04a14 commit 77fbb2f
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 18 deletions.
222 changes: 204 additions & 18 deletions docs/content/docs/developer-guide/customizing-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,218 @@ showtoc: true

# Customizing Models with custom fields

Custom fields allow you to add your own custom data properties to many of the Vendure entities. The entities which may be have custom fields defined are listed in the [CustomFields documentation]({{< relref "/docs/typescript-api/custom-fields" >}})
Custom fields allow you to add your own custom data properties to many of the Vendure entities. The entities which may have custom fields defined are listed in the [CustomFields documentation]({{< relref "/docs/typescript-api/custom-fields" >}}#signature)

They are specified in the VendureConfig:

```TypeScript
const config = {
// ...
dbConnectionOptions: {
// ...
synchronize: true,
},
customFields: {
Product: [
{ name: 'infoUrl', type: 'string' },
{ name: 'downloadable', type: 'boolean' },
{ name: 'shortName', type: 'localeString' },
],
User: [
{ name: 'socialLoginToken', type: 'string' },
],
},
// ...
customFields: {
Product: [
{ name: 'infoUrl', type: 'string' },
{ name: 'downloadable', type: 'boolean' },
{ name: 'shortName', type: 'localeString' },
],
User: [
{ name: 'socialLoginToken', type: 'string' },
],
},
}
```

With the example config above, the following will occur:

1. The database schema will be altered and a column will be added for each custom field. Note: this step requires the [TypeORM synchronize option](https://typeorm.io/#/connection-options/common-connection-options) to be set to `true` as above.
2. The GraphQL APIs will be modified on bootstrap to add the custom fields to the `Product` and `User` types respectively.
1. The database schema will be altered, and a column will be added for each custom field. **Note: changes to custom fields require a database migration**. See the [Migrations guide]({{< relref "migrations" >}}).
2. The GraphQL APIs will be modified to add the custom fields to the `Product` and `User` types respectively.
3. If you are using the [admin-ui-plugin]({{< relref "/docs/typescript-api/admin-ui-plugin" >}}), the Admin UI detail pages will now contain form inputs to allow the custom field data to be added or edited.

The values of the custom fields can then be set and queried via the GraphQL APIs:

```GraphQL
mutation {
updateProduct(input: {
id: 1
customFields: {
infoUrl: "https://some-url.com",
downloadable: true,
}
translations: [
{ languageCode: en, customFields: { shortName: "foo" } }
]
}) {
id
customFields {
infoUrl
downloadable
shortName
}
}
}
```

## Examples

### defaultValue & nullable

A default value for the custom field may be specified, and also whether the field may be nullable (or empty). Any fields set to `nullable: false` should have a default value specified.

```TypeScript
Product: [
{
name: 'downloadable',
type: 'boolean',
defaultValue: false,
nullable: false,
},
]
```

### Labels and descriptions

Labels and descriptions can be specified to supply more information about the purpose of the custom field, and also to allow translations to be set. In the Admin UI, the `label` will be used in any forms featuring the custom field, and will fall back to `name` if no label is specified.

```TypeScript
Product: [
{
name: 'extendedDescription',
type: 'localeString',
label: [
{ languageCode: LanguageCode.en, value: 'Extended description' },
{ languageCode: LanguageCode.de, value: 'Erweiterte Beschreibung' },
],
description: [
{ languageCode: LanguageCode.en, value: 'Technical specs, external links and images' },
{ languageCode: LanguageCode.de, value: 'Technische Daten, externe Links und Bilder' },
],
length: 65535,
},
]
```

### Lists

A custom field may hold an array of values by setting the `list` property to `true`:

```TypeScript
Product: [
{
name: 'keywords',
type: 'localeString',
list: true,
},
]
```

### Validation

Certain custom field types may be configured with validation parameters:

```TypeScript
Product: [
{
name: 'partCode',
type: 'string',
pattern: '^[0-9]{4}\-[A-Z]{3-4}$'
},
{
name: 'location',
type: 'string',
options: [
{ value: 'warehouse1' },
{ value: 'warehouse2' },
{ value: 'shop' },
]
},
{
name: 'weight',
type: 'int',
min: 0,
max: 9999,
step: 1,
},
]
```

In the above examples, attempting to set a custom field value which does not conform to the specified parameters (e.g. a `partCode` of `'121A'`) will throw an exception. In the Admin UI, these constraints will also be expressed in the form fields used to enter the data.

For even more control over validation, a `validate` function may be provided to any field type, which will run whenever the value is set via the GraphQL API. This function can even be asynchronous and may use the [Injector]({{< relref "injector" >}}) to access providers. Returning a string or LocalizedString means validation failed.

```TypeScript
Product: [
{
name: 'partCode',
type: 'string',
validate: async(value, injector) => {
const partCodeService = injector.get(PartCodeService);
const isValid = await partCodeService.validateCode(value);
if (!isValid) {
return `Part code ${value} is not valid`;
}
},
},
]
```

### public, readonly & internal

Some custom fields may be used internally in your business logic, or for integration with external systems. In this case the can restrict access to the information they contain. In this example, the Customer entity has an externalId relating to an external integration.

* `public: false` means that it will not be exposed via the Shop API.
* `readonly: true` means it cannot be updated via the Admin API. It can only be changed programmatically in plugin code.
* `internal: false` - means the field _will_ be exposed via the Admin API and will also appear (in read-only form) in the Admin UI. Internal custom fields are useful for purely internal implementation details.

```TypeScript
Customer: [
{
name: 'externalId',
type: 'string',
public: false,
readonly: true,
internal: false,
},
]
```

### Relations

It is possible to set up custom fields which hold references to other entities using the `'relation'` type:

```TypeScript
Customer: [
{
name: 'avatar',
type: 'relation',
entity: Asset,
// may be omitted if the entity name matches the GraphQL type name,
// which is true for all built-in entities.
graphQLType: 'Asset',
// Whether to "eagerly" load the relation
// See https://typeorm.io/#/eager-and-lazy-relations
eager: false,
},
]
```

In this example, we set up a many-to-one relationship from Customer to Asset, allowing us to specify an avatar image for each Customer. Relation custom fields are unique in that the input and output names are not the same - the input will expect an ID and will be named `'<field name>Id'` or `'<field name>Ids'` for list types.

```GraphQL
mutation {
updateCustomer(input: {
id: 1
customFields: {
avatarId: 42,
}
}) {
id
customFields {
avatar {
id
name
preview
}
}
}
}
```

7 changes: 7 additions & 0 deletions packages/core/src/config/custom-field/custom-field-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ export type CustomFieldConfig =
* * `max?: string`: The latest permitted date
* * `step?: string`: The step value
*
* #### `relation` type
*
* * `entity: VendureEntity`: The entity which this custom field is referencing
* * `eager?: boolean`: Whether to [eagerly load](https://typeorm.io/#/eager-and-lazy-relations) the relation. Defaults to false.
* * `graphQLType?: string`: The name of the GraphQL type that corresponds to the entity.
* Can be omitted if it is the same, which is usually the case.
*
* @example
* ```TypeScript
* bootstrap({
Expand Down

0 comments on commit 77fbb2f

Please sign in to comment.