Custom Fields
Add structured metadata to your assets with custom fields and predefined options.
Overview
Custom fields allow you to add standardized metadata to assets beyond the default title, description, and tags. They're perfect for:
- Status tracking (Draft, Review, Approved)
- Project categorization
- Rights management
- Asset attributes (Season, Collection, Style)
- Workflow states
Prerequisites
- Access Token: OAuth2 token with admin permissions
- Organization Slug: Your organization identifier
Creating Custom Fields
Basic Custom Field
Create a custom field with predefined options:
curl -X POST "https://api.playbook.com/v1/my-org/custom_fields?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"field": {
"name": "Approval Status",
"options": ["Draft", "In Review", "Approved", "Rejected"]
}
}'
Response:
{
"data": {
"name": "Approval Status",
"token": "field-abc123",
"options": [
{
"name": "Draft",
"token": "opt-draft-xyz"
},
{
"name": "In Review",
"token": "opt-review-abc"
},
{
"name": "Approved",
"token": "opt-approved-def"
},
{
"name": "Rejected",
"token": "opt-rejected-ghi"
}
]
}
}
Listing Custom Fields
Get all custom fields in your organization:
curl "https://api.playbook.com/v1/my-org/custom_fields?access_token=TOKEN"
Response:
{
"data": [
{
"name": "Approval Status",
"token": "field-abc123",
"options": [
{ "name": "Draft", "token": "opt-draft-xyz" },
{ "name": "Approved", "token": "opt-approved-def" }
]
},
{
"name": "Season",
"token": "field-def456",
"options": [
{ "name": "Spring/Summer", "token": "opt-ss-abc" },
{ "name": "Fall/Winter", "token": "opt-fw-def" }
]
}
],
"pagy": {
"current_page": 1,
"total_count": 2
}
}
Assigning Field Values to Assets
Once fields are created, assign values when creating or updating assets:
On Asset Creation
curl -X POST "https://api.playbook.com/v1/my-org/assets?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"uri": "https://example.com/product.jpg",
"title": "Product Photo",
"fields": {
"Approval Status": "Draft",
"Season": "Spring/Summer"
}
}'
On Asset Update
curl -X PATCH "https://api.playbook.com/v1/my-org/assets/product-photo/update?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"asset": {
"fields": {
"Approval Status": "Approved"
}
}
}'
Response:
{
"data": {
"id": 123,
"token": "product-photo",
"title": "Product Photo",
"fields": {
"Approval Status": "Approved",
"Season": "Spring/Summer"
}
}
}
Deleting Custom Fields
Remove custom fields when they're no longer needed:
curl -X DELETE "https://api.playbook.com/v1/my-org/custom_fields/field-abc123?access_token=TOKEN"
Note: Deleting a field removes it from all assets.
Use Cases
1. Approval Workflow
Track asset approval status:
// Create approval workflow field
const approvalField = await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/custom_fields?access_token=${ACCESS_TOKEN}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
field: {
name: 'Approval Status',
options: ['Draft', 'Pending Review', 'Approved', 'Rejected', 'Needs Revision']
}
})
}
).then(r => r.json());
// Move asset through workflow
async function updateApprovalStatus(assetToken, status) {
return await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/assets/${assetToken}?access_token=${ACCESS_TOKEN}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
asset: {
fields: {
'Approval Status': status
}
}
})
}
).then(r => r.json());
}
2. Rights Management
Track usage rights and licenses:
curl -X POST "https://api.playbook.com/v1/my-org/custom_fields?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"field": {
"name": "Usage Rights",
"options": [
"Full Rights",
"Editorial Only",
"Web Only",
"Print Only",
"Limited License",
"Expired"
]
}
}'
3. Project Organization
Organize assets by project or campaign:
curl -X POST "https://api.playbook.com/v1/my-org/custom_fields?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"field": {
"name": "Project",
"options": [
"Q4 Campaign",
"Website Redesign",
"Mobile App",
"Social Media",
"Print Catalog"
]
}
}'
4. Asset Attributes
Track specific characteristics:
// Color palette field
await createCustomField('Color Palette', [
'Warm Tones',
'Cool Tones',
'Monochrome',
'Vibrant',
'Pastel'
]);
// Style field
await createCustomField('Style', [
'Minimalist',
'Modern',
'Vintage',
'Industrial',
'Rustic'
]);
// Orientation field
await createCustomField('Orientation', [
'Landscape',
'Portrait',
'Square'
]);
Searching by Custom Fields
Retrieve assets filtered by custom field values:
# Get all approved assets
curl "https://api.playbook.com/v1/my-org/search?access_token=TOKEN&query=&filters[fields][Approval Status]=Approved"
Note: The search endpoint filters by custom field names and values. Combine with other filters for precise results.
Complete Implementation Example
Here's a full custom fields management system:
class CustomFieldsManager {
constructor(orgSlug, accessToken) {
this.orgSlug = orgSlug;
this.accessToken = accessToken;
this.baseUrl = 'https://api.playbook.com/v1';
}
async createField(name, options) {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/custom_fields?access_token=${this.accessToken}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
field: { name, options }
})
}
);
if (!response.ok) {
throw new Error(`Failed to create field: ${response.statusText}`);
}
return await response.json();
}
async listFields() {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/custom_fields?access_token=${this.accessToken}`
);
if (!response.ok) {
throw new Error(`Failed to list fields: ${response.statusText}`);
}
return await response.json();
}
async deleteField(fieldToken) {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/custom_fields/${fieldToken}?access_token=${this.accessToken}`,
{ method: 'DELETE' }
);
if (!response.ok) {
throw new Error(`Failed to delete field: ${response.statusText}`);
}
return response.status === 204;
}
async updateAssetFields(assetToken, fields) {
const response = await fetch(
`${this.baseUrl}/${this.orgSlug}/assets/${assetToken}?access_token=${this.accessToken}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
asset: { fields }
})
}
);
if (!response.ok) {
throw new Error(`Failed to update asset fields: ${response.statusText}`);
}
return await response.json();
}
async setupWorkflow() {
// Create multiple related fields
const approvalStatus = await this.createField('Approval Status', [
'Draft',
'In Review',
'Approved',
'Rejected'
]);
const priority = await this.createField('Priority', [
'Low',
'Medium',
'High',
'Urgent'
]);
const project = await this.createField('Project', [
'Q4 Campaign',
'Website',
'Mobile App',
'Print'
]);
return { approvalStatus, priority, project };
}
}
// Usage
const manager = new CustomFieldsManager('my-org', 'your_token');
// Setup fields
await manager.setupWorkflow();
// Update an asset
await manager.updateAssetFields('asset-token-123', {
'Approval Status': 'Approved',
'Priority': 'High',
'Project': 'Q4 Campaign'
});
// List all fields
const fields = await manager.listFields();
console.log('Available fields:', fields.data);
Best Practices
1. Plan Your Field Structure
Before creating fields, consider:
- What workflows do you need to support?
- What metadata is actually useful?
- How will users interact with these fields?
Example planning:
const fieldPlan = {
'Approval Status': {
purpose: 'Track review workflow',
options: ['Draft', 'In Review', 'Approved', 'Rejected'],
requiredFor: ['client-facing assets']
},
'Usage Rights': {
purpose: 'Manage licensing',
options: ['Full Rights', 'Editorial Only', 'Licensed'],
requiredFor: ['all external assets']
},
'Project': {
purpose: 'Organize by campaign',
options: ['Q4 Campaign', 'Website', 'Social'],
requiredFor: ['campaign assets']
}
};
2. Keep Options Manageable
Good:
// Clear, distinct options
['Draft', 'Review', 'Approved', 'Archived']
Avoid:
// Too many options
['Draft 1', 'Draft 2', 'Draft 3', 'Review 1', 'Review 2'...]
// Use tags or version control instead
3. Standardize Naming
Consistent:
'Approval Status' // Title Case
'Usage Rights' // Title Case
'Project' // Title Case
Inconsistent:
'approval_status' // snake_case
'USAGE-RIGHTS' // SCREAMING-KEBAB
'project' // lowercase
4. Document Field Usage
const fieldDocumentation = {
'Approval Status': {
description: 'Current approval state of the asset',
options: {
'Draft': 'Asset is work in progress',
'In Review': 'Submitted for approval',
'Approved': 'Ready for use',
'Rejected': 'Needs revision'
},
workflow: 'Draft → In Review → Approved/Rejected'
}
};
Advanced Patterns
Batch Field Assignment
Update multiple assets at once:
async function batchUpdateFields(assetTokens, fields) {
const updates = assetTokens.map(token =>
fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/assets/${token}?access_token=${ACCESS_TOKEN}`,
{
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ asset: { fields } })
}
)
);
return await Promise.all(updates);
}
// Usage
await batchUpdateFields(
['asset-1', 'asset-2', 'asset-3'],
{ 'Approval Status': 'Approved', 'Project': 'Q4 Campaign' }
);
Field-Based Automation
Create rules based on field values:
async function autoTagByFields(asset) {
const { fields, token } = asset;
// Auto-tag based on field values
const autoTags = [];
if (fields['Approval Status'] === 'Approved') {
autoTags.push('ready-to-use');
}
if (fields['Priority'] === 'Urgent') {
autoTags.push('high-priority');
}
if (fields['Usage Rights'] === 'Full Rights') {
autoTags.push('unrestricted');
}
// Update asset with auto-generated tags
if (autoTags.length > 0) {
await fetch(
`https://api.playbook.com/v1/${ORG_SLUG}/assets/${token}/change_tags?access_token=${ACCESS_TOKEN}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([...asset.tags, ...autoTags])
}
);
}
}
Field Validation
Validate field values before assignment:
class FieldValidator {
constructor(fields) {
this.fieldDefinitions = fields;
}
validate(fieldName, value) {
const field = this.fieldDefinitions.find(f => f.name === fieldName);
if (!field) {
throw new Error(`Field "${fieldName}" does not exist`);
}
const validOptions = field.options.map(o => o.name);
if (!validOptions.includes(value)) {
throw new Error(
`Invalid value "${value}" for field "${fieldName}". ` +
`Valid options: ${validOptions.join(', ')}`
);
}
return true;
}
validateAll(fields) {
const errors = [];
for (const [name, value] of Object.entries(fields)) {
try {
this.validate(name, value);
} catch (error) {
errors.push(error.message);
}
}
if (errors.length > 0) {
throw new Error(`Validation errors:\n${errors.join('\n')}`);
}
return true;
}
}
// Usage
const fields = await manager.listFields();
const validator = new FieldValidator(fields.data);
try {
validator.validateAll({
'Approval Status': 'Approved',
'Priority': 'High'
});
// Proceed with update
} catch (error) {
console.error('Invalid fields:', error.message);
}
Error Handling
Common Errors
422: Options are empty
{
"error": "Options cannot be empty"
}
Solution: Provide at least one option when creating a field.
404: Field not found
{
"error": "Custom field not found"
}
Solution: Verify the field token is correct.
422: Invalid field value
{
"error": "Invalid option for field"
}
Solution: Ensure the value matches one of the field's defined options exactly.
Related API Endpoints
Next Steps
- Learn about Asset Management to organize assets with custom fields
- Explore Webhooks to automate actions based on field changes
- Read about Search to filter assets by custom fields