shelff JSON Specification
shelff stores metadata and reading progress for PDF files as JSON files in iCloud. This page describes the specification of the JSON files that shelff reads and writes.
shelff manages its data through these JSON files. Other tools can interoperate with shelff by reading and writing the same files.
Overview
shelff uses three types of JSON files.
| File | Location | Purpose |
|---|---|---|
*.pdf.meta.json |
Same directory as the PDF | Per-PDF metadata, reading progress, and display settings |
.shelff/categories.json |
.shelff directory under the shelff document root |
Category definitions and ordering |
.shelff/tags.json |
.shelff directory under the shelff document root |
Tag display order |
The metadata file (*.pdf.meta.json) is automatically created when a PDF is first opened in shelff. At that point, dc:title is set to the filename.
All files use UTF-8 encoding. Dates are recorded in ISO 8601 format.
Metadata File *.pdf.meta.json
A metadata file corresponds to a single PDF file. It is stored in the same directory as the PDF, with .meta.json appended to the original filename.
Example: my-book.pdf → my-book.pdf.meta.json
Example
{
"schemaVersion": 1,
"metadata": {
"dc:title": "Introduction to Programming",
"dc:creator": ["Jane Smith"],
"dc:publisher": "Tech Press",
"dc:date": "2024",
"dc:language": "en",
"dc:subject": ["Programming", "Introduction"],
"dc:identifier": "978-0-00-000000-0"
},
"reading": {
"lastReadPage": 42,
"totalPages": 300,
"lastReadAt": "2025-03-20T14:30:00Z",
"status": "reading",
"finishedAt": null
},
"display": {
"direction": "LTR",
"pageLayout": "spread-with-cover",
"crop": {
"excludeFirstPage": true,
"odd": { "top": 0.05, "bottom": 0.04, "left": 0.03, "right": 0.03 },
"even": { "top": 0.05, "bottom": 0.04, "left": 0.02, "right": 0.04 }
}
},
"collection": { "title": "Introduction to Programming Series", "position": 1 },
"category": "Technical",
"tags": ["Swift", "Beginner"],
"bookmarks": [
{ "page": 3, "label": "Important diagram" },
{ "page": 15 },
{ "page": 42 }
]
}
Top-level Fields
| Field | Type | Required | Description |
|---|---|---|---|
schemaVersion |
Integer | Yes | Schema version. Currently 1 |
metadata |
Object | Yes | Bibliographic metadata (see below) |
reading |
Object | No | Reading progress (see below) |
display |
Object | No | Display settings (see below) |
collection |
Object | No | The collection (series or magazine) this book belongs to. At most one per PDF |
category |
String | No | Category name (one per PDF) |
tags |
Array of strings | No | Tag names |
bookmarks |
Array of objects | No | User-defined page bookmarks (see below) |
metadata Object
Bibliographic metadata. Field names use dc:-prefixed keys based on Dublin Core Metadata Terms.
| Field | Type | Required | Description |
|---|---|---|---|
dc:title |
String | Yes | Title |
dc:creator |
Array of strings | No | Author(s) |
dc:date |
String | No | Date. "2024", "2024-06", and "2024-06-15" are all valid |
dc:publisher |
String | No | Publisher |
dc:language |
String | No | Language code (e.g., "ja", "en") |
dc:subject |
Array of strings | No | Subjects / keywords |
dc:identifier |
String | No | Identifier (e.g., ISBN) |
reading Object
| Field | Type | Required | Description |
|---|---|---|---|
lastReadPage |
Integer | Yes | Last read page number (1-based) |
totalPages |
Integer | Yes | Total number of pages |
lastReadAt |
String | Yes | Last read timestamp (ISO 8601) |
status |
String | No | Reading status: "unread" / "reading" / "finished" |
finishedAt |
String | No | Completion timestamp (ISO 8601) |
display Object
| Field | Type | Required | Description |
|---|---|---|---|
direction |
String | Yes | Reading direction: "LTR" (left-to-right) / "RTL" (right-to-left) |
pageLayout |
String | No | Page layout: "single" / "spread" / "spread-with-cover" |
crop |
Object or null |
No | Margin crop (see below). Absent or null means no crop |
display.crop Object
A non-destructive margin crop applied when rendering pages. The PDF file is never modified; readers apply the crop to the displayed page bounds.
| Field | Type | Required | Description |
|---|---|---|---|
odd |
Object | Yes | Insets for odd-numbered pages (1, 3, 5, …) (see below) |
even |
Object | Yes | Insets for even-numbered pages (2, 4, 6, …) (see below) |
excludeFirstPage |
Boolean | No | When true, page 1 (typically the cover) is displayed uncropped. Defaults to false |
Parity is determined by the PDF page number (1-based) and is independent of pageLayout and direction. For a uniform crop, write the same values to both odd and even.
odd / even are objects with the following four fields. Values are ratios (0.0–1.0) of the page's MediaBox, expressed in the page's displayed orientation (after /Rotate is applied).
| Field | Type | Required | Description |
|---|---|---|---|
top |
Number | Yes | Inset from the top edge, as a ratio of the page height |
bottom |
Number | Yes | Inset from the bottom edge, as a ratio of the page height |
left |
Number | Yes | Inset from the left edge, as a ratio of the page width |
right |
Number | Yes | Inset from the right edge, as a ratio of the page width |
Each value must be >= 0, with top + bottom < 1 and left + right < 1. Readers treat a crop that violates these constraints as absent rather than failing. Tools that rewrite display must preserve crop.
bookmarks Array
An array of user-defined page bookmarks (digital "sticky notes"). These are distinct from the PDF's built-in document outline; readers add them to mark pages of interest.
| Field | Type | Required | Description |
|---|---|---|---|
page |
Integer | Yes | Bookmarked page number (1-based) |
label |
String | No | Bookmark label. When omitted, readers may fall back to the page number |
collection Object
An object representing the series or magazine a PDF belongs to. Inspired by EPUB's belongs-to-collection / group-position, simplified to a single collection without a type distinction.
| Field | Type | Required | Description |
|---|---|---|---|
title |
String | Yes | Name of the collection or series |
position |
Number | No | Position within the collection. Fractional values allowed (e.g. 0.5 for a special half-volume). Omit for collections ordered by other means such as magazine publication date |
Categories File categories.json
Stored in the .shelff directory under the shelff document root, this file defines the available categories. Each PDF can belong to at most one category.
Example
{
"version": 1,
"categories": [
{ "name": "Technical", "order": 0 },
{ "name": "Fiction", "order": 1 },
{ "name": "Papers", "order": 2 }
]
}
| Field | Type | Description |
|---|---|---|
version |
Integer | Schema version. Currently 1 |
categories |
Array | Array of categories. Each element has name (string) and order (integer, 0-based) |
Tags File tags.json
Stored in the .shelff directory under the shelff document root, this file defines the display order of tags. Tags themselves are defined in the tags field of each PDF's metadata file. This file only manages display ordering.
Example
{
"version": 1,
"tagOrder": ["Swift", "SwiftUI", "Programming", "To Read"]
}
| Field | Type | Description |
|---|---|---|
version |
Integer | Schema version. Currently 1 |
tagOrder |
Array of strings | Tag display order. Tags not in this array are appended in name order |
Extension Fields
All three files allow additional top-level fields. Third-party tools and users storing custom data should use the x- prefix (e.g. x-calibre-id). The same applies inside the metadata object, where standard Dublin Core extensions such as dcterms: are also welcome. The reading and display objects do not allow additional fields.
Any tool that reads and writes these files, shelff included, must preserve fields it does not understand and write them back unchanged.
JSON Schema
Formal JSON Schema definitions for each file format are available in the following repository.