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

LegacyYou are viewing Gen 1 documentation. Switch to the latest Gen 2 docs →

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)
);