A modern, type-safe TypeScript library for creating and parsing NDEF (NFC Data Exchange Format) messages with full compile-time type checking and intelligent autocompletion.
- β Type-safe records (URI, JSON, text, mediaβ¦)
- π Zero deps, tree-shakable ESM
- π§ͺ Well-tested parsing/serialization with robust errors
- π Type-Safe: Full TypeScript support with compile-time type checking
- π Modern: Built with modern TypeScript features and ES modules
- π¦ Lightweight: No dependencies and tree-shakable
- π§ Comprehensive: Support for most common NDEF record types
- π― Intelligent: Smart type inference and autocompletion
- π‘οΈ Robust: Comprehensive error handling and validation
- β‘ Fast: Optimized parsing and serialization
- π§ͺ Well-Tested: Extensive test coverage
Unlike other NDEF libraries, tsndef provides compile-time type checking for all NDEF operations. Know exactly what record types you're working with before runtime.
- Built with modern TypeScript (ES2022+)
- Tree-shakable - only bundle what you use
- Zero dependencies for core functionality
- Immutable-style API for better developer experience
The library tracks record types at compile time, providing intelligent autocompletion and preventing runtime errors:
const message = new NDEFMessage()
.add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))
.add(createNDEFRecordMediaApplicationJson({ payload: { hello: 'world' } }))
// TypeScript automatically knows the exact types
const uriRecord = message.records[0] // Type: NDEFRecordWellKnownURI
const jsonRecord = message.records[1] // Type: NDEFRecordMediaApplicationJson# npm
npm install tsndef
# yarn
yarn add tsndef
# pnpm
pnpm add tsndefimport { createNDEFRecordWellKnownURI, NDEFMessage } from 'tsndef'
// Create a simple URI record
const message = new NDEFMessage()
.add(createNDEFRecordWellKnownURI({
payload: 'https://example.com'
}))
// Convert to bytes for NFC writing
const bytes = await message.toBytes()import { createNDEFRecordMediaApplicationJson, NDEFMessage } from 'tsndef'
const jsonMessage = new NDEFMessage()
.add(createNDEFRecordMediaApplicationJson({
payload: {
productId: 12345,
name: 'Awesome Product',
price: 29.99,
inStock: true
}
}))import { createNDEFRecordMediaTextPlain, NDEFMessage } from 'tsndef'
const textMessage = new NDEFMessage()
.add(createNDEFRecordMediaTextPlain({
payload: 'Hello, NFC World! π'
}))import {
createNDEFRecordMediaApplicationJson,
createNDEFRecordMediaTextPlain,
createNDEFRecordWellKnownURI,
NDEFMessage
} from 'tsndef'
const complexMessage = new NDEFMessage()
.add(createNDEFRecordWellKnownURI({
payload: 'https://myapp.com/product/123'
}))
.add(createNDEFRecordMediaApplicationJson({
payload: {
action: 'view_product',
productId: 123,
timestamp: Date.now()
}
}))
.add(createNDEFRecordMediaTextPlain({
payload: 'Scan this tag to view product details'
}))
// Convert to bytes for NFC tag writing
const nfcBytes = await complexMessage.toBytes()import { parseNDEFMessage } from 'tsndef'
// Parse bytes received from NFC tag
const nfcBytes = new Uint8Array([/* raw bytes from NFC tag */])
const message = parseNDEFMessage(nfcBytes)
console.log(`Found ${message.length} records`)import { safeParseNDEFMessage } from 'tsndef'
const result = safeParseNDEFMessage(nfcBytes)
if (result.success) {
console.log('Parsed successfully:', result.message)
}
else {
console.error('Parsing failed:', result.error)
}const message = parseNDEFMessage(nfcBytes)
for (const record of message.records) {
switch (record.tnf) {
case 'well-known':
if (record.type === 'U') {
console.log('Found URI:', await record.payload()) // Full URI string
}
break
case 'media':
if (record.type === 'application/json') {
const data = await record.payload() // Parsed JSON object
console.log('JSON data:', data)
}
else if (record.type === 'text/plain') {
console.log('Text content:', await record.payload())
}
break
default:
console.log('Unknown record type:', record.type)
console.log('Raw payload:', await record.rawPayload())
}
}- URI Records:
createNDEFRecordWellKnownURI()- Supports all standard URI prefixes (http, https, tel, mailto, etc.)
- Automatic prefix optimization for smaller tag sizes
- JSON:
createNDEFRecordMediaApplicationJson() - Plain Text:
createNDEFRecordMediaTextPlain() - HTML:
createNDEFRecordMediaTextHTML() - Images:
createNDEFRecordMediaImagePNG(),createNDEFRecordMediaImageJPEG() - Video:
createNDEFRecordMediaVideoMP4() - Audio:
createNDEFRecordMediaAudioMPEG()
When working with tsndef, it's important to understand how TypeScript's type inference works with our immutable-style API. Adding or removing records after initial variable assignment may interfere with correct type inference.
// Initial assignment with inferred type
const message = new NDEFMessage()
.add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))
// TypeScript infers: NDEFMessage<[NDEFRecordWellKnownURI<https://example.com>]>
// Later modifications lose precise type information
message.remove()
// TypeScript now still sees: NDEFMessage<[NDEFRecordWellKnownURI<https://example.com>]> - lost precise typing!
// Type information is no longer accurate
const firstPayload = await message.records[0].payload() // Valid access to typescript, but results in runtime error as the record was removedOption 1: Build the complete message in one chain
const message = new NDEFMessage()
.add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))
.add(createNDEFRecordMediaApplicationJson({ payload: { id: 123 } }))
.add(createNDEFRecordMediaTextPlain({ payload: 'Description' }))
// TypeScript maintains precise type: NDEFMessage<[NDEFRecordWellKnownURI, NDEFRecordMediaApplicationJson, NDEFRecordMediaTextPlain]>
const uriRecord = message.records[0] // Type: NDEFRecordWellKnownURI β
const jsonRecord = message.records[1] // Type: NDEFRecordMediaApplicationJson βOption 2: Reassign after modifications
// Create new instances to maintain type safety
let message = new NDEFMessage()
.add(createNDEFRecordWellKnownURI({ payload: 'https://example.com' }))
message = message
.add(createNDEFRecordMediaApplicationJson({ payload: { id: 123 } }))Maintaining precise type information allows you to:
- Get accurate autocompletion when accessing record properties
- Catch type errors at compile time
- Leverage TypeScript's powerful type system for safer NFC operations
- Ensure your code is more maintainable and less prone to runtime errors
tsndef supports the most common NDEF record types:
- Well-Known: URI records with automatic prefix optimization
- Media: JSON, plain text, HTML, images (PNG/JPEG), video (MP4), audio (MPEG)
All record types include full TypeScript type definitions for compile-time safety.
Yes! Use safeParseNDEFMessage() for error-safe parsing that won't throw exceptions. Unknown record types are preserved with their raw payload accessible via rawPayload(), so you never lose data.
tsndef is the only NDEF library that provides:
- Full compile-time type safety with TypeScript
- Intelligent type inference that tracks exact record types
- Zero dependencies and tree-shakable architecture
- Modern ES modules with immutable-style API
- Comprehensive error handling with both throwing and safe parsing options
# Run tests
pnpm test
# Run tests in watch mode
pnpm test --watch
# Run linting
pnpm lint
# Fix linting issues
pnpm lint:fixContributions are welcome! Please feel free to submit a Pull Request.
MIT License - see the LICENSE file for details.