Notes in iOS 15

 · 21 mins read

TL;DR: iOS 15 brought more forensics-meaningful changes to Apple Notes than recent major version releases.

Structural Changes

Between iOS 14.8 and iOS 15.0, Apple added the following 25 columns Apple Notes’ ZICCLOUDSYNCINGOBJECT table, bringing that massive table to 181 columns. This post will examine how each of those columns is used in the new features added to Apple Notes.

ZACCOUNT5
ZCREATIONDATE2
ZCREATIONDATE3
ZDISPLAYTEXT
ZISSYSTEMPAPER
ZLASTACTIVITYRECENTUPDATESVIEWEDDATE
ZLASTACTIVITYSUMMARYVIEWEDDATE
ZLASTATTRIBUTIONSVIEWEDDATE
ZLINKPRESENTATIONARCHIVEDMETADATA
ZMENTIONNOTIFICATIONATTEMPTCOUNT
ZMENTIONNOTIFICATIONSTATE
ZPAPERASSETSURL
ZPAPERDATABASEURL
ZREPLICAIDTOBUNDLEIDENTIFIER
ZSERVERSIDEUPDATETASKCONTINUATIONTOKEN
ZSERVERSIDEUPDATETASKFAILURECOUNT
ZSERVERSIDEUPDATETASKLASTATTEMPTEDBUILD
ZSERVERSIDEUPDATETASKLASTATTEMPTEDVERSION
ZSERVERSIDEUPDATETASKLASTCOMPLETEDBUILD
ZSERVERSIDEUPDATETASKLASTCOMPLETEDVERSION
ZSMARTFOLDERQUERYJSON
ZSTANDARDIZEDCONTENT
ZSYNAPSEDATA
ZTOKENCONTENTIDENTIFIER
ZURLEXPIRED

Changelog

Apple’s changelog for iOS 15 lists the following updates for Notes1 and these categories are the primary way we will look at the new columns:

  • Tags
  • Tag Browser
  • Custom Smart Folders
  • Activity View
  • Highlights
  • Mentions
  • Quick Note: Easy to Access

Tags

Apple users can now use hashtags to organize their notes. Under the hood, Apple created an entirely new uniform type identifier (UTI) to represent this object: com.apple.notes.inlinetextattachment.hashtag and these appear to be the most important columns from ZICCLOUDSYNCINGOBJECT for hashtags:

Column New? Description
ZALTTEXT Yes The ZALTTEXT column contains the text that is actually displayed in the note itself, cased as it was entered.
ZCREATIONDATE2 Yes The ZCREATIONDATE2 column indicates when this embedded object was created.
ZNOTE1   While other embedded objects point to the note they belong to in the ZNOTE column, hashtags and mentions use ZNOTE1.
ZTOKENCONTENTIDENTIFIER Yes The ZTOKENCONTENTIDENTIFIER column contains the fully-capitalized version of the hashtag, minus the hash symbol, likely as a normalized way of looking up the tag.
ZTYPEUTI1   Because this UTI behaves differently than other embedded objects, it is in the ZTYPEUTI1 column2 instead of ZTYPEUTI as other embedded objects use.

To see which notes used which hashtags, you can run the following SQLite query on the NoteStore.sqlite database:

SELECT ZNOTE1, ZALTTEXT, ZTOKENCONTENTIDENTIFIER 
  FROM ZICCLOUDSYNCINGOBJECT 
  WHERE ZTYPEUTI1='com.apple.notes.inlinetextattachment.hashtag' 
  ORDER BY ZNOTE1;
ZNOTE1 ZALTTEXT ZTOKENCONTENTIDENTIFIER
35 #NewHashtag NEWHASHTAG
190 #Test TEST
212 #Test TEST
215 #Test TEST
220 #Test2 TEST2
222 #NewHashtag NEWHASHTAG
224 #Test TEST
228 #Test2 TEST2
231 #Test TEST
272 #NewHashtag NEWHASHTAG

Tag Browser

While tags made an interesting change to the Notes database, the tag browser itself is mainly a UI change that reflects the addition of tags. When you create a tag, an entry is created in the ZICCLOUDSYNCINGOBJECT database with a Z_ENT value of 7, the ZDISPLAYTEXT column set to the hashtag as you entered it, and the ZSTANDARDIZEDCONTENT column set to an all capital letters version of the hashtag, if the language has capital letters. The overall tag creation time appears to now be stored in the ZCREATIONDATE1 field, which was used prior to iOS 15 for the overall note creation time. I couldn’t find anywhere these appear to be referenced later by the notes or actual use of the hashtag, so these likely are how the UI is able to display the tag choices to the user.

SELECT Z_PK, Z_ENT, ZDISPLAYTEXT, ZSTANDARDIZEDCONTENT, ZCREATIONDATE1
  FROM ZICCLOUDSYNCINGOBJECT 
  WHERE Z_ENT=7;
Z_PK Z_ENT ZDISPLAYTEXT ZSTANDARDIZEDCONTENT ZCREATIONDATE1
209 7 Test TEST 654049803.242126
218 7 NoNotes NONOTES 654050083.315863
219 7 Test2 TEST2 654050096.184235
247 7 NewHashtag NEWHASHTAG 654050173.539261

Custom Smart Folders

Custom smart folders allow you to define what you want to see in a folder without having to move notes to the folder itself. Unlike other folders where each note will point to one folder’s ZICCLOUDSYNCINGOBJECT.Z_PK, the contents of a smart folder are identified by looking at the JSON contained in the ZSMARTFOLDERQUERYJSON column. You can select the folder’s settings with this SQLite query:

SELECT Z_PK, ZTITLE2, ZSMARTFOLDERQUERYJSON 
  FROM ZICCLOUDSYNCINGOBJECT 
  WHERE ZSMARTFOLDERQUERYJSON IS NOT NULL

In my example, I named the folder “New Smart Folder for Tags” and added three tags to it: “#Test2”, “#NoNotes”, and “#Test”. The JSON that is found in the new column was as follows:

{
  "entity": "note",
  "type": {
    "and": [
      {
        "tag": "TEST2"
      },
      {
        "tag": "NONOTES"
      },
      {
        "tag": "TEST"
      },
      {
        "deleted": false
      }
    ]
  }
}

Activity View

Apple added in the ability to see who edited what on a given note. This obviously adds potentially rich insight to the note contents, but took a lot of time to experiment with. At the end of that time, I felt that the information present could mostly be found using other aspects of the database in an easier way. These appear to be the most important columns from ZICCLOUDSYNCINGOBJECT for the activity view:

Column New? Description
ZACTIVITYEVENTSDATA   GZIP’d protobuf that possibly conforms to the same proto definition that mergeable items use3.
ZLASTACTIVITYRECENTUPDATESVIEWEDDATE Yes Timestamp indicating when the user viewed updates on the note.
ZLASTACTIVITYSUMMARYVIEWEDDATE Yes Timestamp indicating when the user viewed the activity summary.
ZLASTATTRIBUTIONSVIEWEDDATE Yes Timestamp indicating when the user viewed who edited the note.

Most of these are straight forward, although the already existing ZACTIVITYEVENTSDATA warrants a comment. While this GZIP’d protobuf appeared interesting at first, it seems to contain minimal “new” information in and of itself. For example, at its heart are messages that point to the folder, note, and participant’s identities, but those are already referenced by other columns on the same row. Consider these JSONs from one of my test notes and the value they give, as compared to the columns noted below in the “Mentions” section.

{
  "activity": {
    "type": "mention",
    "participants": [
      {
        "recordName": "_12345678901234567890123456789012",
        "mentionRecordName": "7B9C22DD-B791-43F5-ADD0-961A042F7D43"
      }
    ]
  },
  "object": {
    "type": "note",
    "recordName": "0AE73BAB-A205-4EDD-8885-103BE3EDC00B"
  },
  "timestamp": 656723690.242377,
  "participant": {
    "recordName": "_09876543210987654321098765432109"
  }
}
{
  "object": {
    "type": "note",
    "recordName": "0AE73BAB-A205-4EDD-8885-103BE3EDC00B"
  },
  "timestamp": 656723690.242377,
  "minimumSupportedNotesVersion": 6,
  "typeName": "mention",
  "participant": {
    "recordName": "_09876543210987654321098765432109"
  },
  "localizedFallbackTitle": "Somebody mentioned 1 people"
}

Highlights

Similar to the tag browser and tags, highlights reflects more of a UI change to reveal activity to the user in an easy way, not a significant change to the underlying database.

Mentions

Mentions have a potentially significant impact on reviewing notes because, similar to tags, they add a new type of inlined, embedded object which won’t show up if you just look at the text of a note. Further, unlike tags, the object’s token is only a pointer to the person being mentioned, you’ll need to do some CloudKit work to determine the actual account being referenced. The new UTI for mentions is: com.apple.notes.inlinetextattachment.mention and these appear to be the most important columns from ZICCLOUDSYNCINGOBJECT for mentions:

Column New? Description
ZALTTEXT Yes The ZALTTEXT column contains the text that is actually displayed in the note itself, typically @[User first name].
ZCREATIONDATE2 Yes Just like tags, the ZCREATIONDATE2 column indicates when this embedded object was created.
ZMENTIONNOTIFICATIONATTEMPTCOUNT Yes This column indicates how many times the mentioned user was attempted to be contacted.
ZMENTIONNOTIFICATIONSTATE Yes This column likely indicates whether the mentioned user was notified of the mention yet. I have observed values 1, 2, and 3 where 3 might indicate the user was notified and 2 likely indicates at least one attempt was made. I need a lot more iCloud accounts to play with.
ZNOTE1   Just like tags, mentions use ZNOTE1 to denote which note they belong to.
ZTOKENCONTENTIDENTIFIER Yes Unlike tags, the ZTOKENCONTENTIDENTIFIER column for mentions contains a pointer the CloudKit identifier for the account, not something immediately recognizable as the account username.
ZTYPEUTI1   Just like tags, mentions use the ZTYPEUTI1 column instead of ZTYPEUTI to denote their type.

To see which notes mentioned which users, you could run the following:

SELECT ZNOTE1, 
       ZALTTEXT, 
       ZTOKENCONTENTIDENTIFIER as Account, 
       ZMENTIONNOTIFICATIONATTEMPTCOUNT as Notifications,
       ZMENTIONNOTIFICATIONSTATE as State
  FROM ZICCLOUDSYNCINGOBJECT 
  WHERE ZTYPEUTI1='com.apple.notes.inlinetextattachment.mention' 
  ORDER BY ZNOTE1;
ZNOTE1 ZALTTEXT Account Notifications State
16 @Tom _09876543210987654321098765432109 1 2
222 @Tom _09876543210987654321098765432109 1 2
224 @Mark _12345678901234567890123456789012 0 3
228 @Tom _09876543210987654321098765432109 1 2

If you want to know how to move further with that CloudKit ID, it would be worthwhile to read this post about CloudKit data. Apple Notes Parser cheats slightly and reads the CloudKit data from the ZSERVERSHAREDATA column to then look up the value. For the sake of this experiment, it is likely only important to know that “@Mark” was the local test account and “@Tom” was a test account that was not logged into. As such, I am assuming that a state of ‘3’ either indicates the user was notified or that it is a local account and ‘2’ indicates that the user was attempted to be notified.

Quick Note: Easy to access

Like the tag browser and highlights, this is a UI change, not a significant change. I did not find any database changes that I thought were related to this.

Other Changes

ZACCOUNT*

Every other major release of iOS brings a new ZACCOUNT field and now we’re adding ZCREATIONDATE and ZTYPEUTI as well. I have yet to decide if I think this is an intentional design choice by the Apple developers or an accidental result of some build tool that the developer doesn’t realize is occuring under the hood. As of iOS 15, we are up to ZACCOUNT5, and this is getting slightly ridiculous.

[notta@cuppa ~]$ awk -F '|' '{print $2}'  notestore_15.0.schema \
| grep "ZACCOUNT\\|ZCREATIONDATE\|ZTYPEUTI"

ZACCOUNT
ZACCOUNT1
ZACCOUNT2
ZACCOUNT3
ZACCOUNT4
ZACCOUNT5
ZCREATIONDATE
ZCREATIONDATE1
ZCREATIONDATE2
ZCREATIONDATE3
ZTYPEUTI
ZTYPEUTI1

The really annoying aspect is specific values keep moving, based on the version. For example, the note account owner that was in ZACCOUNT3 in iOS 14 is now in ZACCOUNT4 in iOS 15 and the folder owner that was in ZACCOUNT4 is now in ZACCOUNT5. This means that on every major iOS release, you should expect to be updating your scripts and it means you need to make sure you can appropriately fingerprint the correct version of the database.

iOS Version Note Owner Folder Owner
11 / 12 ZACCOUNT2 ZACCOUNT3
13 / 14 ZACCOUNT3 ZACCOUNT4
15 ZACCOUNT4 ZACCOUNT5

ZCREATIONDATE*

As seen above, ZCREATIONDATE2 and ZCREATIONDATE3 are new in iOS 15. ZCREATIONDATE2 has already been identified as being used by Hashtags and Mentions, but ZCREATIONDATE3 contains the same value on note rows in 15.0 as the ZCREATIONDATE1 column did in 14.8. Confusingly, now ZCREATIONDATE1 is used by tags for the tag browser. I ended up making myself this table to keep things straight:

Column iOS 14 iOS 15
ZCREATIONDATE Object embedded inside note Object embedded inside note
ZCREATIONDATE1 Note creation time Overall Tag creation time
ZCREATIONDATE2 - Hashtag and Mention creation time inside note
ZCREATIONDATE3 - Note creation time

Replica Bundle ID

This doesn’t appear related to iOS 15 changes as much as continuing work on how data is replicated across accounts. There already were two other ZREPLICAID* columns, but ZREPLICAIDTOBUNDLEIDENTIFIER is new in iOS 15. This contains an NSKeyedArchive that maps a com.apple.mobilenotes.SharingExtension to com.apple.mobilenotes.IntentsExtension and Apple Notes itself. I only found this value on the ZICCLOUDSYNCINGOBJECT columns that represented an iCloud account.

[notta@cuppa /tmp]$ ruby ~/bplister/nskeyed_archive_list.rb replica_id_to_bundle.bin 
{"root"=>
  {"NS.keys"=>
    ["com.apple.mobilenotes.SharingExtension",
     "com.apple.mobilenotes.IntentsExtension",
     "com.apple.mobilenotes"],
   "NS.objects"=>
    [{"NS.uuidbytes"=>"\xC4?T\x983\x9EC\x16\x9Fg\x037\xDC!\xE0o",
      "$class"=>{"$classname"=>"NSUUID", "$classes"=>["NSUUID", "NSObject"]}},
     {"NS.uuidbytes"=>"\x0A\x51\x84\xA8$\xCBJ\x62\x01@\xD7\xF5\xDCl\x02\xB7",
      "$class"=>{"$classname"=>"NSUUID", "$classes"=>["NSUUID", "NSObject"]}},
     {"NS.uuidbytes"=>"\x36\xA2\x57\a!?I\xCF\xA2\xB7d\xBBY)\xA3g",
      "$class"=>{"$classname"=>"NSUUID", "$classes"=>["NSUUID", "NSObject"]}}],
   "$class"=>
    {"$classname"=>"NSDictionary", "$classes"=>["NSDictionary", "NSObject"]}}}

In addition, these six fields showed up on the actual account row which seemed related to account information and based on naming might be related to replicating data:

Column New? Description
ZSERVERSIDEUPDATETASKCONTINUATIONTOKEN Yes [I have not yet been able to get a value here]
ZSERVERSIDEUPDATETASKFAILURECOUNT Yes [I have not yet been able to get a value here]
ZSERVERSIDEUPDATETASKLASTATTEMPTEDBUILD Yes iOS build number for last iOS attempted update, for example, 19A404.
ZSERVERSIDEUPDATETASKLASTATTEMPTEDVERSION Yes iOS version number for last iOS attempted update, for example, 15.0.2.
ZSERVERSIDEUPDATETASKLASTCOMPLETEDBUILD Yes iOS build number for last iOS update, for example, 19A404.
ZSERVERSIDEUPDATETASKLASTCOMPLETEDVERSION Yes iOS version number for last iOS update, for example, 15.0.2.

Expiring URLs

There is a new column called ZURLEXPIRED which would seem to indicate whether a URL is expired or not. iOS 12 allowed users to share photos and videos by providing a URL which would expire in 30 days, maybe this functionality is coming to Notes in the future? None of my test data has this as anything but ‘0’ and I haven’t had iOS 15 long enough for something to expire.

Embedded Icons

An interesting new column is ZLINKPRESENTATIONARCHIVEDMETADATA. This column is an NSKeyedArchive that contains the information necessary to show a link to an embedded object, such as a VCard or a URL. Interestingly. it embeds a PNG that is what comes up when you see the object, and it can contain links to the original favicons, if it was a URL. I only found this on UTIs that inherited from public.data.


[notta@cuppa /tmp]$ ruby ~/bplister/plist_parse.rb link_presentation_metadata_23.bin 
{"$version"=>100000,
 "$archiver"=>"NSKeyedArchiver",
 "$top"=>{"root"=>1},
 "$objects"=>
  ["$null",
   {"$class"=>14, "title"=>2, "icon"=>3, "specialization2"=>8, "version"=>1},
   "Apple Inc.",
   {"data"=>4, "imageType"=>5, "MIMEType"=>6, "$class"=>7},
   {"NS.data"=> "\x89PNG\r\n [snipped the embedded image]", 
    "$class"=>5},
   {"$classname"=>"NSMutableData",
    "$classes"=>["NSMutableData", "NSData", "NSObject"]},
   "image/png",
   {"$classname"=>"LPImage", "$classes"=>["LPImage", "NSObject"]},
   {"size"=>10,
    "creationDate"=>11,
    "icon"=>3,
    "name"=>2,
    "type"=>9,
    "$class"=>13},
   "public.vcard",
   357,
   {"NS.time"=>621217431.0, "$class"=>12},
   {"$classname"=>"NSDate", "$classes"=>["NSDate", "NSObject"]},
   {"$classname"=>"LPFileMetadata",
    "$classes"=>["LPFileMetadata", "LPSpecializationMetadata", "NSObject"]},
   {"$classname"=>"LPLinkMetadata",
    "$classes"=>["LPLinkMetadata", "NSObject"]}]}

“Paper”

Also in the “potentially a future update” category are three columns related to “paper”. Based on the names, and the fact that ZPAPERSTYLETYPE already exists and appears to track which of the “Lines and Grids” backgrounds I have chosen for handwriting, I’m wondering if support for external lines and grids will be available in the future. These are the “PAPER” columns:

[notta@cuppa ~]$ awk -F '|' '{print $2}'  notestore_15.0.schema \
| grep "PAPER"

ZISSYSTEMPAPER
ZPAPERASSETSURL
ZPAPERDATABASEURL
ZPAPERSTYLETYPE

“Synapse”

In the category of “I have no idea” is the new column named ZSYNAPSEDATA. I have not yet been able to generate any test data that has this filled in with a value and the name isn’t leading to any obvious places on Apple’s developer pages. This could easily be a feature that has not yet fully rolled out, or one that I simply haven’t hit on the right combination of test actions to find. My test phone’s only case of the word “synapse” is this NoteStore.sqlite database.

[notta@cuppa itunes_backup]$ grep -i -r synapse
grep: 4f/4f98687d8ab0d6d1a371110e6b7300f6e465bef2: binary file matches

Conclusion

This post took a lot longer to get out than I had hoped. Given how many columns were added in this release to ZICCLOUDSYNCINGOBJECT, it will hopefully be useful, even if it was delayed. The final score of how the 25 new columns are used is as follows:

Column New? Use
ZACCOUNT5 Yes Folder creation time
ZCREATIONDATE2 Yes Hashtag and Mentions
ZCREATIONDATE3 Yes Note creation date
ZDISPLAYTEXT Yes Hashtags
ZISSYSTEMPAPER Yes Uncertain, likely lines and grids
ZLASTACTIVITYRECENTUPDATESVIEWEDDATE Yes Activity View
ZLASTACTIVITYSUMMARYVIEWEDDATE Yes Activity View
ZLASTATTRIBUTIONSVIEWEDDATE Yes Activity View
ZLINKPRESENTATIONARCHIVEDMETADATA Yes Icon used when sharing
ZMENTIONNOTIFICATIONATTEMPTCOUNT Yes Mentions
ZMENTIONNOTIFICATIONSTATE Yes Mentions
ZPAPERASSETSURL Yes Uncertain, likely lines and grids
ZPAPERDATABASEURL Yes Uncertain, likely lines and grids
ZREPLICAIDTOBUNDLEIDENTIFIER Yes iCloud account
ZSERVERSIDEUPDATETASKCONTINUATIONTOKEN Yes Account information
ZSERVERSIDEUPDATETASKFAILURECOUNT Yes Account information
ZSERVERSIDEUPDATETASKLASTATTEMPTEDBUILD Yes Account information
ZSERVERSIDEUPDATETASKLASTATTEMPTEDVERSION Yes Account information
ZSERVERSIDEUPDATETASKLASTCOMPLETEDBUILD Yes Account information
ZSERVERSIDEUPDATETASKLASTCOMPLETEDVERSION Yes Account information
ZSMARTFOLDERQUERYJSON Yes Smart Folders
ZSTANDARDIZEDCONTENT Yes Hashtags
ZSYNAPSEDATA Yes Unknown
ZTOKENCONTENTIDENTIFIER Yes Hashtags and Mentions
ZURLEXPIRED Yes Uncertain, possibly sharing URLs

Footnotes

  1. From Apple’s iOS 15 feature preview

  2. Interestingly, this field has been in Apple Notes since at least 14.7.1, perhaps preparing the way for this release. 

  3. A partial protobuf definition that appears to work for most of these fields can be found here