Overview
This guide walks you through the complete workflow of managing content in Haystack, from creating your organizational structure to publishing items.
Prerequisites
Before you begin, ensure you have:
- A Haystack account with API access
- Your API token (see Authentication)
- At least one collection created
Step 1: Set Up Your Organization
Create Collections
Collections are the top-level categories for your content. Create them first:
const collections = [
{ name: 'Sunday Sermons' },
{ name: 'Midweek Studies' },
{ name: 'Podcasts' }
];
for (const collection of collections) {
const response = await fetch(`${API_URL}/collections/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(collection)
});
const { collection: created } = await response.json();
console.log(`Created collection: ${created.name} (ID: ${created.id})`);
}
Create Speakers
Add the people who deliver your content:
const speakers = [
{
name: 'Pastor John Smith',
bio: 'Senior Pastor since 2015, passionate about biblical teaching and discipleship.'
},
{
name: 'Sarah Johnson',
bio: 'Youth Pastor and conference speaker focusing on student ministry.'
}
];
for (const speaker of speakers) {
const response = await fetch(`${API_URL}/speakers/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(speaker)
});
const { speaker: created } = await response.json();
console.log(`Created speaker: ${created.name} (ID: ${created.id})`);
}
Create Topics
Define the topics/tags you’ll use to categorize content:
const topics = [
{ name: 'Prayer', displayOrder: 1 },
{ name: 'Faith', displayOrder: 2 },
{ name: 'Leadership', displayOrder: 3 },
{ name: 'Relationships', displayOrder: 4 },
{ name: 'Worship', displayOrder: 5 }
];
for (const topic of topics) {
const response = await fetch(`${API_URL}/topics/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(topic)
});
const { topic: created } = await response.json();
console.log(`Created topic: ${created.name} (ID: ${created.id})`);
}
Step 2: Create a Series (Optional)
For multi-part content, create a series:
const seriesData = {
title: 'Spiritual Disciplines',
subTitle: 'A 4-Week Journey',
description: 'Explore the foundational practices of prayer, fasting, worship, and scripture study that deepen your relationship with God.',
shortDescription: 'Master the spiritual disciplines',
collectionId: 1, // Sunday Sermons
colorHex: '#4A90E2',
itemSortDirection: 'ASC', // Show oldest first
showItemOrderInSeries: true, // Show "Part 1", "Part 2", etc.
published: true
};
const response = await fetch(`${API_URL}/series/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(seriesData)
});
const { series } = await response.json();
console.log(`Created series: ${series.title} (ID: ${series.id})`);
Upload Series Artwork
Add visual branding to your series:
const formData = new FormData();
formData.append('file', seriesArtworkFile); // Your image file
formData.append('artworkType', 'square'); // or 'wide'
const response = await fetch(
`${API_URL}/series/${series.id}/artwork/upload`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`
},
body: formData
}
);
Artwork requirements:
- Square: 1:1 aspect ratio, minimum 1000x1000px
- Wide: 16:9 aspect ratio, minimum 1920x1080px
- Max file size: 15MB
- Formats: JPG, PNG
Step 3: Create an Item
Now create your first content item:
const itemData = {
// Basic information
title: 'The Power of Prayer',
subTitle: 'Part 1 of Spiritual Disciplines',
description: 'Discover how prayer transforms not just our circumstances, but our hearts. Learn practical ways to develop a consistent prayer life and experience deeper intimacy with God.',
shortDescription: 'Transform your prayer life',
date: '2025-01-15',
// Required: Collection
collectionId: 1,
// Optional: Series
seriesId: series.id,
// Optional: Speakers (array of IDs)
speakers: [
{ id: 1 } // Pastor John Smith
],
// Optional: Topics (array of IDs)
topics: [
{ id: 1 }, // Prayer
{ id: 2 } // Faith
],
// Publishing (initially draft)
published: false
};
const response = await fetch(`${API_URL}/items/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(itemData)
});
const { item } = await response.json();
console.log(`Created item: ${item.title} (ID: ${item.id})`);
Step 4: Add Item Artwork
Upload thumbnail images for the item:
// Upload square artwork (for grids)
const squareFormData = new FormData();
squareFormData.append('file', squareArtworkFile);
squareFormData.append('artworkType', 'square');
await fetch(`${API_URL}/items/${item.id}/artwork/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`
},
body: squareFormData
});
// Upload wide artwork (for hero images)
const wideFormData = new FormData();
wideFormData.append('file', wideArtworkFile);
wideFormData.append('artworkType', 'wide');
await fetch(`${API_URL}/items/${item.id}/artwork/upload`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`
},
body: wideFormData
});
console.log('Artwork uploaded successfully');
Step 5: Add Scriptures (Optional)
Link Bible passages to your item:
const scriptures = [
{
itemId: item.id,
book: 'matthew',
chapter: 6,
verseStart: 9,
verseEnd: 13
},
{
itemId: item.id,
book: '1_thessalonians',
chapter: 5,
verseStart: 17,
verseEnd: 17
}
];
for (const scripture of scriptures) {
await fetch(`${API_URL}/scriptures/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(scripture)
});
}
console.log('Scriptures added');
Step 6: Publish the Item
Once everything is ready, publish the item:
const response = await fetch(`${API_URL}/items/${item.id}/publish`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
});
const { item: publishedItem } = await response.json();
console.log(`Published: ${publishedItem.title}`);
Items must have at least one media asset attached before they can be published. See the Working with Media guide to learn how to upload media.
Updating Content
Modify an existing item:
const updates = {
title: 'The Transforming Power of Prayer', // Updated title
description: 'Updated description...',
topics: [
{ id: 1 }, // Prayer
{ id: 5 } // Worship (added)
]
};
const response = await fetch(`${API_URL}/items/${item.id}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
});
const { item: updatedItem } = await response.json();
Unpublish an Item
Take an item offline:
await fetch(`${API_URL}/items/${item.id}/unpublish`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
});
Bulk Operations
Creating Multiple Items
When importing a library, create items in batches:
const items = [
{ title: 'Item 1', date: '2025-01-01', collectionId: 1 },
{ title: 'Item 2', date: '2025-01-08', collectionId: 1 },
{ title: 'Item 3', date: '2025-01-15', collectionId: 1 }
];
// Create items sequentially
for (const itemData of items) {
const response = await fetch(`${API_URL}/items/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(itemData)
});
const { item } = await response.json();
console.log(`Created: ${item.title} (ID: ${item.id})`);
// Small delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 100));
}
For large bulk imports (>100 items), consider batching your requests and implementing retry logic for failed imports.
Querying Content
List Items
Retrieve items with filtering and pagination:
// Get published items from a specific series
const response = await fetch(
`${API_URL}/items?seriesId=5&published=true&sortBy=date&sortDirection=DESC&page=1&pageSize=20`,
{
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
}
);
const data = await response.json();
console.log(`Found ${data.total} items`);
console.log(`Showing page ${data.page} of ${Math.ceil(data.total / data.pageSize)}`);
data.items.forEach(item => {
console.log(`- ${item.title} (${item.date})`);
});
Fetch an item with all related data:
const response = await fetch(
`${API_URL}/items/42?_expand=collection&_expand=series&_expand=speakers&_expand=mediaAssets`,
{
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
}
);
const { item } = await response.json();
// Now item includes nested objects
console.log(item.collection.name); // "Sunday Sermons"
console.log(item.series.title); // "Spiritual Disciplines"
console.log(item.speakers[0].name); // "Pastor John Smith"
Common Patterns
Checking if Item Exists
async function itemExists(title, date, collectionId) {
const response = await fetch(
`${API_URL}/items?title=${encodeURIComponent(title)}&date=${date}&collectionId=${collectionId}`,
{
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
}
);
const data = await response.json();
return data.items.length > 0;
}
Finding Items by Speaker
async function getItemsBySpeaker(speakerId) {
const response = await fetch(
`${API_URL}/items?speakerId=${speakerId}&_expand=speakers`,
{
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
}
);
const data = await response.json();
return data.items;
}
Getting Latest Items
async function getLatestItems(limit = 10) {
const response = await fetch(
`${API_URL}/items?published=true&sortBy=date&sortDirection=DESC&pageSize=${limit}`,
{
headers: {
'Authorization': `Bearer ${API_TOKEN}`
}
}
);
const data = await response.json();
return data.items;
}
Best Practices
Plan Your Collection Structure
Design your collections before importing content. Changing an item’s collection later requires updating the item, which can affect analytics.
Maintain consistent naming conventions for speakers and topics to avoid duplicates.
- ✅ “John Smith” (everywhere)
- ❌ “John Smith”, “Pastor John”, “Rev. Smith” (creates duplicates)
Keep Items in Draft While Building
Create items as drafts, add all metadata and media, then publish when complete.
Use Series for Related Content
Group related items into series for better discovery and user engagement.
Add Comprehensive Metadata
Rich metadata (descriptions, topics, scriptures) improves search quality and user experience.
Error Handling
Always handle errors gracefully:
async function createItemSafely(itemData) {
try {
const response = await fetch(`${API_URL}/items/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(itemData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
const { item } = await response.json();
return item;
} catch (error) {
console.error('Failed to create item:', error.message);
throw error;
}
}
Next Steps