# Collection Configuration

There are plenty of fields to define the core configuration for your collection during creation. These are all stored on the collection object controlled by the manager. These fields control metadata, standards, valid token IDs, custom data, and immutable invariants.

Most fields are updatable over time, according to their corresponding permissions set in the collection permissions object (e.g. canUpdateValidTokenIds, canUpdateCollectionMetadata, etc). However, some fields are immutable and cannot be changed after creation (invariants).

## Complete Example

```typescript
import { MsgCreateCollection } from 'bitbadges';

const msg: MsgCreateCollection = {
    creator: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    defaultBalances: {
        balances: [],
        outgoingApprovals: [],
        incomingApprovals: [],
        autoApproveSelfInitiatedOutgoingTransfers: false,
        autoApproveSelfInitiatedIncomingTransfers: true,
        autoApproveAllIncomingTransfers: false,
        userPermissions: {
            // ... permission fields
        },
    },
    validTokenIds: [{ start: 1n, end: 100n }],
    collectionPermissions: {
        // ... permission fields
    },
    manager: 'bb1kj9kt5y64n5a8677fhjqnmcc24ht2vy9atmdls',
    collectionMetadata: {
        uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub',
        customData: '',
    },
    tokenMetadata: [
        {
            uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub/{id}',
            tokenIds: [{ start: 1n, end: 100n }],
            customData: '',
        },
    ],
    customData: 'Application-specific data',
    collectionApprovals: [
        // ... approval fields
    ],
    standards: ['Tradable', 'NFT'],
    isArchived: false,
    invariants: {
        noCustomOwnershipTimes: false,
        maxSupplyPerId: '0',
        cosmosCoinBackedPath: undefined,
        noForcefulPostMintTransfers: false,
        disablePoolCreation: false,
    },
    // ... other fields
};
```

## Valid Token IDs

`validTokenIds` defines the range of token IDs that exist within a collection. This is mainly informational but may also be used to enforce certain rules like canOverrideWithAnyValidTokenId.

Must be sequential from 1 to the total supply of the collection.

```typescript
const validTokenIds: UintRange<bigint>[] = [{ start: 1n, end: 100n }];
```

**Key points:**

* Set during collection creation or updated via `MsgUpdateCollection` (not invariants — see below)
* Controlled by `canUpdateValidTokenIds` permission
* Strongly recommended to be set at genesis and locked forever. Expanding / reducing valid token IDs is not recommended in most cases and advanced.

## Standards

`standards` provides informational tags that guide how to interpret and implement collection features. Standards are purely informational—no blockchain validation.

```typescript
const standards: string[] = ['Tradable', 'NFT', 'Cosmos Wrappable'];
```

**BitBadges Site Standards:**

* **Tradable**: Enables trading interface, orderbook tracking, and marketplace features
* **NFT**: Expects supply = 1 per token ID with full ownership times
* **Cosmos Wrappable**: Can be wrapped into Cosmos SDK coin denominations
* **Subscriptions**: Designed for recurring content delivery and subscription systems
* **Quests**: Achievement-based systems and quest completion tracking

For more information on compatibility with the BitBadges site, please reach out.

**Important:** Standards are informational only. Applications must verify compliance.

## Collection Metadata

`collectionMetadata` defines metadata for the entire collection.

```typescript
const collectionMetadata: CollectionMetadata = {
    uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub',
    customData: '',
};
```

**Metadata Format:** The BitBadges API expects metadata to follow this interface:

```typescript
interface Metadata {
    name: string;
    description: string;
    image: string;
}
```

**Permission Control:** Updates controlled by `canUpdateCollectionMetadata` permission.

## Token Metadata

`tokenMetadata` defines metadata for individual tokens. Uses first-match approach via linear scan for specific token IDs. We support the dynamic token ID replacement feature {id} in the URI.

```typescript
const tokenMetadata: TokenMetadata[] = [
    {
        uri: 'ipfs://Qmf8xxN2fwXGgouue3qsJtN8ZRSsnoHxM9mGcynTPhh6Ub/{id}',
        tokenIds: [{ start: 1n, end: 100n }],
        customData: '',
    },
];
```

**Key Features:**

* **Dynamic Token ID Replacement**: URI `{id}` placeholder is replaced with actual token ID
* **First-Match Logic**: Entries evaluated in order; first matching entry for a token ID is used, subsequent entries are ignored.
* **Permission Control**: Updates controlled by `canUpdateTokenMetadata` permission

## Custom Data

`customData` provides generic string storage for application-specific information. It is not interpreted by the chain itself—use it for any application-specific implementations or contract integrations. You may also see `customData` used elsewhere like in approvals, address lists, and other structures.

```typescript
const customData: string = 'Any string value you want to store';
```

**Where Custom Data Appears:**

* Collection-level: `customData`
* Token metadata: `customData` within token metadata structures
* Address lists: `customData` for list-specific information
* Messages: Various transaction messages include custom data fields

**Permission Control:** Updates controlled by `canUpdateCustomData` permission.

### Inline metadata via `customData`

Wherever a metadata-bearing entity exposes a `(uri, customData)` pair (collection metadata, token metadata, address lists, dynamic stores, paths, and approvals), `customData` can also hold an inline JSON metadata document with the same shape that a hosted metadata file would have:

```typescript
const collectionMetadata: CollectionMetadata = {
    uri: '',
    customData: JSON.stringify({
        name: 'My Collection',
        image: 'ipfs://Qm.../image.png',
        description: 'A short description.',
    }),
};
```

The indexer, SDK, and frontend resolve metadata with `uri` taking priority: if `uri` is non-empty it is fetched as today; otherwise `customData` is parsed as inline JSON and surfaced as the resolved metadata. This is a zero-hosting alternative to IPFS-hosted JSON—no Pinata account, no pin to maintain. Approval metadata uses `name` + `description` only (no `image`); every other entity expects `name` + `image` + `description`.

`customData` is still a free-form string. Anything that does not parse as a JSON object with at least one of `name`, `image`, or `description` is ignored as metadata and the entity falls through to "no metadata" rather than rendering attacker-controlled fields.

#### Cost considerations: keep images off-chain

Inline `customData` is stored on-chain. **You pay gas proportional to the byte size, every block has a hard size cap, and the bytes live in chain state forever.** This is fine for short text fields and a tiny URL pointing at an image, but it is the wrong place for large payloads.

Strong recommendation: **do not store image bytes (or any base64-encoded media) inline in `customData`.** Even a small JPEG is tens of KB and a PNG can easily run into hundreds of KB or MB — orders of magnitude more expensive than a text-only payload, and large enough to start fighting block-size limits during bulk updates. Pre-host images on IPFS (or any other URL host) and reference them by URL inside the inline JSON:

```typescript
const collectionMetadata: CollectionMetadata = {
    uri: '',
    customData: JSON.stringify({
        name: 'My Collection',
        image: 'ipfs://Qm.../image.png', // <-- URL only, NOT base64
        description: 'A short description.',
    }),
};
```

Rule of thumb:

* **Inline customData is great for**: name, description, attributes, links, small structured fields — the metadata wrapper.
* **Inline customData is the wrong place for**: image bytes, video, audio, anything binary, anything more than a few KB.
* If your full metadata JSON (after stringification) is more than \~4 KB, prefer hosting the JSON externally and using `uri` instead.

It is a tradeoff. Inline customData buys you zero hosting setup and no IPFS pin to maintain; remote hosting buys you cheap storage of large assets. Pick per entity, not per collection.

#### Optional: deterministic SVG placeholder art (zero-hosting image)

If you want zero hosting **and** an image, the SDK ships a deterministic SVG generator that produces 1-8 KB `data:image/svg+xml;base64,...` URIs you can drop directly into the `image` field of inline customData:

```typescript
import { generatePlaceholderArt } from 'bitbadges';

const art = generatePlaceholderArt({ seed: 'My Collection' });
const customData = JSON.stringify({
    name: 'My Collection',
    description: '...',
    image: art.imageUri, // data:image/svg+xml;base64,...
});
// On-chain: collectionMetadata.uri = '', collectionMetadata.customData = customData
```

Same seed always produces the same art (six curated presets × 24 palettes, hash-picked deterministically). Pin a specific look with `style` / `paletteName` if needed.

**This is not free.** The SVG bytes still live on-chain — you're shifting the cost from a hosting fee to chain gas, not eliminating it. Concretely:

* Inline SVG: 1-8 KB on-chain → roughly 10-80k extra gas per write.
* Inline customData with hosted image URL (`ipfs://...`): \~250 B on-chain wrapper, plus an IPFS pin you maintain. Cheapest on-chain when you have an image.
* Full URI mode (`uri` set, `customData` empty): \~50 B on-chain (just the URL), plus an IPFS pin for JSON + image. Cheapest on-chain overall, but two pinning concerns.

Pick the SVG generator when "no hosting setup at all" is worth the extra \~80k gas per write. For high-frequency mints, image-heavy collections, or anything where the art *is* the product, prefer a hosted image URL.

## Default Balances

`defaultBalances` are predefined balance stores automatically assigned to new users (uninitialized balance stores) when they first interact with a collection. Set during collection creation only—cannot be updated after genesis.

This is not often used but can be useful for certain use cases:

* Blocking incoming transfers by default (enforcing opt-in only restrictions)
* Default starting balances for all users
* Default approval settings for all users

```typescript
const defaultBalances: UserBalanceStore<bigint> = {
    balances: [],
    outgoingApprovals: [],
    incomingApprovals: [],
    autoApproveSelfInitiatedOutgoingTransfers: false,
    autoApproveSelfInitiatedIncomingTransfers: true,
    autoApproveAllIncomingTransfers: false,
    userPermissions: {
        // ... permission fields
    },
};
```

**Important Limitations:**

* **No complex approval criteria**: Cannot include any approvals not compatible with auto-scan mode. See [Auto-Scan and Prioritized Approvals](https://github.com/trevormil/bitbadges-docs/blob/master/x-tokenization/learn/auto-scan-and-prioritized-approvals.md) for more information.

**Key points:**

* Creation-only field—set once during collection creation
* Establishes baseline behavior for all new users
* Users can customize their own approval settings after initialization

## IsArchived

`isArchived` controls whether a collection is archived, temporarily or permanently disabling all transactions while keeping collection data verifiable and public on-chain.

This is a collection-level option that can be used to temporarily or permanently disable all transactions until unarchived. Controlled by the manager. Useful for pausing, halts, or other maintenance operations.

Note that there are also other ways to implement halting logic which may be better suited for your use case.

* Chain-level x/circuit breakers
* Dynamic stores / token ownership requirements that can be controlled by another entity

```typescript
const isArchived: boolean = false;
```

**Transaction Behavior:**

* **When archived**: All transactions fail (no updates, transfers, or changes allowed). Read operations continue. Only unarchiving transactions can succeed.
* **When unarchived**: Normal operations resume. All collection data remains intact. Standard permission checks apply.

**Permission Control:** Updates controlled by `canArchiveCollection` permission. Note that this permission controls the updatability of the `isArchived` field, not the current archive status. You can lock the archive status forever (either `true` or `false`) by permanently forbidding updates.

## Invariants

> **Important: Invariants are creation-only.** Once a collection is created, its invariants cannot be modified or removed. Do not include invariants in update transactions — they will be ignored or may cause unexpected behavior. Set invariants only when creating a new collection (`collectionId = "0"` in `MsgUniversalUpdateCollection`, or via `MsgCreateCollection`).

`invariants` are immutable rules set upon collection creation that cannot be broken or modified afterward. They enforce fundamental constraints on collection behavior.

```typescript
const invariants: CollectionInvariants<bigint> = {
    noCustomOwnershipTimes: false,
    maxSupplyPerId: '0',
    cosmosCoinBackedPath: undefined,
    noForcefulPostMintTransfers: false,
    disablePoolCreation: false,
    evmQueryChallenges: [],
};
```

### Available Invariants

**noCustomOwnershipTimes**

* Enforces all ownership times must be full ranges `[{ start: 1n, end: 18446744073709551615n }]` for all time-dependent structures - balances, approvals, etc.
* Prevents time-based restrictions on token ownership
* Affects collection approvals, user approvals, and transfer balances
* Typically recommended to be set to true if you do not need such functionality.

**maxSupplyPerId**

* Maximum supply per token ID (string-based uint64)
* Prevents any balance amount from exceeding the specified maximum
* Zero value means no enforcement
* Sanity check to enforce a minting cap. Should NOT replace proper approval design.

**cosmosCoinBackedPath**

* IBC backed (sdk.coin) path for the collection
* Only one path allowed per collection
* See [IBC Backed Minting Invariants](/token-standard/learn/ibc-backed-minting.md) for details

**noForcefulPostMintTransfers**

* Disallows collection approvals with `overridesFromOutgoingApprovals` or `overridesToIncomingApprovals` set to `true` (excluding the Mint address which is exempt)
* Prevents forceful transfers that bypass user-level approvals
* Easy way for you to enforce that there will never be any forceful transfers that bypass user-level approvals ever in your collection.

**disablePoolCreation**

* Prevents creation of liquidity pools using assets from this collection in our gamm module.
* When enabled, pool creation attempts will fail

**evmQueryChallenges**

* Post-transfer EVM query invariants that are checked after all balance updates
* Each challenge executes a read-only EVM contract query via staticcall
* If any query returns a result that doesn't match the expected result, the transfer is reverted
* Useful for enforcing global invariants like maximum holder count, balance caps, or custom compliance rules
* Placeholders: `$collectionId`, `$sender`, `$initiator`, `$recipient` (first recipient), `$recipients` (all recipients as concatenated 32-byte hex). See [EVM Query Challenges – Placeholders](/token-standard/learn/approval-criteria/evm-query-challenges.md#placeholders) for approval vs invariant placeholder details.
* See [EVM Query Challenges](/token-standard/learn/approval-criteria/evm-query-challenges.md) for the full challenge structure (fields, operators, gas limits)

```typescript
const evmQueryChallenges: EVMQueryChallenge<bigint>[] = [
    {
        contractAddress: '0xComplianceContract...',
        calldata: '70a08231000000000000000000000000$collectionId',
        expectedResult: '0000000000000000000000000000000000000000000000000000000000000001',
        comparisonOperator: 'eq',
        gasLimit: '100000',
        uri: '',
        customData: '',
    },
];
```

**Important:** Invariants can only be set during collection creation and cannot be updated or removed afterward.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.bitbadges.io/token-standard/learn/collection-setup-fields.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
