Skip to main content
Metadata allows you to attach arbitrary key-value data to workflows. This is useful for tracking business context, debugging information, or any custom data you want associated with a workflow instance.

What is Metadata?

Metadata is a map of string keys to byte array values that you can attach to workflows at creation time. Unlike tags (which are for identification and retrieval), metadata is for storing contextual information.

Metadata Use Cases

  • Business context - Store customer info, order details, or request data
  • Debugging information - Track request IDs, trace IDs, or correlation IDs
  • Audit trails - Record who started the workflow and when
  • Feature flags - Store configuration or feature toggle states
  • Custom attributes - Any domain-specific data

Adding Metadata to Workflows

Attach metadata when creating a workflow:
val metadata = mapOf(
    "userId" to "12345".toByteArray(),
    "requestId" to "req-abc-123".toByteArray(),
    "source" to "web-app".toByteArray()
)

val workflow = client.newWorkflow(
    MyWorkflow::class.java,
    meta = metadata
)
workflow.process(data)

Storing Different Data Types

Since metadata values are byte arrays, you can store any serializable data:

Strings

val meta = mapOf(
    "userName" to "john.doe".toByteArray(),
    "email" to "[email protected]".toByteArray()
)

Numbers

val meta = mapOf(
    "priority" to 5.toString().toByteArray(),
    "retryCount" to 3.toString().toByteArray()
)

JSON Objects

import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString

val userContext = mapOf(
    "id" to "12345",
    "name" to "John Doe",
    "role" to "admin"
)

val meta = mapOf(
    "userContext" to Json.encodeToString(userContext).toByteArray()
)

Serialized Objects

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString

@Serializable
data class OrderContext(
    val orderId: String,
    val customerId: String,
    val amount: Double
)

val orderContext = OrderContext(
    orderId = "ORD-001",
    customerId = "CUST-123",
    amount = 99.99
)

val meta = mapOf(
    "orderContext" to Json.encodeToString(orderContext).toByteArray()
)

Metadata in CloudEvents

Metadata is included in CloudEvents emitted by Infinitic. When a workflow is dispatched, the metadata is available in the WORKFLOW_META field:
// CloudEvent data structure
{
    "workflowName": "MyWorkflow",
    "workflowId": "550e8400-e29b-41d4-a716-446655440000",
    "workflowMeta": {
        "userId": "12345",
        "requestId": "req-abc-123",
        "source": "web-app"
    },
    "workflowTags": ["user:12345"]
}

WorkflowMeta Class

Internally, metadata is managed by the WorkflowMeta class:
data class WorkflowMeta(val meta: Map<String, ByteArray>)

Combining Tags and Metadata

Use tags for workflow identification and metadata for context:
val workflow = client.newWorkflow(
    OrderWorkflow::class.java,
    tags = setOf(
        "order:ORD-2024-001",
        "customer:CUST-123"
    ),
    meta = mapOf(
        "customerName" to "John Doe".toByteArray(),
        "orderAmount" to "99.99".toByteArray(),
        "currency" to "USD".toByteArray(),
        "createdBy" to "web-app".toByteArray(),
        "timestamp" to System.currentTimeMillis().toString().toByteArray()
    )
)

Metadata Helper Functions

Create helper functions for common metadata patterns:
class MetadataBuilder {
    private val metadata = mutableMapOf<String, ByteArray>()
    
    fun addString(key: String, value: String) = apply {
        metadata[key] = value.toByteArray()
    }
    
    fun addInt(key: String, value: Int) = apply {
        metadata[key] = value.toString().toByteArray()
    }
    
    fun addJson(key: String, value: Any) = apply {
        metadata[key] = Json.encodeToString(value).toByteArray()
    }
    
    fun addTimestamp(key: String = "timestamp") = apply {
        metadata[key] = System.currentTimeMillis().toString().toByteArray()
    }
    
    fun build(): Map<String, ByteArray> = metadata
}

// Usage
val meta = MetadataBuilder()
    .addString("userId", "12345")
    .addInt("priority", 5)
    .addTimestamp()
    .build()

Reading Metadata from Events

When listening to CloudEvents, you can extract metadata:
import io.infinitic.cloudEvents.CloudEventListener

class MyEventListener : CloudEventListener {
    override suspend fun onEvent(event: CloudEvent) {
        if (event.type == "infinitic.workflow.dispatch") {
            val data = parseCloudEventData(event)
            val metadata = data["workflowMeta"] as? Map<String, String>
            
            val userId = metadata?.get("userId")
            val requestId = metadata?.get("requestId")
            
            logger.info("Workflow dispatched for user: $userId, request: $requestId")
        }
    }
}

Best Practices

Metadata is stored with the workflow. Keep it reasonably sized to avoid storage bloat.
Establish a convention for metadata keys (e.g., camelCase, snake_case) and stick to it.
Never store sensitive data like passwords or API keys in metadata.
Always include request/trace IDs for distributed tracing and debugging.
Maintain documentation of what metadata keys your application uses and their formats.
Create type-safe wrappers around metadata to prevent serialization errors.

Metadata vs Tags

FeatureTagsMetadata
PurposeIdentification & retrievalContext & information
FormatSet of stringsMap of string to bytes
QueryableYes (getByTag)No
Size limitReasonable (IDs/labels)Flexible (but keep small)
Use caseFinding workflowsStoring context
MutabilityImmutableImmutable

Example: E-commerce Order Tracking

@Serializable
data class OrderMetadata(
    val customerId: String,
    val customerEmail: String,
    val orderTotal: Double,
    val currency: String,
    val shippingAddress: String,
    val paymentMethod: String,
    val createdAt: Long,
    val source: String
)

class OrderWorkflowService(private val client: InfiniticClient) {
    
    fun startOrderWorkflow(order: Order): String {
        val orderMetadata = OrderMetadata(
            customerId = order.customerId,
            customerEmail = order.customerEmail,
            orderTotal = order.total,
            currency = order.currency,
            shippingAddress = order.shippingAddress,
            paymentMethod = order.paymentMethod,
            createdAt = System.currentTimeMillis(),
            source = "web-checkout"
        )
        
        val workflow = client.newWorkflow(
            OrderWorkflow::class.java,
            tags = setOf(
                "order:${order.id}",
                "customer:${order.customerId}",
                "customId:${order.id}"
            ),
            meta = mapOf(
                "orderMetadata" to Json.encodeToString(orderMetadata).toByteArray(),
                "requestId" to order.requestId.toByteArray(),
                "correlationId" to order.correlationId.toByteArray()
            )
        )
        
        workflow.processOrder(order)
        return order.id
    }
}

Build docs developers (and LLMs) love