Gathering detailed insights and metrics for @cantoo/pdf-lib
Gathering detailed insights and metrics for @cantoo/pdf-lib
Gathering detailed insights and metrics for @cantoo/pdf-lib
Gathering detailed insights and metrics for @cantoo/pdf-lib
npm install @cantoo/pdf-lib
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
142 Stars
644 Commits
29 Forks
5 Watching
11 Branches
2 Contributors
Updated on 19 Nov 2024
Minified
Minified + Gzipped
TypeScript (71.22%)
HTML (21.55%)
JavaScript (6.68%)
Objective-C (0.21%)
CSS (0.15%)
Starlark (0.11%)
Java (0.08%)
Cumulative downloads
Total Downloads
Last day
-14.3%
7,715
Compared to previous day
Last week
-2%
44,540
Compared to previous week
Last month
22.4%
196,030
Compared to previous month
Last year
2,994.3%
786,507
Compared to previous year
24
This fork adds the support for svg to the pdf-lib project. Until pdf-lib project gets a better maintainance, we will maintain this project as long as we need it but cannot guarantee the support for issues too far from our own roadmap.
Install with: npm install @cantoo/pdf-lib
Learn more at pdf-lib.js.org
pdf-lib
was created to address the JavaScript ecosystem's lack of robust support for PDF manipulation (especially for PDF modification).
Two of pdf-lib
's distinguishing features are:
There are other good open source JavaScript PDF libraries available. However, most of them can only create documents, they cannot modify existing ones. And many of them only work in particular environments.
This example produces this PDF.
1import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
2
3// Create a new PDFDocument
4const pdfDoc = await PDFDocument.create()
5
6// Embed the Times Roman font
7const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
8
9// Add a blank page to the document
10const page = pdfDoc.addPage()
11
12// Get the width and height of the page
13const { width, height } = page.getSize()
14
15// Draw a string of text toward the top of the page
16const fontSize = 30
17page.drawText('Creating PDFs in JavaScript is awesome!', {
18 x: 50,
19 y: height - 4 * fontSize,
20 size: fontSize,
21 font: timesRomanFont,
22 color: rgb(0, 0.53, 0.71),
23})
24
25// Serialize the PDFDocument to bytes (a Uint8Array)
26const pdfBytes = await pdfDoc.save()
27
28// For example, `pdfBytes` can be:
29// • Written to a file in Node
30// • Downloaded from the browser
31// • Rendered in an <iframe>
This example produces this PDF (when this PDF is used for the existingPdfBytes
variable).
1import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib'; 2 3// This should be a Uint8Array or ArrayBuffer 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const existingPdfBytes = ... 8 9// Load a PDFDocument from the existing PDF bytes 10const pdfDoc = await PDFDocument.load(existingPdfBytes) 11 12// Embed the Helvetica font 13const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) 14 15// Get the first page of the document 16const pages = pdfDoc.getPages() 17const firstPage = pages[0] 18 19// Get the width and height of the first page 20const { width, height } = firstPage.getSize() 21 22// Draw a string of text diagonally across the first page 23firstPage.drawText('This text was added with JavaScript!', { 24 x: 5, 25 y: height / 2 + 300, 26 size: 50, 27 font: helveticaFont, 28 color: rgb(0.95, 0.1, 0.1), 29 rotate: degrees(-45), 30}) 31 32 33// Serialize the PDFDocument to bytes (a Uint8Array) 34const pdfBytes = await pdfDoc.save() 35 36// For example, `pdfBytes` can be: 37// • Written to a file in Node 38// • Downloaded from the browser 39// • Rendered in an <iframe>
This example produces this PDF.
See also Creating and Filling Forms
1import { PDFDocument } from 'pdf-lib'
2
3// Create a new PDFDocument
4const pdfDoc = await PDFDocument.create()
5
6// Add a blank page to the document
7const page = pdfDoc.addPage([550, 750])
8
9// Get the form so we can add fields to it
10const form = pdfDoc.getForm()
11
12// Add the superhero text field and description
13page.drawText('Enter your favorite superhero:', { x: 50, y: 700, size: 20 })
14
15const superheroField = form.createTextField('favorite.superhero')
16superheroField.setText('One Punch Man')
17superheroField.addToPage(page, { x: 55, y: 640 })
18
19// Add the rocket radio group, labels, and description
20page.drawText('Select your favorite rocket:', { x: 50, y: 600, size: 20 })
21
22page.drawText('Falcon Heavy', { x: 120, y: 560, size: 18 })
23page.drawText('Saturn IV', { x: 120, y: 500, size: 18 })
24page.drawText('Delta IV Heavy', { x: 340, y: 560, size: 18 })
25page.drawText('Space Launch System', { x: 340, y: 500, size: 18 })
26
27const rocketField = form.createRadioGroup('favorite.rocket')
28rocketField.addOptionToPage('Falcon Heavy', page, { x: 55, y: 540 })
29rocketField.addOptionToPage('Saturn IV', page, { x: 55, y: 480 })
30rocketField.addOptionToPage('Delta IV Heavy', page, { x: 275, y: 540 })
31rocketField.addOptionToPage('Space Launch System', page, { x: 275, y: 480 })
32rocketField.select('Saturn IV')
33
34// Add the gundam check boxes, labels, and description
35page.drawText('Select your favorite gundams:', { x: 50, y: 440, size: 20 })
36
37page.drawText('Exia', { x: 120, y: 400, size: 18 })
38page.drawText('Kyrios', { x: 120, y: 340, size: 18 })
39page.drawText('Virtue', { x: 340, y: 400, size: 18 })
40page.drawText('Dynames', { x: 340, y: 340, size: 18 })
41
42const exiaField = form.createCheckBox('gundam.exia')
43const kyriosField = form.createCheckBox('gundam.kyrios')
44const virtueField = form.createCheckBox('gundam.virtue')
45const dynamesField = form.createCheckBox('gundam.dynames')
46
47exiaField.addToPage(page, { x: 55, y: 380 })
48kyriosField.addToPage(page, { x: 55, y: 320 })
49virtueField.addToPage(page, { x: 275, y: 380 })
50dynamesField.addToPage(page, { x: 275, y: 320 })
51
52exiaField.check()
53dynamesField.check()
54
55// Add the planet dropdown and description
56page.drawText('Select your favorite planet*:', { x: 50, y: 280, size: 20 })
57
58const planetsField = form.createDropdown('favorite.planet')
59planetsField.addOptions(['Venus', 'Earth', 'Mars', 'Pluto'])
60planetsField.select('Pluto')
61planetsField.addToPage(page, { x: 55, y: 220 })
62
63// Add the person option list and description
64page.drawText('Select your favorite person:', { x: 50, y: 180, size: 18 })
65
66const personField = form.createOptionList('favorite.person')
67personField.addOptions([
68 'Julius Caesar',
69 'Ada Lovelace',
70 'Cleopatra',
71 'Aaron Burr',
72 'Mark Antony',
73])
74personField.select('Ada Lovelace')
75personField.addToPage(page, { x: 55, y: 70 })
76
77// Just saying...
78page.drawText(`* Pluto should be a planet too!`, { x: 15, y: 15, size: 15 })
79
80// Serialize the PDFDocument to bytes (a Uint8Array)
81const pdfBytes = await pdfDoc.save()
82
83// For example, `pdfBytes` can be:
84// • Written to a file in Node
85// • Downloaded from the browser
86// • Rendered in an <iframe>
This example produces this PDF (when this PDF is used for the formPdfBytes
variable, this image is used for the marioImageBytes
variable, and this image is used for the emblemImageBytes
variable).
See also Creating and Filling Forms
1import { PDFDocument } from 'pdf-lib' 2 3// These should be Uint8Arrays or ArrayBuffers 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const formPdfBytes = ... 8const marioImageBytes = ... 9const emblemImageBytes = ... 10 11// Load a PDF with form fields 12const pdfDoc = await PDFDocument.load(formPdfBytes) 13 14// Embed the Mario and emblem images 15const marioImage = await pdfDoc.embedPng(marioImageBytes) 16const emblemImage = await pdfDoc.embedPng(emblemImageBytes) 17 18// Get the form containing all the fields 19const form = pdfDoc.getForm() 20 21// Get all fields in the PDF by their names 22const nameField = form.getTextField('CharacterName 2') 23const ageField = form.getTextField('Age') 24const heightField = form.getTextField('Height') 25const weightField = form.getTextField('Weight') 26const eyesField = form.getTextField('Eyes') 27const skinField = form.getTextField('Skin') 28const hairField = form.getTextField('Hair') 29 30const alliesField = form.getTextField('Allies') 31const factionField = form.getTextField('FactionName') 32const backstoryField = form.getTextField('Backstory') 33const traitsField = form.getTextField('Feat+Traits') 34const treasureField = form.getTextField('Treasure') 35 36const characterImageField = form.getButton('CHARACTER IMAGE') 37const factionImageField = form.getTextField('Faction Symbol Image') 38 39// Fill in the basic info fields 40nameField.setText('Mario') 41ageField.setText('24 years') 42heightField.setText(`5' 1"`) 43weightField.setText('196 lbs') 44eyesField.setText('blue') 45skinField.setText('white') 46hairField.setText('brown') 47 48// Fill the character image field with our Mario image 49characterImageField.setImage(marioImage) 50 51// Fill in the allies field 52alliesField.setText( 53 [ 54 `Allies:`, 55 ` • Princess Daisy`, 56 ` • Princess Peach`, 57 ` • Rosalina`, 58 ` • Geno`, 59 ` • Luigi`, 60 ` • Donkey Kong`, 61 ` • Yoshi`, 62 ` • Diddy Kong`, 63 ``, 64 `Organizations:`, 65 ` • Italian Plumbers Association`, 66 ].join('\n'), 67) 68 69// Fill in the faction name field 70factionField.setText(`Mario's Emblem`) 71 72// Fill the faction image field with our emblem image 73factionImageField.setImage(emblemImage) 74 75// Fill in the backstory field 76backstoryField.setText( 77 `Mario is a fictional character in the Mario video game franchise, owned by Nintendo and created by Japanese video game designer Shigeru Miyamoto. Serving as the company's mascot and the eponymous protagonist of the series, Mario has appeared in over 200 video games since his creation. Depicted as a short, pudgy, Italian plumber who resides in the Mushroom Kingdom, his adventures generally center upon rescuing Princess Peach from the Koopa villain Bowser. His younger brother and sidekick is Luigi.`, 78) 79 80// Fill in the traits field 81traitsField.setText( 82 [ 83 `Mario can use three basic three power-ups:`, 84 ` • the Super Mushroom, which causes Mario to grow larger`, 85 ` • the Fire Flower, which allows Mario to throw fireballs`, 86 ` • the Starman, which gives Mario temporary invincibility`, 87 ].join('\n'), 88) 89 90// Fill in the treasure field 91treasureField.setText(['• Gold coins', '• Treasure chests'].join('\n')) 92 93// Serialize the PDFDocument to bytes (a Uint8Array) 94const pdfBytes = await pdfDoc.save() 95 96// For example, `pdfBytes` can be: 97// • Written to a file in Node 98// • Downloaded from the browser 99// • Rendered in an <iframe>
This example produces this PDF (when this PDF is used for the formPdfBytes
variable).
1import { PDFDocument } from 'pdf-lib' 2 3// This should be a Uint8Array or ArrayBuffer 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const formPdfBytes = ... 8 9// Load a PDF with form fields 10const pdfDoc = await PDFDocument.load(formPdfBytes) 11 12// Get the form containing all the fields 13const form = pdfDoc.getForm() 14 15// Fill the form's fields 16form.getTextField('Text1').setText('Some Text'); 17 18form.getRadioGroup('Group2').select('Choice1'); 19form.getRadioGroup('Group3').select('Choice3'); 20form.getRadioGroup('Group4').select('Choice1'); 21 22form.getCheckBox('Check Box3').check(); 23form.getCheckBox('Check Box4').uncheck(); 24 25form.getDropdown('Dropdown7').select('Infinity'); 26 27form.getOptionList('List Box6').select('Honda'); 28 29// Flatten the form's fields 30form.flatten(); 31 32// Serialize the PDFDocument to bytes (a Uint8Array) 33const pdfBytes = await pdfDoc.save() 34 35// For example, `pdfBytes` can be: 36// • Written to a file in Node 37// • Downloaded from the browser 38// • Rendered in an <iframe>
This example produces this PDF (when this PDF is used for the firstDonorPdfBytes
variable and this PDF is used for the secondDonorPdfBytes
variable).
1import { PDFDocument } from 'pdf-lib' 2 3// Create a new PDFDocument 4const pdfDoc = await PDFDocument.create() 5 6// These should be Uint8Arrays or ArrayBuffers 7// This data can be obtained in a number of different ways 8// If your running in a Node environment, you could use fs.readFile() 9// In the browser, you could make a fetch() call and use res.arrayBuffer() 10const firstDonorPdfBytes = ... 11const secondDonorPdfBytes = ... 12 13// Load a PDFDocument from each of the existing PDFs 14const firstDonorPdfDoc = await PDFDocument.load(firstDonorPdfBytes) 15const secondDonorPdfDoc = await PDFDocument.load(secondDonorPdfBytes) 16 17// Copy the 1st page from the first donor document, and 18// the 743rd page from the second donor document 19const [firstDonorPage] = await pdfDoc.copyPages(firstDonorPdfDoc, [0]) 20const [secondDonorPage] = await pdfDoc.copyPages(secondDonorPdfDoc, [742]) 21 22// Add the first copied page 23pdfDoc.addPage(firstDonorPage) 24 25// Insert the second copied page to index 0, so it will be the 26// first page in `pdfDoc` 27pdfDoc.insertPage(0, secondDonorPage) 28 29// Serialize the PDFDocument to bytes (a Uint8Array) 30const pdfBytes = await pdfDoc.save() 31 32// For example, `pdfBytes` can be: 33// • Written to a file in Node 34// • Downloaded from the browser 35// • Rendered in an <iframe>
This example produces this PDF (when this image is used for the jpgImageBytes
variable and this image is used for the pngImageBytes
variable).
1import { PDFDocument } from 'pdf-lib' 2 3// These should be Uint8Arrays or ArrayBuffers 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const jpgImageBytes = ... 8const pngImageBytes = ... 9 10// Create a new PDFDocument 11const pdfDoc = await PDFDocument.create() 12 13// Embed the JPG image bytes and PNG image bytes 14const jpgImage = await pdfDoc.embedJpg(jpgImageBytes) 15const pngImage = await pdfDoc.embedPng(pngImageBytes) 16 17// Get the width/height of the JPG image scaled down to 25% of its original size 18const jpgDims = jpgImage.scale(0.25) 19 20// Get the width/height of the PNG image scaled down to 50% of its original size 21const pngDims = pngImage.scale(0.5) 22 23// Add a blank page to the document 24const page = pdfDoc.addPage() 25 26// Draw the JPG image in the center of the page 27page.drawImage(jpgImage, { 28 x: page.getWidth() / 2 - jpgDims.width / 2, 29 y: page.getHeight() / 2 - jpgDims.height / 2, 30 width: jpgDims.width, 31 height: jpgDims.height, 32}) 33 34// Draw the PNG image near the lower right corner of the JPG image 35page.drawImage(pngImage, { 36 x: page.getWidth() / 2 - pngDims.width / 2 + 75, 37 y: page.getHeight() / 2 - pngDims.height, 38 width: pngDims.width, 39 height: pngDims.height, 40}) 41 42// Serialize the PDFDocument to bytes (a Uint8Array) 43const pdfBytes = await pdfDoc.save() 44 45// For example, `pdfBytes` can be: 46// • Written to a file in Node 47// • Downloaded from the browser 48// • Rendered in an <iframe>
This example produces this PDF (when this PDF is used for the americanFlagPdfBytes
variable and this PDF is used for the usConstitutionPdfBytes
variable).
1import { PDFDocument } from 'pdf-lib' 2 3// These should be Uint8Arrays or ArrayBuffers 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const americanFlagPdfBytes = ... 8const usConstitutionPdfBytes = ... 9 10// Create a new PDFDocument 11const pdfDoc = await PDFDocument.create() 12 13// Embed the American flag PDF bytes 14const [americanFlag] = await pdfDoc.embedPdf(americanFlagPdfBytes) 15 16// Load the U.S. constitution PDF bytes 17const usConstitutionPdf = await PDFDocument.load(usConstitutionPdfBytes) 18 19// Embed the second page of the constitution and clip the preamble 20const preamble = await pdfDoc.embedPage(usConstitutionPdf.getPages()[1], { 21 left: 55, 22 bottom: 485, 23 right: 300, 24 top: 575, 25}) 26 27// Get the width/height of the American flag PDF scaled down to 30% of 28// its original size 29const americanFlagDims = americanFlag.scale(0.3) 30 31// Get the width/height of the preamble clipping scaled up to 225% of 32// its original size 33const preambleDims = preamble.scale(2.25) 34 35// Add a blank page to the document 36const page = pdfDoc.addPage() 37 38// Draw the American flag image in the center top of the page 39page.drawPage(americanFlag, { 40 ...americanFlagDims, 41 x: page.getWidth() / 2 - americanFlagDims.width / 2, 42 y: page.getHeight() - americanFlagDims.height - 150, 43}) 44 45// Draw the preamble clipping in the center bottom of the page 46page.drawPage(preamble, { 47 ...preambleDims, 48 x: page.getWidth() / 2 - preambleDims.width / 2, 49 y: page.getHeight() / 2 - preambleDims.height / 2 - 50, 50}) 51 52// Serialize the PDFDocument to bytes (a Uint8Array) 53const pdfBytes = await pdfDoc.save() 54 55// For example, `pdfBytes` can be: 56// • Written to a file in Node 57// • Downloaded from the browser 58// • Rendered in an <iframe>
pdf-lib
relies on a sister module to support embedding custom fonts: @pdf-lib/fontkit
. You must add the @pdf-lib/fontkit
module to your project and register it using pdfDoc.registerFontkit(...)
before embedding custom fonts.
This example produces this PDF (when this font is used for the fontBytes
variable).
1import { PDFDocument, rgb } from 'pdf-lib'
2import fontkit from '@pdf-lib/fontkit'
3
4// This should be a Uint8Array or ArrayBuffer
5// This data can be obtained in a number of different ways
6// If you're running in a Node environment, you could use fs.readFile()
7// In the browser, you could make a fetch() call and use res.arrayBuffer()
8const fontBytes = ...
9
10// Create a new PDFDocument
11const pdfDoc = await PDFDocument.create()
12
13// Register the `fontkit` instance
14pdfDoc.registerFontkit(fontkit)
15
16// Embed our custom font in the document
17const customFont = await pdfDoc.embedFont(fontBytes)
18
19// Add a blank page to the document
20const page = pdfDoc.addPage()
21
22// Create a string of text and measure its width and height in our custom font
23const text = 'This is text in an embedded font!'
24const textSize = 35
25const textWidth = customFont.widthOfTextAtSize(text, textSize)
26const textHeight = customFont.heightAtSize(textSize)
27
28// Draw the string of text on the page
29page.drawText(text, {
30 x: 40,
31 y: 450,
32 size: textSize,
33 font: customFont,
34 color: rgb(0, 0.53, 0.71),
35})
36
37// Draw a box around the string of text
38page.drawRectangle({
39 x: 40,
40 y: 450,
41 width: textWidth,
42 height: textHeight,
43 borderColor: rgb(1, 0, 0),
44 borderWidth: 1.5,
45})
46
47// Serialize the PDFDocument to bytes (a Uint8Array)
48const pdfBytes = await pdfDoc.save()
49
50// For example, `pdfBytes` can be:
51// • Written to a file in Node
52// • Downloaded from the browser
53// • Rendered in an <iframe>
This example produces this PDF (when this image is used for the jpgAttachmentBytes
variable and this PDF is used for the pdfAttachmentBytes
variable).
1import { PDFDocument } from 'pdf-lib' 2 3// These should be Uint8Arrays or ArrayBuffers 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const jpgAttachmentBytes = ... 8const pdfAttachmentBytes = ... 9 10// Create a new PDFDocument 11const pdfDoc = await PDFDocument.create() 12 13// Add the JPG attachment 14await pdfDoc.attach(jpgAttachmentBytes, 'cat_riding_unicorn.jpg', { 15 mimeType: 'image/jpeg', 16 description: 'Cool cat riding a unicorn! 🦄🐈🕶️', 17 creationDate: new Date('2019/12/01'), 18 modificationDate: new Date('2020/04/19'), 19}) 20 21// Add the PDF attachment 22await pdfDoc.attach(pdfAttachmentBytes, 'us_constitution.pdf', { 23 mimeType: 'application/pdf', 24 description: 'Constitution of the United States 🇺🇸🦅', 25 creationDate: new Date('1787/09/17'), 26 modificationDate: new Date('1992/05/07'), 27}) 28 29// Add a page with some text 30const page = pdfDoc.addPage(); 31page.drawText('This PDF has two attachments', { x: 135, y: 415 }) 32 33// Serialize the PDFDocument to bytes (a Uint8Array) 34const pdfBytes = await pdfDoc.save() 35 36// For example, `pdfBytes` can be: 37// • Written to a file in Node 38// • Downloaded from the browser 39// • Rendered in an <iframe>
This example produces this PDF.
1import { PDFDocument, StandardFonts } from 'pdf-lib'
2
3// Create a new PDFDocument
4const pdfDoc = await PDFDocument.create()
5
6// Embed the Times Roman font
7const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
8
9// Add a page and draw some text on it
10const page = pdfDoc.addPage([500, 600])
11page.setFont(timesRomanFont)
12page.drawText('The Life of an Egg', { x: 60, y: 500, size: 50 })
13page.drawText('An Epic Tale of Woe', { x: 125, y: 460, size: 25 })
14
15// Set all available metadata fields on the PDFDocument. Note that these fields
16// are visible in the "Document Properties" section of most PDF readers.
17pdfDoc.setTitle('🥚 The Life of an Egg 🍳')
18pdfDoc.setAuthor('Humpty Dumpty')
19pdfDoc.setSubject('📘 An Epic Tale of Woe 📖')
20pdfDoc.setKeywords(['eggs', 'wall', 'fall', 'king', 'horses', 'men'])
21pdfDoc.setProducer('PDF App 9000 🤖')
22pdfDoc.setCreator('pdf-lib (https://github.com/Hopding/pdf-lib)')
23pdfDoc.setCreationDate(new Date('2018-06-24T01:58:37.228Z'))
24pdfDoc.setModificationDate(new Date('2019-12-21T07:00:11.000Z'))
25
26// Serialize the PDFDocument to bytes (a Uint8Array)
27const pdfBytes = await pdfDoc.save()
28
29// For example, `pdfBytes` can be:
30// • Written to a file in Node
31// • Downloaded from the browser
32// • Rendered in an <iframe>
1import { PDFDocument } from 'pdf-lib' 2 3// This should be a Uint8Array or ArrayBuffer 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const existingPdfBytes = ... 8 9// Load a PDFDocument without updating its existing metadata 10const pdfDoc = await PDFDocument.load(existingPdfBytes, { 11 updateMetadata: false 12}) 13 14// Print all available metadata fields 15console.log('Title:', pdfDoc.getTitle()) 16console.log('Author:', pdfDoc.getAuthor()) 17console.log('Subject:', pdfDoc.getSubject()) 18console.log('Creator:', pdfDoc.getCreator()) 19console.log('Keywords:', pdfDoc.getKeywords()) 20console.log('Producer:', pdfDoc.getProducer()) 21console.log('Creation Date:', pdfDoc.getCreationDate()) 22console.log('Modification Date:', pdfDoc.getModificationDate())
This script outputs the following (when this PDF is used for the existingPdfBytes
variable):
Title: Microsoft Word - Basic Curriculum Vitae example.doc
Author: Administrator
Subject: undefined
Creator: PScript5.dll Version 5.2
Keywords: undefined
Producer: Acrobat Distiller 8.1.0 (Windows)
Creation Date: 2010-07-29T14:26:00.000Z
Modification Date: 2010-07-29T14:26:00.000Z
1import {
2 PDFDocument,
3 StandardFonts,
4 NonFullScreenPageMode,
5 ReadingDirection,
6 PrintScaling,
7 Duplex,
8 PDFName,
9} from 'pdf-lib'
10
11// Create a new PDFDocument
12const pdfDoc = await PDFDocument.create()
13
14// Embed the Times Roman font
15const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman)
16
17// Add a page and draw some text on it
18const page = pdfDoc.addPage([500, 600])
19page.setFont(timesRomanFont)
20page.drawText('The Life of an Egg', { x: 60, y: 500, size: 50 })
21page.drawText('An Epic Tale of Woe', { x: 125, y: 460, size: 25 })
22
23// Set all available viewer preferences on the PDFDocument:
24const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences()
25viewerPrefs.setHideToolbar(true)
26viewerPrefs.setHideMenubar(true)
27viewerPrefs.setHideWindowUI(true)
28viewerPrefs.setFitWindow(true)
29viewerPrefs.setCenterWindow(true)
30viewerPrefs.setDisplayDocTitle(true)
31
32// Set the PageMode (otherwise setting NonFullScreenPageMode has no meaning)
33pdfDoc.catalog.set(PDFName.of('PageMode'), PDFName.of('FullScreen'))
34
35// Set what happens when fullScreen is closed
36viewerPrefs.setNonFullScreenPageMode(NonFullScreenPageMode.UseOutlines)
37
38viewerPrefs.setReadingDirection(ReadingDirection.L2R)
39viewerPrefs.setPrintScaling(PrintScaling.None)
40viewerPrefs.setDuplex(Duplex.DuplexFlipLongEdge)
41viewerPrefs.setPickTrayByPDFSize(true)
42
43// We can set the default print range to only the first page
44viewerPrefs.setPrintPageRange({ start: 0, end: 0 })
45
46// Or we can supply noncontiguous ranges (e.g. pages 1, 3, and 5-7)
47viewerPrefs.setPrintPageRange([
48 { start: 0, end: 0 },
49 { start: 2, end: 2 },
50 { start: 4, end: 6 },
51])
52
53viewerPrefs.setNumCopies(2)
54
55// Serialize the PDFDocument to bytes (a Uint8Array)
56const pdfBytes = await pdfDoc.save()
57
58// For example, `pdfBytes` can be:
59// • Written to a file in Node
60// • Downloaded from the browser
61// • Rendered in an <iframe>
1import { PDFDocument } from 'pdf-lib' 2 3// This should be a Uint8Array or ArrayBuffer 4// This data can be obtained in a number of different ways 5// If your running in a Node environment, you could use fs.readFile() 6// In the browser, you could make a fetch() call and use res.arrayBuffer() 7const existingPdfBytes = ... 8 9// Load a PDFDocument without updating its existing metadata 10const pdfDoc = await PDFDocument.load(existingPdfBytes) 11const viewerPrefs = pdfDoc.catalog.getOrCreateViewerPreferences() 12 13// Print all available viewer preference fields 14console.log('HideToolbar:', viewerPrefs.getHideToolbar()) 15console.log('HideMenubar:', viewerPrefs.getHideMenubar()) 16console.log('HideWindowUI:', viewerPrefs.getHideWindowUI()) 17console.log('FitWindow:', viewerPrefs.getFitWindow()) 18console.log('CenterWindow:', viewerPrefs.getCenterWindow()) 19console.log('DisplayDocTitle:', viewerPrefs.getDisplayDocTitle()) 20console.log('NonFullScreenPageMode:', viewerPrefs.getNonFullScreenPageMode()) 21console.log('ReadingDirection:', viewerPrefs.getReadingDirection()) 22console.log('PrintScaling:', viewerPrefs.getPrintScaling()) 23console.log('Duplex:', viewerPrefs.getDuplex()) 24console.log('PickTrayByPDFSize:', viewerPrefs.getPickTrayByPDFSize()) 25console.log('PrintPageRange:', viewerPrefs.getPrintPageRange()) 26console.log('NumCopies:', viewerPrefs.getNumCopies())
This script outputs the following (when this PDF is used for the existingPdfBytes
variable):
HideToolbar: true
HideMenubar: true
HideWindowUI: false
FitWindow: true
CenterWindow: true
DisplayDocTitle: true
NonFullScreenPageMode: UseNone
ReadingDirection: R2L
PrintScaling: None
Duplex: DuplexFlipLongEdge
PickTrayByPDFSize: true
PrintPageRange: [ { start: 1, end: 1 }, { start: 3, end: 4 } ]
NumCopies: 2
This example produces this PDF.
1import { PDFDocument, rgb } from 'pdf-lib'
2
3// SVG path for a wavy line
4const svgPath =
5 'M 0,20 L 100,160 Q 130,200 150,120 C 190,-40 200,200 300,150 L 400,90'
6
7// Create a new PDFDocument
8const pdfDoc = await PDFDocument.create()
9
10// Add a blank page to the document
11const page = pdfDoc.addPage()
12page.moveTo(100, page.getHeight() - 5)
13
14// Draw the SVG path as a black line
15page.moveDown(25)
16page.drawSvgPath(svgPath)
17
18// Draw the SVG path as a thick green line
19page.moveDown(200)
20page.drawSvgPath(svgPath, { borderColor: rgb(0, 1, 0), borderWidth: 5 })
21
22// Draw the SVG path and fill it with red
23page.moveDown(200)
24page.drawSvgPath(svgPath, { color: rgb(1, 0, 0) })
25
26// Draw the SVG path at 50% of its original size
27page.moveDown(200)
28page.drawSvgPath(svgPath, { scale: 0.5 })
29
30// Serialize the PDFDocument to bytes (a Uint8Array)
31const pdfBytes = await pdfDoc.save()
32
33// For example, `pdfBytes` can be:
34// • Written to a file in Node
35// • Downloaded from the browser
36// • Rendered in an <iframe>
1import { PDFDocument, rgb } from 'pdf-lib' 2 3// SVG of a square inside a square 4const svg = `<svg width="100" height="100"> 5 <rect y="0" x="0" width="100" height="100" fill="none" stroke="black"/> 6 <rect y="25" x="25" width="50" height="50" fill="black"/> 7</svg>`; 8const svg2 = '<svg><image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="/></svg>' 9 10// Create a new PDFDocument 11const pdfDoc = await PDFDocument.create() 12 13// Add a blank page to the document 14const page = pdfDoc.addPage() 15 16// drawSvg can accept the svg as a string, as long as there are no images in it 17page.moveTo(100, 10) 18page.drawSvg(svg) 19 20// If the svg has images, or if you don't know if it does, you should call embedSVG first 21page.moveTo(200, 10) 22const pdfSvg = await pdfDoc.embedSvg(svg2) 23page.drawSvg(pdfSvg) 24 25// Serialize the PDFDocument to bytes (a Uint8Array) 26const pdfBytes = await pdfDoc.save()
pdf-lib
fully supports the exciting new Deno runtime! All of the usage examples work in Deno. The only thing you need to do is change the imports for pdf-lib
and @pdf-lib/fontkit
to use the Skypack CDN, because Deno requires all modules to be referenced via URLs.
See also How to Create and Modify PDF Files in Deno With pdf-lib
Below is the create document example modified for Deno:
1import {
2 PDFDocument,
3 StandardFonts,
4 rgb,
5} from 'https://cdn.skypack.dev/pdf-lib@^1.11.1?dts';
6
7const pdfDoc = await PDFDocument.create();
8const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
9
10const page = pdfDoc.addPage();
11const { width, height } = page.getSize();
12const fontSize = 30;
13page.drawText('Creating PDFs in JavaScript is awesome!', {
14 x: 50,
15 y: height - 4 * fontSize,
16 size: fontSize,
17 font: timesRomanFont,
18 color: rgb(0, 0.53, 0.71),
19});
20
21const pdfBytes = await pdfDoc.save();
22
23await Deno.writeFile('out.pdf', pdfBytes);
If you save this script as create-document.ts
, you can execute it using Deno with the following command:
deno run --allow-write create-document.ts
The resulting out.pdf
file will look like this PDF.
Here's a slightly more complicated example demonstrating how to embed a font and measure text in Deno:
1import { 2 degrees, 3 PDFDocument, 4 rgb, 5 StandardFonts, 6} from 'https://cdn.skypack.dev/pdf-lib@^1.11.1?dts'; 7import fontkit from 'https://cdn.skypack.dev/@pdf-lib/fontkit@^1.0.0?dts'; 8 9const url = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf'; 10const fontBytes = await fetch(url).then((res) => res.arrayBuffer()); 11 12const pdfDoc = await PDFDocument.create(); 13 14pdfDoc.registerFontkit(fontkit); 15const customFont = await pdfDoc.embedFont(fontBytes); 16 17const page = pdfDoc.addPage(); 18 19const text = 'This is text in an embedded font!'; 20const textSize = 35; 21const textWidth = customFont.widthOfTextAtSize(text, textSize); 22const textHeight = customFont.heightAtSize(textSize); 23 24page.drawText(text, { 25 x: 40, 26 y: 450, 27 size: textSize, 28 font: customFont, 29 color: rgb(0, 0.53, 0.71), 30}); 31page.drawRectangle({ 32 x: 40, 33 y: 450, 34 width: textWidth, 35 height: textHeight, 36 borderColor: rgb(1, 0, 0), 37 borderWidth: 1.5, 38}); 39 40const pdfBytes = await pdfDoc.save(); 41 42await Deno.writeFile('out.pdf', pdfBytes);
If you save this script as custom-font.ts
, you can execute it with the following command:
deno run --allow-write --allow-net custom-font.ts
The resulting out.pdf
file will look like this PDF.
The usage examples provide code that is brief and to the point, demonstrating the different features of pdf-lib
. You can find complete working examples in the apps/
directory. These apps are used to do manual testing of pdf-lib
before every release (in addition to the automated tests).
There are currently four apps:
node
- contains tests for pdf-lib
in Node environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib
from the filesystem. They also allow you to quickly open your PDFs in different viewers (Acrobat, Preview, Foxit, Chrome, Firefox, etc...) to ensure compatibility.web
- contains tests for pdf-lib
in browser environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib
in a browser environment.rn
- contains tests for pdf-lib
in React Native environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib
in a React Native environment.deno
- contains tests for pdf-lib
in Deno environments. These tests are a handy reference when trying to save/load PDFs, fonts, or images with pdf-lib
from the filesystem.To install the latest stable version:
1# With npm 2npm install --save pdf-lib 3 4# With yarn 5yarn add pdf-lib
This assumes you're using npm or yarn as your package manager.
You can also download pdf-lib
as a UMD module from unpkg or jsDelivr. The UMD builds have been compiled to ES5, so they should work in any modern browser. UMD builds are useful if you aren't using a package manager or module bundler. For example, you can use them directly in the <script>
tag of an HTML page.
The following builds are available:
NOTE: if you are using the CDN scripts in production, you should include a specific version number in the URL, for example:
When using a UMD build, you will have access to a global window.PDFLib
variable. This variable contains all of the classes and functions exported by pdf-lib
. For example:
1// NPM module 2import { PDFDocument, rgb } from 'pdf-lib'; 3 4// UMD module 5var PDFDocument = PDFLib.PDFDocument; 6var rgb = PDFLib.rgb;
pdf-lib
relies upon a sister module to support embedding custom fonts: @pdf-lib/fontkit
. You must add the @pdf-lib/fontkit
module to your project and register it using pdfDoc.registerFontkit(...)
before embedding custom fonts (see the font embedding example). This module is not included by default because not all users need it, and it increases bundle size.
Installing this module is easy. Just like pdf-lib
itself, @pdf-lib/fontkit
can be installed with npm
/yarn
or as a UMD module.
1# With npm 2npm install --save @pdf-lib/fontkit 3 4# With yarn 5yarn add @pdf-lib/fontkit
To register the fontkit
instance:
1import { PDFDocument } from 'pdf-lib' 2import fontkit from '@pdf-lib/fontkit' 3 4const pdfDoc = await PDFDocument.create() 5pdfDoc.registerFontkit(fontkit)
The following builds are available:
NOTE: if you are using the CDN scripts in production, you should include a specific version number in the URL, for example:
When using a UMD build, you will have access to a global window.fontkit
variable. To register the fontkit
instance:
1var pdfDoc = await PDFLib.PDFDocument.create()
2pdfDoc.registerFontkit(fontkit)
API documentation is available on the project site at https://pdf-lib.js.org/docs/api/.
The repo for the project site (and generated documentation files) is located here: https://github.com/Hopding/pdf-lib-docs.
When working with PDFs, you will frequently come across the terms "character encoding" and "font". If you have experience in web development, you may wonder why these are so prevalent. Aren't they just annoying details that you shouldn't need to worry about? Shouldn't PDF libraries and readers be able to handle all of this for you like web browsers can? Unfortunately, this is not the case. The nature of the PDF file format makes it very difficult to avoid thinking about character encodings and fonts when working with PDFs.
pdf-lib
does its best to simplify things for you. But it can't perform magic. This means you should be aware of the following:
1import { PDFDocument, StandardFonts } from 'pdf-lib'
2const pdfDoc = await PDFDocument.create()
3const courierFont = await pdfDoc.embedFont(StandardFonts.Courier)
4const page = pdfDoc.addPage()
5page.drawText('Some boring latin text in the Courier font', {
6 font: courierFont,
7})
embedFont
method. When you embed your own font, you can use any Unicode characters that it supports. This capability frees you from the limitations imposed by the standard fonts. Most PDF files use embedded fonts. You can embed and use a custom font like so (see also):
1import { PDFDocument } from 'pdf-lib' 2import fontkit from '@pdf-lib/fontkit' 3 4const url = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf' 5const fontBytes = await fetch(url).then((res) => res.arrayBuffer()) 6 7const pdfDoc = await PDFDocument.create() 8 9pdfDoc.registerFontkit(fontkit) 10const ubuntuFont = await pdfDoc.embedFont(fontBytes) 11 12const page = pdfDoc.addPage() 13page.drawText('Some fancy Unicode text in the ŪЬȕǹƚü font', { 14 font: ubuntuFont, 15})
Note that encoding errors will be thrown if you try to use a character with a font that does not support it. For example, Ω
is not in the WinAnsi character set. So trying to draw it on a page with the standard Helvetica font will throw the following error:
Error: WinAnsi cannot encode "Ω" (0x03a9)
at Encoding.encodeUnicodeCodePoint
Embedding a font in a PDF document will typically increase the file's size. You can reduce the amount a file's size is increased by subsetting the font so that only the necessary characters are embedded. You can subset a font by setting the subset
option to true
. For example:
1const font = await pdfDoc.embedFont(fontBytes, { subset: true });
Note that subsetting does not work for all fonts. See https://github.com/Hopding/pdf-lib/issues/207#issuecomment-537210471 for additional details.
pdf-lib
can create, fill, and read PDF form fields. The following field types are supported:
See the form creation and form filling usage examples for code samples. Tests 1, 14, 15, 16, and 17 in the complete examples contain working example code for form creation and filling in a variety of different JS environments.
IMPORTANT: The default font used to display text in buttons, dropdowns, option lists, and text fields is the standard Helvetica font. This font only supports characters in the latin alphabet (see Fonts and Unicode for details). This means that if any of these field types are created or modified to contain text outside the latin alphabet (as is often the case), you will need to embed and use a custom font to update the field appearances. Otherwise an error will be thrown (likely when you save the PDFDocument
).
You can use an embedded font when filling form fields as follows:
1import { PDFDocument } from 'pdf-lib'; 2import fontkit from '@pdf-lib/fontkit'; 3 4// Fetch the PDF with form fields 5const formUrl = 'https://pdf-lib.js.org/assets/dod_character.pdf'; 6const formBytes = await fetch(formUrl).then((res) => res.arrayBuffer()); 7 8// Fetch the Ubuntu font 9const fontUrl = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf'; 10const fontBytes = await fetch(fontUrl).then((res) => res.arrayBuffer()); 11 12// Load the PDF with form fields 13const pdfDoc = await PDFDocument.load(formBytes); 14 15// Embed the Ubuntu font 16pdfDoc.registerFontkit(fontkit); 17const ubuntuFont = await pdfDoc.embedFont(fontBytes); 18 19// Get two text fields from the form 20const form = pdfDoc.getForm(); 21const nameField = form.getTextField('CharacterName 2'); 22const ageField = form.getTextField('Age'); 23 24// Fill the text fields with some fancy Unicode characters (outside 25// the WinAnsi latin character set) 26nameField.setText('Ӎӑȑїõ'); 27ageField.setText('24 ŷȇȁŗš'); 28 29// **Key Step:** Update the field appearances with the Ubuntu font 30form.updateFieldAppearances(ubuntuFont); 31 32// Save the PDF with filled form fields 33const pdfBytes = await pdfDoc.save();
Existing form fields can be accessed with the following methods of PDFForm
:
PDFForm.getButton
PDFForm.getCheckBox
PDFForm.getDropdown
PDFForm.getOptionList
PDFForm.getRadioGroup
PDFForm.getTextField
New form fields can be created with the following methods of PDFForm
:
PDFForm.createButton
PDFForm.createCheckBox
PDFForm.createDropdown
PDFForm.createOptionList
PDFForm.createRadioGroup
PDFForm.createTextField
Below are some of the most commonly used methods for reading and filling the aforementioned subclasses of PDFField
:
PDFDropdown.select
PDFDropdown.clear
PDFDropdown.getSelected
PDFDropdown.getOptions
PDFDropdown.addOptions
PDFOptionList.select
PDFOptionList.clear
PDFOptionList.getSelected
PDFOptionList.getOptions
PDFOptionList.addOptions
PDFRadioGroup.select
PDFRadioGroup.clear
PDFRadioGroup.getSelected
PDFRadioGroup.getOptions
PDFRadioGroup.addOptionToPage
PDFTextField.setText
PDFTextField.getText
PDFTextField.setMaxLength
PDFTextField.getMaxLength
PDFTextField.removeMaxLength
pdf-lib
can extract the content of text fields (see PDFTextField.getText
), but it cannot extract plain text on a page outside of a form field. This is a difficult feature to implement, but it is within the scope of this library and may be added to pdf-lib
in the future. See
#93,
#137,
#177,
#329, and
#380.pdf-lib
can remove and edit the content of text fields (see PDFTextField.setText
), but it does not provide APIs for removing or editing text on a page outside of a form field. This is also a difficult feature to implement, but is within the scope of pdf-lib
and may be added in the future. See
#93,
#137,
#177,
#329, and
#380.pdf-lib
does not support the use of HTML or CSS when adding content to a PDF. Similarly, pdf-lib
cannot embed HTML/CSS content into PDFs. As convenient as such a feature might be, it would be extremely difficult to implement and is far beyond the scope of this library. If this capability is something you need, consider using Puppeteer.Discussions is the best place to chat with us, ask questions, and learn more about pdf-lib!
See also MAINTAINERSHIP.md#communication and MAINTAINERSHIP.md#discord.
pdf-lib
does support encrypted documents.
To load a document, use this:
1// Load a random document you know nothing about: 2const doc = PDFDocument.load(content, { ignoreEncryption: true }) 3// Check if the document is encrypted: 4const isEncrypted = doc.isEncrypted 5// If isEncrypted is true, you know the you need to ask the user for the password.
If you know the password of the document, or if it was provided by the user, you can now open the document with it:
1// Load an encrypted document with its password: 2const password = "The password" 3const doc = PDFDocument.load(content, { ignoreEncryption: true, password })
We welcome contributions from the open source community! If you are interested in contributing to pdf-lib
, please take a look at the CONTRIBUTING.md file. It contains information to help you get pdf-lib
setup and running on your machine. (We try to make this as simple and fast as possible! :rocket:)
Check out MAINTAINERSHIP.md for details on how this repo is maintained and how we use issues, PRs, and discussions.
pdfkit
is a PDF generation library for Node and the Browser. This library was immensely helpful as a reference and existence proof when creating pdf-lib
. pdfkit
's code for font embedding, PNG embedding, and JPG embedding was especially useful.pdf.js
is a PDF rendering library for the Browser. This library was helpful as a reference when writing pdf-lib
's parser. Some of the code for stream decoding was ported directly to TypeScript for use in pdf-lib
.pdfbox
is a PDF generation and modification library written in Java. This library was an invaluable reference when implementing form creation and filling APIs for pdf-lib
.jspdf
is a PDF generation library for the browser.pdfmake
is a PDF generation library for the browser.hummus
is a PDF generation and modification library for Node environments. hummus
is a Node wrapper around a C++ library, so it doesn't work in many JavaScript environments - like the Browser or React Native.react-native-pdf-lib
is a PDF generation and modification library for React Native environments. react-native-pdf-lib
is a wrapper around C++ and Java libraries.pdfassembler
is a PDF generation and modification library for Node and the browser. It requires some knowledge about the logical structure of PDF documents to use.This repo used to contain a file called pdf_specification.pdf
in the root directory. This was a copy of the PDF 1.7 specification, which is made freely available by Adobe. On 8/30/2021, we received a DMCA complaint requiring us to remove the file from this repo. Simply removing the file via a new commit to master
was insufficient to satisfy the complaint. The file needed to be completely removed from the repo's git history. Unfortunately, the file was added over two years ago, this meant we had to rewrite the repo's git history and force push to master
😔.
We removed the file and rewrote the repo's history using BFG Repo-Cleaner as outlined here. For full transparency, here are the exact commands we ran:
$ git clone git@github.com:Hopding/pdf-lib.git
$ cd pdf-lib
$ rm pdf_specification.pdf
$ git commit -am 'Remove pdf_specification.pdf'
$ bfg --delete-files pdf_specification.pdf
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
$ git push --force
If you're a user of pdf-lib
, you shouldn't care! Just keep on using pdf-lib
like normal 😃 ✨!
If you are a pdf-lib
developer (meaning you've forked pdf-lib
and/or have an open PR) then this does impact you. If you forked or cloned the repo prior to 8/30/2021 then your fork's git history is out of sync with this repo's master
branch. Unfortunately, this will likely be a headache for you to deal with. Sorry! We didn't want to rewrite the history, but there really was no alternative.
It's important to note that pdf-lib's source code has not changed at all. It's exactly the same as it was before the git history rewrite. The repo still has the exact same number of commits (and even the same commit contents, except for the commit that added pdf_specification.pdf
). What has changed are the SHAs of those commits.
The simplest way to deal with this fact is to:
See this StackOverflow answer for a great, in depth explanation of what a git history rewrite entails.
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
binaries present in source code
Details
Reason
0 commit(s) and 9 issue activity found in the last 90 days -- score normalized to 7
Reason
Found 4/29 approved changesets -- score normalized to 1
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
56 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More