The NFT Catalog is an on chain registry listing NFT collections that exists on Flow which adhere to the NFT metadata standard. This empowers dApp developers to easily build on top of and discover interoperable NFT collections on Flow.
Checkout the catalog site to submit your NFT collection both on testnet and mainnet.
NFTCatalog.cdc
: This contract contains the NFT Catalog
Network | Address |
---|---|
Mainnet | 0x49a7cda3a1eecc29 |
Testnet | 0x324c34e1c517e4db |
NFTRetrieval.cdc
: This contract contains helper functions to make it easier to discover NFTs within accounts and from the catalog
Network | Address |
---|---|
Mainnet | 0x49a7cda3a1eecc29 |
Testnet | 0x324c34e1c517e4db |
- Visit here
- Enter the address containing the NFT contract which contains the collection and select the contract.

-
Enter the storage path where the NFTs are stored and enter an address that holds a sample NFT or log in if you have access to an account that owns the NFT.
-
The application will verify that your NFT collection implements the required Metadata views.
- The required metadata views include…
- NFT Display
- How to display an individual NFT part of the collection
- External URL
- A website for the NFT collection
- Collection Data
- Information needed to store and retrieve an NFT
- Collection Display
- How to display information about the NFT collection the NFT belongs to
- Royalties
- Any royalties that should be accounted for during marketplace transactions
- NFT Display
- You can find sample implementations of all these views in this example NFT contract.
- If you are not implementing a view, the app will communicate this and you can update your NFT contract and try resubmitting.
- The required metadata views include…
-
Submit proposal transaction to the NFT catalog by entering a unique url safe identifier for the collection and a message including any additional context (like contact information).

- Once submitted you can view all proposals here to track the review of your NFT.
If you would like to make a proposal manually, you may submit the following transaction with all parameters filled in: https://github.com/dapperlabs/nft-catalog/blob/main/cadence/transactions/propose_nft_to_catalog.cdc
Proposals should be reviewed and approved within a few days. Reasons for a proposal being rejected may include:
- Providing duplicate path or name information of an existing collection on the catalog
- Providing a not url safe or inaccurate name as the identifier
All of the below examples use the catalog in mainnet, you may replace the imports to the testnet address when using the testnet network.
Example 1 - Retrieve all NFT collections on the catalog
1import NFTCatalog from 0x49a7cda3a1eecc2923/*4The catalog is returned as a `String: NFTCatalogMetadata`5The key string is intended to be a unique identifier for a specific collection.6The NFTCatalogMetadata contains collection-level views corresponding to each7collection identifier.8*/9pub fun main(): {String : NFTCatalog.NFTCatalogMetadata} {10return NFTCatalog.getCatalog()1112}
Example 2 - Retrieve all collection names in the catalog
1import NFTCatalog from 0x49a7cda3a1eecc2923pub fun main(): [String] {4let catalog: {String : NFTCatalog.NFTCatalogMetadata} = NFTCatalog.getCatalog()5let catalogNames: [String] = []6for collectionIdentifier in catalog.keys {7catalogNames.append(catalog[collectionIdentifier]!.collectionDisplay.name)8}9return catalogNames10}
Example 3 - Retrieve NFT collections and counts owned by an account
1import MetadataViews from 0x1d7e57aa558174482import NFTCatalog from 0x49a7cda3a1eecc293import NFTRetrieval from 0x49a7cda3a1eecc2945pub fun main(ownerAddress: Address) : {String : Number} {6let catalog = NFTCatalog.getCatalog()7let account = getAuthAccount(ownerAddress)8let items : {String : Number} = {}910for key in catalog.keys {11let value = catalog[key]!12let tempPathStr = "catalog".concat(key)13let tempPublicPath = PublicPath(identifier: tempPathStr)!14account.link<&{MetadataViews.ResolverCollection}>(15tempPublicPath,16target: value.collectionData.storagePath17)18let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)19if !collectionCap.check() {20continue21}22let count = NFTRetrieval.getNFTCountFromCap(collectionIdentifier : key, collectionCap : collectionCap)23if count != 0 {24items[key] = count25}26}2728return items29}
Sample Response...
1{2"schmoes_prelaunch_token": 13}
Example 4 - Retrieve all NFTs including metadata owned by an account
1import MetadataViews from 0x1d7e57aa558174482import NFTCatalog from 0x49a7cda3a1eecc293import NFTRetrieval from 0x49a7cda3a1eecc2945pub struct NFT {6pub let id : UInt647pub let name : String8pub let description : String9pub let thumbnail : String10pub let externalURL : String11pub let storagePath : StoragePath12pub let publicPath : PublicPath13pub let privatePath: PrivatePath14pub let publicLinkedType: Type15pub let privateLinkedType: Type16pub let collectionName : String17pub let collectionDescription: String18pub let collectionSquareImage : String19pub let collectionBannerImage : String20pub let royalties: [MetadataViews.Royalty]2122init(23id: UInt64,24name : String,25description : String,26thumbnail : String,27externalURL : String,28storagePath : StoragePath,29publicPath : PublicPath,30privatePath : PrivatePath,31publicLinkedType : Type,32privateLinkedType : Type,33collectionIdentifier: String,34collectionName : String,35collectionDescription : String,36collectionSquareImage : String,37collectionBannerImage : String,38royalties : [MetadataViews.Royalty]39) {40self.id = id41self.name = name42self.description = description43self.thumbnail = thumbnail44self.externalURL = externalURL45self.storagePath = storagePath46self.publicPath = publicPath47self.privatePath = privatePath48self.publicLinkedType = publicLinkedType49self.privateLinkedType = privateLinkedType50self.collectionIdentifier = collectionIdentifier51self.collectionName = collectionName52self.collectionDescription = collectionDescription53self.collectionSquareImage = collectionSquareImage54self.collectionBannerImage = collectionBannerImage55self.royalties = royalties56}57}5859pub fun main(ownerAddress: Address) : { String : [NFT] } {60let catalog = NFTCatalog.getCatalog()61let account = getAuthAccount(ownerAddress)62let items : [NFTRetrieval.BaseNFTViewsV1] = []6364let data : {String : [NFT] } = {}6566for key in catalog.keys {67let value = catalog[key]!68let tempPathStr = "catalog".concat(key)69let tempPublicPath = PublicPath(identifier: tempPathStr)!70account.link<&{MetadataViews.ResolverCollection}>(71tempPublicPath,72target: value.collectionData.storagePath73)74let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)75if !collectionCap.check() {76continue77}78let views = NFTRetrieval.getNFTViewsFromCap(collectionIdentifier : key, collectionCap : collectionCap)7980let items : [NFT] = []81for view in views {82let displayView = view.display83let externalURLView = view.externalURL84let collectionDataView = view.collectionData85let collectionDisplayView = view.collectionDisplay86let royaltyView = view.royalties87if (displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil) {88// This NFT does not have the proper views implemented. Skipping....89continue90}9192items.append(93NFT(94id: view.id,95name : displayView!.name,96description : displayView!.description,97thumbnail : displayView!.thumbnail.uri(),98externalURL : externalURLView!.url,99storagePath : collectionDataView!.storagePath,100publicPath : collectionDataView!.publicPath,101privatePath : collectionDataView!.providerPath,102publicLinkedType : collectionDataView!.publicLinkedType,103privateLinkedType : collectionDataView!.providerLinkedType,104collectionIdentifier: key,105collectionName : collectionDisplayView!.name,106collectionDescription : collectionDisplayView!.description,107collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),108collectionBannerImage : collectionDisplayView!.bannerImage.file.uri(),109royalties : royaltyView!.getRoyalties()110)111)112}113data[key] = items114}115return data116}
Sample Response...
1{2"FlovatarComponent": [],3"schmoes_prelaunch_token": [4s.aa16be98aac20e8073f923261531cbbdfae1464f570f5be796b57cdc97656248.NFT(5id: 1006,6name: "Schmoes Pre Launch Token #1006",7description: "",8thumbnail: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",9externalURL: "https://schmoes.io",10storagePath: /storage/SchmoesPreLaunchTokenCollection,11publicPath: /public/SchmoesPreLaunchTokenCollection,12privatePath: /private/SchmoesPreLaunchTokenCollection,13publicLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A. 1d7e57aa55817448.NonFungibleToken.Receiver,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),14privateLinkedType: Type<&A.6c4fe48768523577.SchmoesPreLaunchToken.Collection{A.1d7e57aa55817448.NonFungibleToken.CollectionPublic,A.1d7e57aa55817448.NonFungibleToken.Provider,A.1d7e57aa55817448.MetadataViews.ResolverCollection}>(),15collectionName: "Schmoes Pre Launch Token",16collectionDescription: "",17collectionSquareImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",18collectionBannerImage: "https://gateway.pinata.cloud/ipfs/QmXQ1iBke5wjcjYG22ACVXsCvtMJKEkwFiMf96UChP8uJq",19royalties: []20)21],22"Flovatar": []23}
Example 5 - Retrieve all NFTs including metadata owned by an account for large wallets
For Wallets that have a lot of NFTs you may run into memory issues. The common pattern to get around this for now is to retrieve just the ID's in a wallet by calling the following script
1import MetadataViews from 0x1d7e57aa558174482import NFTCatalog from 0x49a7cda3a1eecc293import NFTRetrieval from 0x49a7cda3a1eecc2945pub fun main(ownerAddress: Address) : {String : [UInt64]} {6let catalog = NFTCatalog.getCatalog()7let account = getAuthAccount(ownerAddress)89let items : {String : [UInt64]} = {}1011for key in catalog.keys {12let value = catalog[key]!13let tempPathStr = "catalogIDs".concat(key)14let tempPublicPath = PublicPath(identifier: tempPathStr)!15account.link<&{MetadataViews.ResolverCollection}>(16tempPublicPath,17target: value.collectionData.storagePath18)1920let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)21if !collectionCap.check() {22continue23}2425let ids = NFTRetrieval.getNFTIDsFromCap(collectionIdentifier : key, collectionCap : collectionCap)2627if ids.length > 0 {28items[key] = ids29}30}31return items3233}
and then use the ids to retrieve the full metadata for only those ids by calling the following script and passing in a map of collectlionIdentifer -> [ids]
1import MetadataViews from 0x1d7e57aa558174482import NFTCatalog from 0x49a7cda3a1eecc293import NFTRetrieval from 0x49a7cda3a1eecc2945pub struct NFT {6pub let id : UInt647pub let name : String8pub let description : String9pub let thumbnail : String10pub let externalURL : String11pub let storagePath : StoragePath12pub let publicPath : PublicPath13pub let privatePath: PrivatePath14pub let publicLinkedType: Type15pub let privateLinkedType: Type16pub let collectionName : String17pub let collectionDescription: String18pub let collectionSquareImage : String19pub let collectionBannerImage : String20pub let royalties: [MetadataViews.Royalty]2122init(23id: UInt64,24name : String,25description : String,26thumbnail : String,27externalURL : String,28storagePath : StoragePath,29publicPath : PublicPath,30privatePath : PrivatePath,31publicLinkedType : Type,32privateLinkedType : Type,33collectionName : String,34collectionDescription : String,35collectionSquareImage : String,36collectionBannerImage : String,37royalties : [MetadataViews.Royalty]38) {39self.id = id40self.name = name41self.description = description42self.thumbnail = thumbnail43self.externalURL = externalURL44self.storagePath = storagePath45self.publicPath = publicPath46self.privatePath = privatePath47self.publicLinkedType = publicLinkedType48self.privateLinkedType = privateLinkedType49self.collectionName = collectionName50self.collectionDescription = collectionDescription51self.collectionSquareImage = collectionSquareImage52self.collectionBannerImage = collectionBannerImage53self.royalties = royalties54}55}5657pub fun main(ownerAddress: Address, collections: {String : [UInt64]}) : {String : [NFT] } {58let data : {String : [NFT] } = {}5960let catalog = NFTCatalog.getCatalog()61let account = getAuthAccount(ownerAddress)62for collectionIdentifier in collections.keys {63if catalog.containsKey(collectionIdentifier) {64let value = catalog[collectionIdentifier]!65let tempPathStr = "catalog".concat(collectionIdentifier)66let tempPublicPath = PublicPath(identifier: tempPathStr)!67account.link<&{MetadataViews.ResolverCollection}>(68tempPublicPath,69target: value.collectionData.storagePath70)7172let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)7374if !collectionCap.check() {75return data76}7778let views = NFTRetrieval.getNFTViewsFromIDs(collectionIdentifier : collectionIdentifier, ids: collections[collectionIdentifier]!, collectionCap : collectionCap)7980let items : [NFT] = []8182for view in views {83let displayView = view.display84let externalURLView = view.externalURL85let collectionDataView = view.collectionData86let collectionDisplayView = view.collectionDisplay87let royaltyView = view.royalties88if (displayView == nil || externalURLView == nil || collectionDataView == nil || collectionDisplayView == nil || royaltyView == nil) {89// Bad NFT. Skipping....90continue91}9293items.append(94NFT(95id: view.id,96name : displayView!.name,97description : displayView!.description,98thumbnail : displayView!.thumbnail.uri(),99externalURL : externalURLView!.url,100storagePath : collectionDataView!.storagePath,101publicPath : collectionDataView!.publicPath,102privatePath : collectionDataView!.providerPath,103publicLinkedType : collectionDataView!.publicLinkedType,104privateLinkedType : collectionDataView!.providerLinkedType,105collectionName : collectionDisplayView!.name,106collectionDescription : collectionDisplayView!.description,107collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),108collectionBannerImage : collectionDisplayView!.bannerImage.file.uri(),109royalties : royaltyView!.getRoyalties()110)111)112}113114data[collectionIdentifier] = items115}116}117118119return data120}
Example 6 - Retrieve all MetadataViews for NFTs in a wallet
If you're looking for some MetadataViews that aren't in the core view list you can leverage this script to grab all the views each NFT supports. Note: You lose some typing here but get more data.
1import MetadataViews from 0x1d7e57aa558174482import NFTCatalog from 0x49a7cda3a1eecc293import NFTRetrieval from 0x49a7cda3a1eecc2945pub struct NFTCollectionData {6pub let storagePath : StoragePath7pub let publicPath : PublicPath8pub let privatePath: PrivatePath9pub let publicLinkedType: Type10pub let privateLinkedType: Type1112init(13storagePath : StoragePath,14publicPath : PublicPath,15privatePath : PrivatePath,16publicLinkedType : Type,17privateLinkedType : Type,18) {19self.storagePath = storagePath20self.publicPath = publicPath21self.privatePath = privatePath22self.publicLinkedType = publicLinkedType23self.privateLinkedType = privateLinkedType24}25}2627pub fun main(ownerAddress: Address) : { String : {String : AnyStruct} } {28let catalog = NFTCatalog.getCatalog()29let account = getAuthAccount(ownerAddress)30let items : [MetadataViews.NFTView] = []3132let data : { String : {String : AnyStruct} } = {}3334for key in catalog.keys {35let value = catalog[key]!36let tempPathStr = "catalog".concat(key)37let tempPublicPath = PublicPath(identifier: tempPathStr)!38account.link<&{MetadataViews.ResolverCollection}>(39tempPublicPath,40target: value.collectionData.storagePath41)42let collectionCap = account.getCapability<&AnyResource{MetadataViews.ResolverCollection}>(tempPublicPath)43if !collectionCap.check() {44continue45}4647var views = NFTRetrieval.getAllMetadataViewsFromCap(collectionIdentifier : key, collectionCap : collectionCap)4849if views.keys.length == 0 {50continue51}5253// Cadence doesn't support function return types, lets manually get rid of it54let nftCollectionDisplayView = views[Type<MetadataViews.NFTCollectionData>().identifier] as! MetadataViews.NFTCollectionData?55let collectionDataView = NFTCollectionData(56storagePath : nftCollectionDisplayView!.storagePath,57publicPath : nftCollectionDisplayView!.publicPath,58privatePath : nftCollectionDisplayView!.providerPath,59publicLinkedType : nftCollectionDisplayView!.publicLinkedType,60privateLinkedType : nftCollectionDisplayView!.providerLinkedType,61)62views.insert(key: Type<MetadataViews.NFTCollectionData>().identifier, collectionDataView)6364data[key] = views65}6667return data68}
Example 7 - Setup a user’s account to receive a specific collection
- Run the following script to retrieve some collection-level information for an NFT collection identifier from the catalog
1import MetadataViews from 0x1d7e57aa558174482import NFTCatalog from 0x49a7cda3a1eecc293import NFTRetrieval from 0x49a7cda3a1eecc2945pub struct NFTCollection {6pub let storagePath : StoragePath7pub let publicPath : PublicPath8pub let privatePath: PrivatePath9pub let publicLinkedType: Type10pub let privateLinkedType: Type11pub let collectionName : String12pub let collectionDescription: String13pub let collectionSquareImage : String14pub let collectionBannerImage : String1516init(17storagePath : StoragePath,18publicPath : PublicPath,19privatePath : PrivatePath,20publicLinkedType : Type,21privateLinkedType : Type,22collectionName : String,23collectionDescription : String,24collectionSquareImage : String,25collectionBannerImage : String26) {27self.storagePath = storagePath28self.publicPath = publicPath29self.privatePath = privatePath30self.publicLinkedType = publicLinkedType31self.privateLinkedType = privateLinkedType32self.collectionName = collectionName33self.collectionDescription = collectionDescription34self.collectionSquareImage = collectionSquareImage35self.collectionBannerImage = collectionBannerImage36}37}3839pub fun main(collectionIdentifier : String) : NFT? {40let catalog = NFTCatalog.getCatalog()4142assert(catalog.containsKey(collectionIdentifier), message: "Invalid Collection")4344return NFTCollection(45storagePath : collectionDataView!.storagePath,46publicPath : collectionDataView!.publicPath,47privatePath : collectionDataView!.providerPath,48publicLinkedType : collectionDataView!.publicLinkedType,49privateLinkedType : collectionDataView!.providerLinkedType,50collectionName : collectionDisplayView!.name,51collectionDescription : collectionDisplayView!.description,52collectionSquareImage : collectionDisplayView!.squareImage.file.uri(),53collectionBannerImage : collectionDisplayView!.bannerImage.file.uri()54)55}5657panic("Invalid Token ID")58}
- This script result can then be used to form a transaction by inserting the relevant variables from above into a transaction template like the following:
1import NonFungibleToken from 0x1d7e57aa558174482import MetadataViews from 0x1d7e57aa558174483{ADDITIONAL_IMPORTS}45transaction {67prepare(signer: AuthAccount) {8// Create a new empty collection9let collection <- {CONTRACT_NAME}.createEmptyCollection()1011// save it to the account12signer.save(<-collection, to: {STORAGE_PATH})1314// create a public capability for the collection15signer.link<&{PUBLIC_LINKED_TYPE}>(16{PUBLIC_PATH},17target: {STORAGE_PATH}18)1920// create a private capability for the collection21signer.link<&{PRIVATE_LINKED_TYPE}>(22{PRIVATE_PATH},23target: {STORAGE_PATH}24)25}26}
2. Install Node
1git clone --depth=1 https://github.com/onflow/nft-catalog.git
- Run
npm install
in the root of the project
- Run
npm test
in the root of the project
The works in these files:
- FungibleToken.cdc
- NonFungibleToken.cdc
- ExampleNFT.cdc
- MetadataViews.cdc
- NFTCatalog.cdc
- NFTCatalogAdmin.cdc
- NFTRetrieval.cdc
are under the Unlicense.