       where citekey.parentItemID is null
     """
 
-    nokeys = yield Zotero.DB.queryAsync(nokey)
-    for item in (nokeys || [])
+    items = yield Zotero.DB.queryAsync(items)
+    for item in (items || [])
       Zotero.debug("No citation key: #{item.itemID}")
+      ## TODO: generate new key here
+
+    ## TODO: Possibly only do these scans when extensions.zotero.translators.better-bibtex.scanCitekeys is set, then reset it
+
+    ## TODO: find items with more than one citekey and delete all but one
+
+    ## TODO: scan for 'bibtex:...' keys and move them to attachments
+
+    @observerID = Zotero.Notifier.registerObserver(@, ['item'], 'BetterBibTeX.KeyManager', 100)
 
     debug('KeyManager.init() done')
     return
+  )
 
+  notify: Zotero.Promise.coroutine((action, type, ids, extraData) ->
+    debug('KeyManager.notify', {action, type, ids, extraData})
+
+    ## test for field updates https://groups.google.com/d/msg/zotero-dev/naAxXIbpDhU/7rM-5IKGBQAJ
+    ids = (id for id in ids when !extraData[id].BetterBibTeX)
+    debug('KeyManager.notify', {ids})
+
+    for item in yield Zotero.Items.getAsync(ids)
+      continue if item.isAttachment() || item.isNote()
+      # continue if item.getField('extra')
+      debug('KeyManager.notify: change extra', item.id)
+
+      ## generate citation key if not pinned, store in attachment not extra
+      item.setField('extra', 'updated')
+      yield item.saveTx({
+        notifierData: { BetterBibTeX: true }
+      })
+
+    return
+  )
 module.exports = new KeyManager()

      where attachment.linkMode = #{Zotero.Attachments.LINK_MODE_LINKED_URL}
    """

    nokey = """
      with citekey as (#{@query.citekeys})
      select item.itemID
      from items item

debug = require('./debug.coffee')

class KeyManager
  init: Zotero.Promise.coroutine(->
    debug('KeyManager.init()')
    @query = {
      field: {}
