Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated Apr 29, 2024

Customize primary keys

Customize primary keys

By default, DataStore models have an id field that is automatically populated on the client with a UUID v4, allowing DataStore to generate non-colliding globally unique identifiers in a scalable way. While UUIDs have desirable properties (they are large, non-sequential and opaque), there are times when a custom primary key, also known as custom identifier, is needed. For instance, to:

  • Have friendly/readable identifiers (surrogate/opaque vs. natural keys)
  • Define composite primary keys
  • Customize data partitioning to optimize for scale (especially important when planning to handle large amounts of data in short periods of time)
  • Selectively synchronize data to clients (e.g. by fields like deviceId, userId or similar)
  • Prioritize the sort order in which objects are returned by the sync queries
  • Make existing data consumable and syncable by DataStore

A schema with the typical id field looks like this:

1type Book @model {
2 id: ID!
3 title: String!
4 description: String
5}

You can customize the primary key by adding the @primaryKey directive to a field:

1type Book @model {
2 isbn: ID! @primaryKey
3 title: String!
4 description: String
5}

You can also require multiple fields to define your primary key. When your primary key references multiple fields, it's called a composite key. In the example below, the primary key is defined by the isbn and title fields:

1type Book @model {
2 isbn: ID! @primaryKey(sortKeyFields: ["title"])
3 title: String!
4 description: String
5}

Determine when the primary key field is auto-populated upon record creation

When you create a record with DataStore, a UUID is automatically populated for the default id: ID! primary key field. When working with custom primary keys, DataStore will automatically populate the key fields in the following conditions:

DescriptionTypeAutopopulated with UUID

Without @primaryKey

1type Customer @model {
2 firstName: String
3 lastName: String
4}
✅ Yes

Without @primaryKey, explicit id field

1type Customer @model {
2 id: ID!
3 firstName: String
4 lastName: String
5}
✅ Yes

@primaryKey on a custom field

1type Customer @model {
2 customerId: ID! @primaryKey
3 firstName: String
4 lastName: String
5}
❌ No

Explicit @primaryKey on id field

1type Customer @model {
2 id: ID! @primaryKey
3 dob: AWSDateTime!
4 firstName: String
5 lastName: String
6}
✅ Yes

Explicit @primaryKey on id field with sort key

1type Customer @model {
2 id: ID! @primaryKey(sortKeyFields: ["dob"])
3 dob: AWSDateTime!
4 firstName: String
5 lastName: String
6}
✅ Yes

Explicit id field in sort key

1type Customer @model {
2 country: String! @primaryKey(sortKeyFields: ["id"])
3 id: ID!
4 firstName: String
5 lastName: String
6}
✅ Yes

@primaryKey with no id field

1type Customer @model {
2 zip: String! @primaryKey(sortKeyFields: ["username"])
3 username: String!
4 firstName: String
5 lastName: String
6}
❌ No

Querying records with custom primary keys

Deleting records with custom primary keys

In addition to deleting by the model type (or Predicates.ALL), a record with a custom primary key can be deleted the following ways:

With the value of the primary key:

1const book = await DataStore.delete(Book, '12345');

With a predicate:

1const book = await DataStore.delete(Book, b =>
2 b.isbn.eq('12345')
3);
1const book = await DataStore.delete(Book, b => b.and([
2 b.isbn.eq('12345'),
3 b.title.eq('My Book')
4]));

With an object literal:

1const book = await DataStore.delete(Book, {
2 isbn: '12345'
3});
1const book = await DataStore.delete(Book, {
2 isbn: '12345',
3 title: 'My Book',
4});

The SQLite storage adapter does not currently support custom primary keys.