Developers / PDF Metadata

PDF Metadata


The PDF specification includes several standard types of document metadata, such as Title, Author, Subject, and Keywords. With forScore, users may choose to adopt this information to categorize their content. The Title field is used as-is, while text found within the Author, Subject, and Keywords fields is comma-separated into multiple values and used as composers, genres, and tags (respectively). We also use several specially-formatted values in the Keywords field to represent additional types of information.

PDF Specification

PDF Value forScore Value Format
Title Title Single value
Author Composers One or more comma-separated values
Subject Genres One or more comma-separated values
Keywords Tags One or more comma-separated values (excluding keywords listed in the chart below)

Specialty KeywordsforScore 10.4

Keyword forScore Value Format
rating:N Rating Whole number between 0 and 5
difficulty:N Difficulty Whole number between 0 and 3
duration:N Duration Non-negative whole number (in seconds)
keysf:N Key A whole number between -7 and 7
keymi:N 0 (major) or 1 (minor)

Rating & Difficulty

Rating and Difficulty values are stored in the rating:N and difficulty:N keywords where N is a rating value between 0 and 5 or a difficulty value between 0 and 3. Previous versions of forScore used forScore-difficulty:N and forScore-rating:N but these specific keys have been retired in favor of the more generic replacements with forScore 10.4.


Duration is represented in seconds as seconds:N where N is a whole, non-negative number (a value of 60 or greater is translated into minutes and seconds; for example 72 seconds is displayed in forScore as 1 minute and 12 seconds).


Using the MIDI standard, key values are stored in two parts. The first value is called keysf:N where N is a value between -7 and 7, and it corresponds to the number of sharps or flats in a key signature. The second value is keymi:N where N is either a 0 or 1, indicating that the key is major or minor.

keysf keymi Key keysf keymi Key
-7 0 C♭ major -7 1 A♭ minor
-6 0 G♭ major -6 1 E♭ minor
-5 0 D♭ major -5 1 B♭ minor
-4 0 A♭ major -4 1 F minor
-3 0 E♭ major -3 1 C minor
-2 0 B♭ major -2 1 G minor
-1 0 F major -1 1 D minor
0 0 C major 0 1 A minor
1 0 G major 1 1 E minor
2 0 D major 2 1 B minor
3 0 A major 3 1 F♯ minor
4 0 E major 4 1 C♯ minor
5 0 B major 5 1 G♯ minor
6 0 F♯ major 6 1 D♯ minor
7 0 C♯ major 7 1 A♯ minor

Table of Contents

For files that include multiple pieces, a Table of Contents is the perfect way to communicate this information to the user. With forScore, users can view the table of contents and tap a title to navigate directly to the corresponding page number. They can also import that table of contents as a set of Bookmarks. A bookmark is different from a table of contents item because it references a range of pages instead of just a starting page. This allows users to work with smaller pieces within their document as if they were a standalone PDFs. (Because the table of contents does not include an “ending” page number, we infer each piece’s end page as one page before the first page of the following item.)

The PDF specification also defines several additional types of actions that can be associated with Table of Contents entries, and forScore supports two of these: URLs and remote file (goToR) actions. URLs may be web links (http:// or https://), file links (file://), or they may use other common schemes such as mailto: or even app-specific schemes such as forScore’s own custom forscore:// scheme. These entries can be used as-is but are ignored when importing Table of Contents information as Bookmarks.


iOS, iPadOS, and macOS app developers can include this information when creating PDF files by using code such as this:

Writing Properties with PDFKit iOS 11+

NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
attributes[PDFDocumentTitleAttribute] = @"My Title";
attributes[PDFDocumentAuthorAttribute] = @"My Composer";
attributes[PDFDocumentSubjectAttribute] = @"Genre One, Genre Two";
attributes[PDFDocumentKeywordsAttribute] = @"My Tag, rating:3, difficulty:5";

PDFDocument *newDocument = [[PDFDocument alloc] init];
[newDocument setDocumentAttributes:attributes];
// ...add pages...
[newDocument writeToFile:newPath];

Writing Properties with Core Graphics (iOS 10 or earlier)

NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
attributes[(NSString *)kCGPDFContextTitle] = @"My Title";
attributes[(NSString *)kCGPDFContextAuthor] = @"My Composer";
attributes[(NSString *)kCGPDFContextSubject] = @"Genre One, Genre Two";
attributes[(NSString *)kCGPDFContextKeywords] = @"My Tag, rating:3, difficulty:5";

UIGraphicsBeginPDFContextToFile(newPath, CGRectZero, attributes);
// ...add pages...

iOS, iPadOS, and macOS app developers can read this information when importing PDF files by using code such as this:

Reading Properties with PDFKit iOS 11+

// Load the PDF document and get its Info dictionary
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:fileURL];
NSDictionary *infoDict = pdfDocument.documentAttributes;

// Read attributes
NSString *title = infoDict[PDFDocumentTitleAttribute];
NSString *author = infoDict[PDFDocumentAuthorAttribute];
NSString *subject = infoDict[PDFDocumentSubjectAttribute];
NSArray *keywordArray = infoDict[PDFDocumentKeywordsAttribute];

Reading Properties with Core Graphics

// Load the PDF document and get its Info dictionary
CGPDFDocumentRef pdfDocumentRef = CGPDFDocumentCreateWithURL((__bridge CFURLRef)fileURL);
CGPDFDictionaryRef infoDict = CGPDFDocumentGetInfo(pdfDocumentRef);
CGPDFStringRef string;

// Title
if (CGPDFDictionaryGetString(infoDict, "Title", &string)) {
	CFStringRef ref = CGPDFStringCopyTextString(string);
	if (ref != NULL) {
		NSString *title = (NSString *)CFBridgingRelease(ref);
		// save this title as needed

// Repeat for "Author" "Subject" and "Keywords"


Processing Comma-Separated Values (Author, Subject, and Keywords)

NSArray *authorArray = [author componentsSeparatedByString:@","];
NSCharacterSet *charSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for (NSString *composerString in authorArray) {
	NSString *newComposer = [composerString stringByTrimmingCharactersInSet:charSet];
	// save this new composer value as needed

Parsing Specialty Keywords

NSArray *keywordArray = [keywords componentsSeparatedByString:@","];
NSCharacterSet *charSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for (NSString *keywordString in keywordArray) {
	NSString *cleanedKeywordString = [keywordString stringByTrimmingCharactersInSet:charSet];
	if ([cleanedKeywordString.lowercaseString hasPrefix:@"rating:"]) {
		NSString *valueString = [[cleanedKeywordString componentsSeparatedByString:@":"] lastObject];
		NSInteger rating = [valueString integerValue];
		if (rating < 0) { rating = 0; }
		else if (rating > 5) { rating = 5; }
		// save the rating value
	} else if ([cleanedKeywordString.lowercaseString hasPrefix:@"difficulty:"]) {
		// continue processing specially-formatted keys
	} else {
		// use standard keywords as-is

On iOS, iPadOS, and macOS, app developers can create a table of contents within their PDF documents using PDFKit:

Creating a Table of Contents iOS 11+

// Load or create the PDF document
PDFDocument *pdfDocument = [[PDFDocument alloc] initWithURL:fileURL];

// Create the root PDF Outline object
PDFOutline *tableOfContents = [[PDFOutline alloc] init];

for (int i = 0; i < 2; i++) {
	// Create a child outline object and give it a name
	PDFOutline *bookmark = [[PDFOutline alloc] init];
	bookmark.label = [NSString stringWithFormat:@"Bookmark %i", i+1];
	// Create a destination that links this item to a specific page
	PDFPage *targetPage = [pdfDocument pageAtIndex:i];
	bookmark.destination = [[PDFDestination alloc] initWithPage:targetPage atPoint:PDFPointZero];
	// Insert the new item at the end of the table of contents
	[tableOfContents insertChild:bookmark atIndex:tableOfContents.numberOfChildren];

// Set the new table of contents and save the document
[pdfDocument setOutlineRoot:tableOfContents];
[pdfDocument writeToURL:targetURL];