Overview
Metadata allows you to attach custom key-value pairs to Bloque resources. Use metadata to:
Store application-specific data
Track internal references and IDs
Add context to transactions
Implement custom business logic
Enable filtering and reporting
Metadata is not used for operational logic by Bloque—it’s purely for your application’s use. However, certain metadata fields like spending_control and mcc_whitelist are used to configure card behavior.
Attach metadata when creating accounts to store custom information.
Card Accounts
const card = await user . accounts . card . create ({
name: 'Corporate Card' ,
ledgerId: pocket . ledgerId ,
metadata: {
// Custom application data
department: 'engineering' ,
employeeId: 'EMP-12345' ,
costCenter: 'CC-001' ,
// Card configuration (special fields)
spending_control: 'smart' ,
preferred_asset: 'DUSD/6' ,
default_asset: 'DUSD/6' ,
mcc_whitelist: {
[foodPocket.urn]: [ '5411' , '5812' ],
},
priority_mcc: [ foodPocket . urn , generalPocket . urn ],
},
});
console . log ( 'Department:' , card . metadata ?. department );
For card accounts, certain metadata fields are reserved and control card behavior:
spending_control - ‘default’ or ‘smart’
mcc_whitelist - MCC routing configuration
priority_mcc - Pocket priority order
preferred_asset - Preferred settlement asset
default_asset - Default asset for transactions
See Spending Controls for details.
Virtual Accounts
const pocket = await user . accounts . virtual . create (
{
name: 'Project Budget' ,
metadata: {
projectId: 'PROJ-789' ,
budgetYear: 2025 ,
owner: '[email protected] ' ,
tags: [ 'research' , 'development' ],
},
},
{ waitLedger: true },
);
Polygon Wallets
const wallet = await user . accounts . polygon . create ({
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb' ,
metadata: {
network: 'mainnet' ,
purpose: 'treasury' ,
multiSig: false ,
},
});
Add context to individual transfers:
Single Transfer
const transfer = await user . accounts . transfer ({
sourceUrn: savings . urn ,
destinationUrn: spending . urn ,
amount: '50000000' ,
asset: 'DUSD/6' ,
metadata: {
// Transaction context
note: 'Weekly allowance' ,
category: 'personal' ,
invoiceId: 'INV-2025-001' ,
externalRef: 'ext_abc123' ,
// Custom tracking
reconciliationId: 'REC-456' ,
approvedBy: '[email protected] ' ,
},
});
Batch Transfer
Metadata can be added at both the batch level and per-operation:
const result = await user . accounts . batchTransfer ({
reference: 'payroll-2025-02' ,
// Batch-level metadata
metadata: {
department: 'engineering' ,
period: '2025-02' ,
payrollRun: 'PR-2025-02-15' ,
approvedBy: '[email protected] ' ,
totalEmployees: 3 ,
},
operations: [
{
fromUrn: treasury . urn ,
toUrn: alice . urn ,
reference: 'salary-alice-feb' ,
amount: '3000000000' ,
asset: 'DUSD/6' ,
// Per-operation metadata
metadata: {
employee: 'alice' ,
employeeId: 'EMP-001' ,
type: 'salary' ,
hours: 160 ,
rate: '18.75' ,
},
},
{
fromUrn: treasury . urn ,
toUrn: bob . urn ,
reference: 'salary-bob-feb' ,
amount: '2500000000' ,
asset: 'DUSD/6' ,
metadata: {
employee: 'bob' ,
employeeId: 'EMP-002' ,
type: 'salary' ,
hours: 160 ,
rate: '15.625' ,
},
},
],
webhookUrl: 'https://api.example.com/webhooks/payroll' ,
});
Update metadata on existing accounts:
const updated = await user . accounts . card . updateMetadata ({
urn: card . urn ,
metadata: {
// Update custom fields
department: 'sales' ,
cardLimit: '10000' ,
// Update spending controls
spending_control: 'smart' ,
mcc_whitelist: {
[foodPocket.urn]: [ '5411' , '5812' , '5814' ],
},
priority_mcc: [ foodPocket . urn , mainWallet . urn ],
},
});
For card accounts, name and source are reserved fields and cannot be modified via updateMetadata().
const updated = await user . accounts . virtual . updateMetadata ({
urn: pocket . urn ,
metadata: {
budgetYear: 2026 ,
lastReviewed: new Date (). toISOString (),
status: 'active' ,
},
});
Use consistent naming Establish naming conventions for metadata keys across your application.
Keep it flat Avoid deeply nested objects. Use simple key-value pairs when possible.
Don't store sensitive data Never store passwords, API keys, or PII in metadata fields.
Document your schema Maintain documentation of metadata fields used in your application.
Keys : Max 256 characters, alphanumeric and underscores only
Values : Max 2048 characters per value
Total size : Max 50 metadata keys per resource
Types : Strings, numbers, booleans, null, and simple arrays/objects
Common Patterns
Tracking External References
Link Bloque resources to your internal systems:
const account = await user . accounts . virtual . create ({
metadata: {
internalId: 'ACC-12345' ,
externalSystem: 'salesforce' ,
externalId: 'sf_0062000001abc' ,
syncedAt: new Date (). toISOString (),
},
});
Multi-tenancy
Segment resources by tenant or organization:
const card = await user . accounts . card . create ({
metadata: {
tenantId: 'tenant_xyz' ,
organizationId: 'org_abc' ,
workspaceId: 'ws_123' ,
},
});
Feature Flags
Control feature availability per account:
const account = await user . accounts . virtual . create ({
metadata: {
features: {
advancedReporting: true ,
autoTopUp: false ,
exportToCsv: true ,
},
},
});
Audit Trail
Track who created or modified resources:
const transfer = await user . accounts . transfer ({
sourceUrn: source . urn ,
destinationUrn: dest . urn ,
amount: '1000000' ,
asset: 'DUSD/6' ,
metadata: {
createdBy: '[email protected] ' ,
createdAt: new Date (). toISOString (),
ipAddress: request . ip ,
userAgent: request . headers [ 'user-agent' ],
reason: 'Monthly subscription payment' ,
},
});
Metadata is included when fetching accounts or transactions:
// Get single account
const account = await user . accounts . get ( accountUrn );
console . log ( 'Metadata:' , account . metadata );
// List accounts
const { accounts } = await user . accounts . list ();
accounts . forEach (( acc ) => {
console . log ( ` ${ acc . urn } : ${ JSON . stringify ( acc . metadata ) } ` );
});
// Check movements
const { data } = await user . accounts . movements ({
urn: account . urn ,
asset: 'DUSD/6' ,
});
data . forEach (( movement ) => {
if ( movement . details ?. metadata ) {
console . log ( 'Transaction metadata:' , movement . details . metadata );
}
});
Metadata is included in webhook payloads:
{
"event" : "transfer.completed" ,
"timestamp" : "2025-02-15T14:30:00Z" ,
"data" : {
"queueId" : "queue_abc123" ,
"sourceUrn" : "did:bloque:account:virtual:acc-12345" ,
"destinationUrn" : "did:bloque:account:card:usr-123:crd-456" ,
"amount" : "50000000" ,
"asset" : "DUSD/6" ,
"status" : "completed" ,
"metadata" : {
"note" : "Weekly allowance" ,
"category" : "personal" ,
"invoiceId" : "INV-2025-001"
}
}
}
Use metadata to route webhooks or trigger specific actions:
app . post ( '/webhooks/bloque' , async ( req , res ) => {
const { event , data } = req . body ;
if ( event === 'transfer.completed' ) {
const { metadata } = data ;
// Route based on metadata
if ( metadata ?. category === 'payroll' ) {
await notifyHRDepartment ( data );
} else if ( metadata ?. category === 'invoice' ) {
await updateInvoiceStatus ( metadata . invoiceId , 'paid' );
}
}
res . status ( 200 ). send ( 'OK' );
});
Examples by Use Case
// Track subscription details
const transfer = await user . accounts . transfer ({
sourceUrn: user . urn ,
destinationUrn: service . urn ,
amount: '29000000' , // $29/month
asset: 'DUSD/6' ,
metadata: {
subscriptionId: 'SUB-789' ,
plan: 'premium' ,
billingPeriod: '2025-02' ,
renewalDate: '2025-03-15' ,
autoRenew: true ,
},
});
// Track employee expenses
const card = await user . accounts . card . create ({
metadata: {
employeeId: 'EMP-123' ,
department: 'sales' ,
expenseCategory: 'travel' ,
approvalRequired: true ,
monthlyLimit: '5000000000' , // $5,000
requiresReceipt: true ,
},
});