Name:
interface
Value:
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:

type Book @model {
id: ID!
title: String!
description: String
}

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

type Book @model {
isbn: ID! @primaryKey
title: String!
description: String
}

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:

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

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

type Customer @model {
firstName: String
lastName: String
}
✅ Yes

Without @primaryKey, explicit id field

type Customer @model {
id: ID!
firstName: String
lastName: String
}
✅ Yes

@primaryKey on a custom field

type Customer @model {
customerId: ID! @primaryKey
firstName: String
lastName: String
}
❌ No

Explicit @primaryKey on id field

type Customer @model {
id: ID! @primaryKey
dob: AWSDateTime!
firstName: String
lastName: String
}
✅ Yes

Explicit @primaryKey on id field with sort key

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

Explicit id field in sort key

type Customer @model {
country: String! @primaryKey(sortKeyFields: ["id"])
id: ID!
firstName: String
lastName: String
}
✅ Yes

@primaryKey with no id field

type Customer @model {
zip: String! @primaryKey(sortKeyFields: ["username"])
username: String!
firstName: String
lastName: String
}
❌ No

Querying records with custom primary keys

With the value of the primary key:

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

With the values of the composite key:

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

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

Amplify.DataStore.query(Book.class, Where.identifier(Book.class,
Where.identifier(Book.class, "12345")),
matches -> {
if (matches.hasNext()) {
Book book = matches.next();
Amplify.DataStore.delete(book,
deleted -> Log.i("MyAmplifyApp", "Deleted a book."),
failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
);
}
},
failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java, "12345"),
{ matches ->
if (matches.hasNext()) {
val post = matches.next()
Amplify.DataStore.delete(post,
{ Log.i("MyAmplifyApp", "Deleted a book.") },
{ Log.e("MyAmplifyApp", "Delete failed.", it) }
)
}
},
{ Log.e("MyAmplifyApp", "Query failed.", it) }
)
Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java, "12345"))
.catch { Log.e("MyAmplifyApp", "Query failed", it) }
.onEach { Amplify.DataStore.delete(it) }
.catch { Log.e("MyAmplifyApp", "Delete failed", it) }
.collect { Log.i("MyAmplifyApp", "Deleted a book") }
RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java, "12345"))
.flatMapCompletable(RxAmplify.DataStore::delete)
.subscribe(
() -> Log.i("MyAmplifyApp", "Deleted a book."),
failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
);

Composite Key

Amplify.DataStore.query(Book.class, Where.identifier(Book.class, Where.identifier(Book::class.java,
new BookIdentifier( "12345", "My Title")),
matches -> {
if (matches.hasNext()) {
Book book = matches.next();
Amplify.DataStore.delete(book,
deleted -> Log.i("MyAmplifyApp", "Deleted a book."),
failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
);
}
},
failure -> Log.e("MyAmplifyApp", "Query failed.", failure));
Amplify.DataStore.query(Book::class.java, Where.identifier(Book::class.java,
BookIdentifier( "12345", "My Title")),
{ matches ->
if (matches.hasNext()) {
val post = matches.next()
Amplify.DataStore.delete(post,
{ Log.i("MyAmplifyApp", "Deleted a book.") },
{ Log.e("MyAmplifyApp", "Delete failed.", it) }
)
}
},
{ Log.e("MyAmplifyApp", "Query failed.", it) }
)
Amplify.DataStore.query(Book::class, Where.identifier(Book::class.java,
BookIdentifier( "12345", "My Title")))
.catch { Log.e("MyAmplifyApp", "Query failed", it) }
.onEach { Amplify.DataStore.delete(it) }
.catch { Log.e("MyAmplifyApp", "Delete failed", it) }
.collect { Log.i("MyAmplifyApp", "Deleted a book") }
RxAmplify.DataStore.query(Book.class, Where.identifier(Book::class.java,
new BookIdentifier( "12345", "My Title")))
.flatMapCompletable(RxAmplify.DataStore::delete)
.subscribe(
() -> Log.i("MyAmplifyApp", "Deleted a book."),
failure -> Log.e("MyAmplifyApp", "Delete failed.", failure)
);