Lens Protocol Metadata Standards
Schema validation and TS types for LIP-2 Lens Protocol Metadata Standards.
Features
- Zod schema definitions
- JSON Schema definitions
- TypeScript type definitions
Installation
npm install @lens-protocol/metadata zod
yarn add @lens-protocol/metadata zod
pnpm add @lens-protocol/metadata zod
[!NOTE]
zod is marked as optional peer dependency, so if you all you need is the JSON Schema definitions, you can install @lens-protocol/metadata without zod.
Documentation
See https://lens-protocol.github.io/metadata/.
Compose
Post metadata
You can create compliant PostMetadata objects via the following builder functions:
import {
article,
audio,
checkingIn,
embed,
event,
image,
link,
livestream,
mint,
space,
story,
textOnly,
threeD,
transaction,
video,
} from '@lens-protocol/metadata';
const json = article({
content: 'The content of the article',
});
[!NOTE]
Use the type definitions to explore the available properties and their types. The builders will throw a ValidationError with instructions on how to fix the error if the object is not compliant with the schema.
We also provided a set of builder function for specific metadata sub-types (list to be expanded):
import { geoUri } from '@lens-protocol/metadata';
const uri = geoUri({
lat: 51.5074,
lng: 0.1278,
});
Account metadata
You can create compliant AccountMetadata objects via the following builder function:
import { account } from '@lens-protocol/metadata';
const json = account({
name: 'Bob',
bio: 'I am a Lens user',
});
[!NOTE]
Use the type definitions to explore the available properties and their types. The builder will throw a ValidationError with instructions on how to fix the error if the object is not compliant with the schema.
Parse
Assuming we have 2 JS objects:
const valid = {
};
const invalid = {
};
Post metadata
Post metadata schema is a union of all content schemas (e.g. ArticleMetadata, AudioMetadata, etc.
Use it to parse the metadata referenced by contentURI of Lens Post.
import { PostMetadataSchema } from '@lens-protocol/metadata';
PostMetadataSchema.parse(valid);
PostMetadataSchema.parse(invalid);
PostMetadataSchema.safeParse(valid);
PostMetadataSchema.safeParse(invalid);
Account metadata
import { AccountMetadataSchema } from '@lens-protocol/metadata';
AccountMetadataSchema.parse(valid);
AccountMetadataSchema.parse(invalid);
AccountMetadataSchema.safeParse(valid);
AccountMetadataSchema.safeParse(invalid);
A convenience extractVersion function is available to extract the version from a parsed PublicationMetadata or ProfileMetadata.
import { extractVersion, PostMetadataSchema, AccountMetadataSchema } from '@lens-protocol/metadata';
const postMetadata = PostMetadataSchema.parse(valid);
extractVersion(postMetadata);
const accountMetadata = AccountMetadataSchema.parse(valid);
extractVersion(accountMetadata);
Format validation error
ZodError contains all the information needed to inform you about the validation error, but it's not very user friendly. You can use formatZodError to get a more readable error message.
import { PostMetadataSchema, formatZodError } from '@lens-protocol/metadata';
const result = PostMetadataSchema.safeParse(invalid);
if (!result.success) {
console.log(formatZodError(result.error));
}
Types
Narrowing types
Every time you have a discriminated union, you can use the discriminant to narrow the type. See few examples below.
PostMetadata
import { PostMetadata, PostMetadataSchema, PostSchemaId } from '@lens-protocol/metadata';
const metadata = PostMetadataSchema.parse(valid);
switch (metadata.$schema) {
case PostSchemaId.ARTICLE_LATEST:
break;
case PostSchemaId.AUDIO_LATEST:
break;
case PostSchemaId.IMAGE_LATEST:
break;
case PostSchemaId.TEXT_ONLY_LATEST:
break;
}
AccessCondition
import { AccessCondition, ConditionType, PostMetadataSchema } from '@lens-protocol/metadata';
const metadata = PostMetadataSchema.parse(valid);
switch (metadata.encryptedWith?.accessCondition.type) {
case ConditionType.AND:
break;
case ConditionType.OR:
break;
case ConditionType.NFT_OWNERSHIP:
break;
case ConditionType.EOA_OWNERSHIP:
break;
}
MetadataAttribute
import { MetadataAttribute, MetadataAttributeType } from '@lens-protocol/metadata';
switch (attribute.type) {
case MetadataAttributeType.BOOLEAN:
break;
case MetadataAttributeType.DATE:
break;
case MetadataAttributeType.NUMBER:
break;
case MetadataAttributeType.STRING:
break;
case MetadataAttributeType.JSON:
break;
}
Other useful types
The package also exports all enums and types that you might need to work with the metadata.
Use your IDE's autocomplete to explore the available types.
Some examples:
import {
MediaAudioKind,
MediaAudioMimeType,
MediaImageMimeType,
MediaVideoMimeType,
MetadataAttributeType,
PostMainFocus,
ThreeDFormat,
ArticleMetadata,
AudioMetadata,
CheckingInMetadata,
EmbedMetadata,
EventMetadata,
ImageMetadata,
LinkMetadata,
LivestreamMetadata,
MintMetadata,
ProfileMetadata,
PublicationMetadata,
SpaceMetadata,
StoryMetadata,
TextOnlyMetadata,
ThreeDMetadata,
TransactionMetadata,
VideoMetadata,
MetadataAttribute,
MediaAudio,
MediaImage,
MediaVideo,
AnyMedia,
GeoLocation,
BooleanAttribute,
DateAttribute,
NumberAttribute,
StringAttribute,
JSONAttribute,
Locale,
Markdown,
Signature,
URI,
AppId,
Datetime,
} from '@lens-protocol/metadata';
JSON schemas
Importing JSON schema in TypeScript is a simple as:
import audio from '@lens-protocol/metadata/jsonschemas/post/audio/3.0.0.json' assert { type: 'json' };
import audio from '@lens-protocol/metadata/jsonschemas/post/article/3.0.0.json' assert { type: 'json' };
import mirror from '@lens-protocol/metadata/jsonschemas/post/mirror/1.0.0.json' assert { type: 'json' };
import profile from '@lens-protocol/metadata/jsonschemas/account/1.0.0.json' assert { type: 'json' };
You can the use them in your JSON Schema validator of choice, for example ajv.
Versioning
The Lens Protocol Metadata Standards use a self-describing JSON format. All metadata files that adopt this standard MUST have a $schema property that identifies the schema the file conforms to.
{
"$schema": "https://json-schemas.lens.dev/post/article/3.0.0.json",
"lens": {
"id": "b3d7f1a0-1f75-11ec-9621-0242ac130002",
"content": "The content of the article",
"locale": "en"
}
}
The $schema property is a URI that identify the schema type and its version.
Schemas are versioned using Semantic Versioning.
[!NOTE]
Even though schemas are identified by URIs, those identifiers are not necessarily network-addressable. They are just identifiers.
Generally, JSON schema validators don’t make HTTP requests (https://) to fetch schemas. Instead, they provide a way to load schemas into an internal schema database. When a schema is referenced by its URI identifier, the schema is retrieved from the internal schema database.
Future changes should aim to be backwards compatible as much as possible.
When adding a new version of a schema, the previous version should be kept for a reasonable amount of time to allow consumers to migrate and to support the new specification.
Adding a new schema
In this example we will add a new version of the AudioSchema schema, but the same process applies to all the other schemas.
- create a new
PostSchemaId enum entry with value of PostSchemaId.AUDIO_LATEST. Name it after the current schema version (e.g. AUDIO_V1_0_0).
- rename the existing
AudioSchema into AudioV1_0_0Schema and update the $schema value to PostSchemaId.AUDIO_V1_0_0
- increase the version number of the
PostSchemaId.AUDIO_LATEST based on the nature of your changes. Remember to follow semver rules.
- create a new
AudioSchema with the new schema definition and use the PostSchemaId.AUDIO_LATEST as $schema value
- update the
scripts/build.ts script to include the new schema and old schema files under the correct version file name in the jsonschemas/post/audio folder
- release a new version of this package according to the nature of the changes (new major version of a schema = new major version of the package, etc.)
In case the changes are backwards compatible, you could create a single AudioMetadataDetailsSchema definition and just declare 2 schemas out of it, one for the old version and one for the new version. For example:
export const AudioMetadataDetailsSchema = metadataDetailsWith({
mainContentFocus: mainContentFocus(PostMainFocus.AUDIO),
audio: MediaAudioSchema,
attachments: AnyMediaSchema.array()
.min(1)
.optional()
.describe('The other attachments you want to include with it.'),
});
export type AudioMetadataDetails = z.infer<typeof AudioMetadataDetailsSchema>;
export const AudioSchema = postWith({
$schema: z.literal(PostSchemaId.AUDIO_LATEST),
lens: AudioMetadataDetailsSchema,
});
export type AudioMetadata = z.infer<typeof AudioSchema>;
export const AudioV1Schema = postWith({
$schema: z.literal(PostSchemaId.AUDIO_V1_0_0),
lens: AudioMetadataDetailsSchema,
});
export type AudioV1Metadata = z.infer<typeof AudioV1Schema>;
In this case consumers of this package can take advantage of the structural likeness and just do the following:
switch (metadata.$schema) {
case PostSchemaId.AUDIO_V1_0_0:
case PostSchemaId.AUDIO_LATEST:
break;
}
Contributing
To contribute to the Lens Protocol Metadata Standards, please fork this repository and submit a pull request with your changes.
To run the unit tests, run:
pnpm test
Pro-tip: you can run pnpm test --watch to run the tests in watch mode.
To build the project, run:
pnpm build
Generate and include up to date documentation with:
pnpm typedoc:docs
Add changeset with:
pnpm changeset add
Use keepachangelog format for the changeset message.
Releasing
Release flow is managed by changesets.
To release a new version follow the steps below:
- Create a new branch from
main with the name release/<version>
- Build the project
pnpm install && pnpm build && pnpm typedoc:docs
- Update relevant
package.json's versions and update CHANGELOG.md with:
pnpm changeset version
- Review, commit and push the changes
- Create a PR from
release/<version> to main
- Once approved, publish with (you need to be logged in to npm authorized to publish under
@lens-protocol):
pnpm changeset publish
git push origin release/<version> --follow-tags
- Merge the PR with a merge commit
License
Lens Protocol Metadata Standards is MIT licensed
Support
See the Lens API and SDK channel on our Discord