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

A record with a custom primary key can be queried for in the following ways:

With the value of the primary key:

Amplify.DataStore.query(Book.self, byIdentifier: .identifier(isbn: "12345")) { result in
switch result {
case .success(let book):
guard let book = book else {
print("Query was successful with empty result")
return
}
print("Query was successful, retrieved book: \(book)")
case .failure(let error):
print("Error on query() for type Book with error: \(error)")
}
}
let sink = Amplify.DataStore.query(Book.self, byIdentifier: .identifier(isbn: "12345")).sink {
if case let .failure(error) = $0 {
print("Error on query() for type Book with error: \(error)")
}
} receiveValue:{ book in
guard let book = book else {
print("Query was successful with empty result")
return
}
print("Query was successful, retrieved book: \(book)")
}

With the value of QueryPredicate:

Amplify.DataStore.query(Book.self, where: Book.keys.isbn == "12345") { result in
switch result {
case .success(let books):
print("Query was successful, retrieved books: \(books)")
case .failure(let error):
print("Error on query() for type Book with error: \(error)")
}
}
let sink = Amplify.DataStore.query(Book.self, where: Book.keys.isbn == "12345").sink {
if case let .failure(error) = $0 {
print("Error on query() for type Book with error \(error)")
}
} receiveValue: { books in
print("Query was successful, retrieved books: \(books)")
}

Deleting records with custom primary keys

A record with a custom primary key can be deleted in the following ways:

With the value of the primary key:

Amplify.DataStore.delete(Book.self, withIdentifier: .identifier(isbn: "12345")) { result in
switch result{
case .success:
print("Book deleted!")
case .failure(let error):
print("Error deleting book: \(error)")
}
}
let sink = Amplify.DataStore.delete(Book.self, withIdentifier: .identifier(isbn: "12345")).sink {
if case let .failure(error) = $0 {
print("Error deleting book: \(error)")
}
} receiveValue: {
print("Book deleted!")
}

With the value of QueryPredicate:

Amplify.DataStore.delete(Book.self, where: Book.keys.isbn == "12345") { result in
switch result {
case .success:
print("Book deleted!")
case .failure(let error):
print("Error deleting book: \(error)")
}
}
let sink = Amplify.DataStore.delete(Book.self, where: Book.keys.isbn == "12345").sink {
if case let .failure(error) = $0 {
print("Error deleting book: \(error)")
}
} receiveValue: {
print("Book deleted!")
}