Page updated Jan 16, 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

With the value of the primary key:

1Amplify.DataStore.query(Book.class, Where.identifier(Book.class, "12345"),
2 matches -> {
3 if (matches.hasNext()) {
4 Book book = matches.next();
5 Log.i("MyAmplifyApp", "Title: " + book.getTitle());
6 }
7 },
8 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"),
2 { allBooks: Iterator<Book> ->
3 while (allBooks.hasNext()) {
4 val book = allBooks.next()
5 Log.i("MyAmplifyApp", "Title: " + book.title)
6 }
7 },
8 { failure: DataStoreException? -> Log.e("MyAmplifyApp", "Query failed.", failure) })
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345"))
2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
3 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345")).subscribe(
2 book -> Log.i("MyAmplifyApp", "Title: " + book.getTitle()),
3 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
4);

With the values of the composite key:

1Amplify.DataStore.query(Book.class, Where.identifier(Book.class,
2 new BookIdentifier( "12345", "My Title")),
3 matches -> {
4 if (matches.hasNext()) {
5 Book book = matches.next();
6 Log.i("MyAmplifyApp", "Title: " + book.getTitle());
7 }
8 },
9 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java,
2 BookIdentifier( "12345", "My Title")),
3 { allBooks: Iterator<Book> ->
4 while (allBooks.hasNext()) {
5 val book = allBooks.next()
6 Log.i("MyAmplifyApp", "Title: " + book.title)
7 }
8 },
9 { failure: DataStoreException? -> Log.e("MyAmplifyApp", "Query failed.", failure) })
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java,
2 BookIdentifier( "12345", "My Title")))
3 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
4 .collect { Log.i("MyAmplifyApp", "Title: ${it.title}") }
1RxAmplify.DataStore.query(Post.class, Where.identifier(Book::class.java,
2 new BookIdentifier( "12345", "My Title"))).subscribe(
3 post -> Log.i("MyAmplifyApp", "Title: " + post.getTitle()),
4 failure -> Log.e("MyAmplifyApp", "Query failed.", failure)
5);

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 a predicate:

Custom Primary Key

1Amplify.DataStore.query(Book.class, Where.identifier(Book.class,
2 Where.identifier(Book.class, "12345")),
3 matches -> {
4 if (matches.hasNext()) {
5 Book book = matches.next();
6 Amplify.DataStore.delete(book,
7 deleted -> Log.i("MyAmplifyApp", "Deleted a book."),
8 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
9 );
10 }
11 },
12 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"),
2 { matches ->
3 if (matches.hasNext()) {
4 val post = matches.next()
5 Amplify.DataStore.delete(post,
6 { Log.i("MyAmplifyApp", "Deleted a book.") },
7 { Log.e("MyAmplifyApp", "Delete failed.", it) }
8 )
9 }
10 },
11 { Log.e("MyAmplifyApp", "Query failed.", it) }
12)
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345"))
2 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
3 .onEach { Amplify.DataStore.delete(it) }
4 .catch { Log.e("MyAmplifyApp", "Delete failed", it) }
5 .collect { Log.i("MyAmplifyApp", "Deleted a book") }
1RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345"))
2 .flatMapCompletable(RxAmplify.DataStore::delete)
3 .subscribe(
4 () -> Log.i("MyAmplifyApp", "Deleted a book."),
5 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
6 );

Composite Key

1Amplify.DataStore.query(Book.class, Where.identifier(Book.class, Where.identifier(Book::class.java,
2 new BookIdentifier( "12345", "My Title")),
3 matches -> {
4 if (matches.hasNext()) {
5 Book book = matches.next();
6 Amplify.DataStore.delete(book,
7 deleted -> Log.i("MyAmplifyApp", "Deleted a book."),
8 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
9 );
10 }
11 },
12 failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
1Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java,
2 BookIdentifier( "12345", "My Title")),
3 { matches ->
4 if (matches.hasNext()) {
5 val post = matches.next()
6 Amplify.DataStore.delete(post,
7 { Log.i("MyAmplifyApp", "Deleted a book.") },
8 { Log.e("MyAmplifyApp", "Delete failed.", it) }
9 )
10 }
11 },
12 { Log.e("MyAmplifyApp", "Query failed.", it) }
13)
1Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java,
2 BookIdentifier( "12345", "My Title")))
3 .catch { Log.e("MyAmplifyApp", "Query failed", it) }
4 .onEach { Amplify.DataStore.delete(it) }
5 .catch { Log.e("MyAmplifyApp", "Delete failed", it) }
6 .collect { Log.i("MyAmplifyApp", "Deleted a book") }
1RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java,
2 new BookIdentifier( "12345", "My Title")))
3 .flatMapCompletable(RxAmplify.DataStore::delete)
4 .subscribe(
5 () -> Log.i("MyAmplifyApp", "Deleted a book."),
6 failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
7 );