Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.moralis.com/llms.txt

Use this file to discover all available pages before exploring further.

Question it answers

“Give me the collection metadata — name, symbol, contract type — for NFT collection 0x….” Mirrors Moralis GET /nft/{address}/metadata.
This is the NFT-collection sibling of Token Metadata. Both read the same per-block deployed-contracts stream, but this recipe keeps only NFT-type contracts (ERC-721 / ERC-1155) and drops ERC-20 tokens. Collection metadata is fixed at deploy time, so there is normally exactly one row per (chain_id, token_address) — the latest deploy by (block_number, transaction_index) is canonical (defensive against a CREATE2 re-deploy at a reused address).

What you get

One row per NFT-type contract deploy, keyed by the collection (token_address):
ColumnDescription
token_addressThe NFT collection contract address
nameOn-chain collection name — best-effort; may be empty for many ERC-1155 collections
symbolOn-chain collection symbol — best-effort; may be empty
contract_typeERC721 · ERC1155 · NFT (derived from the EIP-165 interface ids; NFT is the fallback when neither interface is advertised)
deployer_addressAddress that deployed the contract
block_number, transaction_indexDeploy position — the recency tiebreaker for latest-wins
block_timestampBlock time of the deploy
Unlike the ERC-20 path, symbol-less contracts are not dropped — name/symbol are best-effort on-chain fields, not filter keys. Far fewer rows land than Token Metadata: only a handful of NFT contracts deploy per ~1000 blocks.

Source

The transform reads the per-block deployed-contracts stream and keeps the NFT slice — contracts whose type contains NFT (ERC-721 / ERC-1155). A contract is kept when its deployer_address is non-empty. contract_type is derived from the contract’s supportedInterfaces (EIP-165 ids): 0xd9b67a26ERC1155, else 0x80ac58cdERC721, else the NFT fallback.

Destination

DestinationTableBy-collection access
ClickHouse (first-class)fact_nft_collection_metadataORDER BY (chain_id, token_address); latest deploy via argMax(…, (block_number, transaction_index)), read with FINAL
Postgresnft_collection_metadata (matview over nft_collection_metadata_observations)Unique index on (token_address)
MySQLnft_collection_metadata (latest-wins state table over the observations)Primary key on (token_address)
ClickHouse uses the collapsing log-table pattern (see the recipes overview) so a reorg of a deploy block self-corrects. Because a collection can in principle deploy more than once at the same address (CREATE2), reads resolve latest deploy wins with argMax(…, (block_number, transaction_index)) rather than assuming a single row. Postgres derives the current-metadata surface as a DISTINCT ON (token_address) materialized view; MySQL maintains it incrementally with a latest-wins trigger.

Full schema

The complete read table this recipe produces. Keep the columns you need — name/symbol/contract_type are usually enough for a metadata lookup (see Schema & flexibility). name and symbol are stored as text; they can contain quotes and odd characters, which are parameter-bound on insert so there is no quoting breakage.
CREATE TABLE recipe_nft_collection_metadata.fact_nft_collection_metadata
(
    vendor_event_id     String,
    ingested_at         DateTime64(3),
    chain_id            UInt32,
    block_hash          String,
    block_number        UInt64,
    transaction_index   UInt32,
    event_ts            DateTime64(3),
    token_address       String,
    deployer_address    String,
    name                String,
    symbol              String,
    contract_type       LowCardinality(String),   -- ERC721 | ERC1155 | NFT
    sign                Int8
)
ENGINE = ReplicatedCollapsingMergeTree(
    '/clickhouse/tables/{database}/fact_nft_collection_metadata', '{replica}', sign)
PARTITION BY (chain_id, toYYYYMM(event_ts))
ORDER BY (chain_id, token_address, block_number, transaction_index, vendor_event_id);
The sign column drives reorg collapsing — read with FINAL or a sign-aware aggregate, never a bare WHERE sign = 1. A single-node setup can use CollapsingMergeTree(sign) without the replication path. The collection-keyed sort order makes a single-collection lookup a tight prefix scan.
-- 1. Observations (sink target): one row per NFT-contract deploy.
CREATE TABLE public.nft_collection_metadata_observations (
  position           BIGINT          NOT NULL,    -- block-level cursor used during backfill
  transaction_index  BIGINT,
  block_number       BIGINT          NOT NULL,
  block_timestamp    BIGINT          NOT NULL,    -- unix seconds
  tx_hash            VARCHAR(66)     NOT NULL,
  vendor_event_id    TEXT            NOT NULL,
  token_address      VARCHAR(66)     NOT NULL,
  deployer_address   VARCHAR(66)     NOT NULL,
  name               TEXT            NOT NULL,
  symbol             TEXT            NOT NULL,
  contract_type      VARCHAR(16)     NOT NULL     -- ERC721 | ERC1155 | NFT
);

-- Speeds the DISTINCT ON (latest-per-collection) the materialized view computes.
CREATE INDEX IF NOT EXISTS ncmo_token_recency_idx
  ON public.nft_collection_metadata_observations
  (token_address, block_number DESC, transaction_index DESC);

-- 2. Current-metadata materialized view: latest deploy per token_address.
CREATE MATERIALIZED VIEW public.nft_collection_metadata AS
SELECT DISTINCT ON (token_address)
  token_address,
  name,
  symbol,
  contract_type,
  block_number,
  deployer_address
FROM public.nft_collection_metadata_observations
ORDER BY token_address, block_number DESC, transaction_index DESC;

-- Required by REFRESH MATERIALIZED VIEW CONCURRENTLY (one row per collection).
CREATE UNIQUE INDEX IF NOT EXISTS nft_collection_metadata_pk
  ON public.nft_collection_metadata (token_address);

-- Lookup helpers.
CREATE INDEX IF NOT EXISTS nft_collection_metadata_symbol_idx
  ON public.nft_collection_metadata (symbol);
CREATE INDEX IF NOT EXISTS nft_collection_metadata_type_idx
  ON public.nft_collection_metadata (contract_type);
The sink appends to nft_collection_metadata_observations; refresh the current-metadata view on a schedule with REFRESH MATERIALIZED VIEW CONCURRENTLY nft_collection_metadata;. MySQL mirrors this shape — VARCHAR(255) for name/symbol — with the current-metadata nft_collection_metadata table maintained incrementally by an AFTER INSERT latest-wins trigger.

Example reads

Metadata for one collection — latest deploy wins (ClickHouse):
SELECT
  token_address,
  argMax(name,             (block_number, transaction_index)) AS name,
  argMax(symbol,           (block_number, transaction_index)) AS symbol,
  argMax(contract_type,    (block_number, transaction_index)) AS contract_type,
  argMax(deployer_address, (block_number, transaction_index)) AS deployer_address
FROM recipe_nft_collection_metadata.fact_nft_collection_metadata FINAL
WHERE chain_id = 1 AND token_address = lower('0x...')
GROUP BY token_address;
All NFT collections deployed in a block range (ClickHouse):
SELECT token_address, name, symbol, contract_type, block_number
FROM recipe_nft_collection_metadata.fact_nft_collection_metadata FINAL
WHERE chain_id = 1 AND block_number BETWEEN 19000000 AND 19001000
ORDER BY block_number, transaction_index
LIMIT 100;
Metadata for one collection (Postgres, after REFRESH MATERIALIZED VIEW CONCURRENTLY nft_collection_metadata;):
SELECT token_address, name, symbol, contract_type
FROM public.nft_collection_metadata
WHERE token_address = lower('0x...');

Modes

Shipped defaults: ClickHouse hybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse — see the overview.
The realtime reorg path on Postgres / MySQL needs a single-column UNIQUE on the block-level position, but a single block can deploy several NFT contracts — so the array-expanded rows can only carry a composite unique. Run realtime/hybrid on ClickHouse, where the collapsing log table corrects reorgs per-block. The Postgres / MySQL configs are intended for historical backfill, which is the dominant use of this recipe — a once-off metadata census.

EVM only

This recipe is EVM-shaped: it reads EVM contract deploys. Solana NFT (Metaplex) collection metadata does not flow through the deployed-contracts stream the same way EVM contract deploys do, so the recipe targets EVM chains. It is chain-parametrized via the chain setting across any supported EVM chain.

Fidelity gaps

GET /nft/{address}/metadata returns enrichment fields this recipe does not source — all off-chain indexer or editorial state:
  • collection_logo / collection_banner_image — off-chain CDN assets.
  • collection_category — editorial categorisation.
  • project_url / discord_url / telegram_url / wiki_url — social links.
  • possible_spam / verified_collection — spam & verification heuristics from a separate pipeline.
  • synced_at (wall-clock) — this recipe carries on-chain block_number / block_timestamp instead.
The core on-chain fields — token_address, name, symbol, contract_type, plus block_number and deployer_address — are fully sourced.

NFT Owners by Contract

Current holders of a collection — pair with this metadata for a complete collection view.

NFT Marketplace

The use case this collection metadata powers.