Overview
The Postiz Calendar is your command center for managing all scheduled social media content. It provides a visual, interactive interface for viewing, creating, editing, and rescheduling posts across all your connected channels.
Calendar Views
Postiz offers multiple calendar views to help you manage your content effectively:
Week View
Day View
Month View
The default view shows a full week with hourly time slots. Perfect for detailed planning and visualizing your posting schedule. // Week view displays 7 days with hourly rows
const currentWeek = dayjs . utc ( startDate );
const days = Array . from ({ length: 7 }, ( _ , i ) =>
currentWeek . add ( i , 'day' )
);
Focus on a single day with detailed hourly breakdown. Ideal for managing high-volume posting days. // Day view shows 24 hours with time slot groupings
const options = sortBy (
groupBy ( posts , ( p ) => p . time ),
( p ) => p [ 0 ]. time
);
See your entire month at a glance. Great for long-term content planning. Month view shows post counts per day. Click any day to see detailed posts.
Calendar Interface Components
The left sidebar displays all your connected social media channels:
interface Integration {
id : string ;
name : string ; // Channel name
identifier : string ; // Platform type: 'x', 'linkedin', 'facebook'
picture : string ; // Profile picture
disabled : boolean ; // Channel status
inBetweenSteps : boolean ; // Setup incomplete
refreshNeeded : boolean ; // Token expired
time : Array <{ time : number }>; // Time slots
}
Channel Status Indicators
Active - Channel is connected and ready
Disabled - Channel is temporarily disabled
Setup Incomplete - Orange warning indicator
Refresh Needed - Red indicator, click to reconnect
{( integration . inBetweenSteps || integration . refreshNeeded ) && (
< div className = "bg-red-500 w-[15px] h-[15px] rounded-full" >
!
</ div >
)}
Main Calendar Grid
The calendar grid displays posts organized by date and time:
Time Rows
Each row represents a time slot (hour in week view, time slot in day view). // Hours are generated for 24-hour display
export const hours = Array . from ({ length: 24 }, ( _ , i ) => i );
// Convert to local format (12h or 24h)
const convertTimeFormatBasedOnLocality = ( time : number ) => {
if ( isUSCitizen ()) {
return ` ${ time === 12 ? 12 : time % 12 } :00 ${ time >= 12 ? 'PM' : 'AM' } ` ;
}
return ` ${ time } :00` ;
};
Post Cards
Posts appear as cards in the calendar grid with preview content. Scheduled Posts
Post States
interface Post {
id : string ;
group : string ; // Groups related posts
content : string ;
publishDate : string ; // ISO date string
state : State ; // DRAFT, SCHEDULED, PUBLISHED, etc.
integration : {
id : string ;
name : string ;
picture : string ;
providerIdentifier : string ;
};
image ?: Array <{
id : string ;
path : string ;
}>;
}
Posts are color-coded by state:
Scheduled - Default color
Publishing - Loading indicator
Published - Success state
Error - Red indicator with retry option
Drag-and-Drop Scheduling
Reschedule posts by dragging them to new time slots:
Setting Up Drag Source
import { useDrag } from 'react-dnd' ;
const [{ isDragging }, drag ] = useDrag (() => ({
type: 'post' ,
item: {
post ,
oldDate: post . publishDate
},
collect : ( monitor ) => ({
isDragging: monitor . isDragging ()
})
}));
Setting Up Drop Target
import { useDrop } from 'react-dnd' ;
const [{ isOver , canDrop }, drop ] = useDrop (() => ({
accept: 'post' ,
drop : async ( item : { post : Post }) => {
// Calculate new date from drop position
const newDate = calculateNewDate ( day , hour );
// Update via API
await fetch ( `/posts/ ${ item . post . id } /date` , {
method: 'PUT' ,
body: JSON . stringify ({
date: newDate ,
action: 'update'
})
});
// Refresh calendar
reloadCalendarView ();
},
collect : ( monitor ) => ({
isOver: monitor . isOver (),
canDrop: monitor . canDrop ()
})
}));
Posts cannot be dragged to past dates or times. The drop zone will indicate if a slot is invalid.
Post Interactions
Right-click or click on any post to access actions:
const usePostActions = () => {
const editPost = useCallback (( post ) => async () => {
// Fetch full post data
const data = await fetch ( `/posts/group/ ${ post . group } ` ). json ();
// Open edit modal
modal . openModal ({
children: < AddEditModal
existingData ={ data }
integrations ={[ integration ]}
date ={ dayjs . utc ( post . publishDate ). local ()}
/>
});
}, []);
const deletePost = useCallback (( post ) => async () => {
if ( ! await deleteDialog ( 'Delete this post?' )) return ;
await fetch ( `/posts/ ${ post . group } ` , {
method: 'DELETE'
});
mutate ();
}, []);
const duplicatePost = useCallback (( post ) => async () => {
// Load post data
const data = await fetch ( `/posts/group/ ${ post . group } ` ). json ();
// Find next available slot
const { date } = await fetch ( '/posts/find-slot' ). json ();
// Open with pre-filled content but new date
modal . openModal ({
children: < AddEditModal
onlyValues ={ data . posts }
date ={ dayjs . utc ( date ). local ()}
/>
});
}, []);
const viewStatistics = useCallback (( postId ) => () => {
modal . openModal ({
children: < StatisticsModal postId ={ postId } />
});
}, []);
return { editPost , deletePost , duplicatePost , viewStatistics };
};
Keyboard Shortcuts
Delete - Delete selected post (after confirmation)
Escape - Close modal or deselect post
Arrow Keys - Navigate between posts (coming soon)
Ctrl/Cmd + D - Duplicate selected post (coming soon)
Calendar Navigation
Date Navigation
const CalendarNavigation = () => {
const { startDate , setStartDate } = useCalendar ();
const previousWeek = () => {
setStartDate ( dayjs ( startDate ). subtract ( 7 , 'days' ));
};
const nextWeek = () => {
setStartDate ( dayjs ( startDate ). add ( 7 , 'days' ));
};
const today = () => {
setStartDate ( dayjs (). startOf ( 'week' ));
};
return (
< div className = "flex gap-2" >
< button onClick = { previousWeek } > ← Previous </ button >
< button onClick = { today } > Today </ button >
< button onClick = { nextWeek } > Next → </ button >
</ div >
);
};
Quick Date Picker
Click the date header to open a date picker for quick navigation:
< DatePicker
value = { startDate }
onChange = {(date) => setStartDate ( date )}
minDate = { dayjs (). subtract (1, 'year' )}
maxDate = { dayjs (). add (1, 'year' )}
/>
Filtering and Searching
Filter the calendar view by various criteria:
Channel Filter
const [ selectedChannels , setSelectedChannels ] = useState < string []>([]);
// Filter posts by selected channels
const filteredPosts = posts . filter ( post =>
selectedChannels . length === 0 ||
selectedChannels . includes ( post . integration . id )
);
Tag Filter
// GET /posts/tags - Fetch available tags
const { tags } = await fetch ( '/posts/tags' ). json ();
// Filter by tags
const filteredByTag = posts . filter ( post =>
post . tags ?. some ( tag => selectedTags . includes ( tag . id ))
);
State Filter
All States
Scheduled Only
Published Only
Failed Only
Show all posts regardless of state
const scheduledPosts = posts . filter ( p =>
p . state === State . SCHEDULED
);
const publishedPosts = posts . filter ( p =>
p . state === State . PUBLISHED
);
const failedPosts = posts . filter ( p =>
p . state === State . ERROR
);
Real-time Updates
The calendar automatically refreshes to show the latest post states:
// Polling interval for calendar updates
const { interval } = useInterval (() => {
reloadCalendarView ();
}, 30000 ); // Refresh every 30 seconds
// Manual refresh
const refreshCalendar = useCallback (() => {
mutate (); // SWR revalidate
}, []);
The calendar polls for updates every 30 seconds when the tab is active. This ensures you always see the latest post states without manual refreshing.
Channel Management in Calendar
Adding Channels
const addChannel = () => {
modal . openModal ({
title: 'Add Channel' ,
children: < AddProviderButton onSuccess ={ mutate } />
});
};
Organizing Channels
Drag channels to reorder them in the sidebar:
const [{ isDragging }, drag ] = useDrag ({
type: 'menu' ,
item: { id: integration . id }
});
const [{ isOver }, drop ] = useDrop ({
accept: 'menu' ,
drop : ( item : { id : string }) => {
changeItemGroup ( item . id , group . id );
}
});
Calendar Context API
The calendar uses React Context for state management:
interface CalendarContextType {
startDate : string ;
setStartDate : ( date : string ) => void ;
posts : Post [];
integrations : Integration [];
reloadCalendarView : () => void ;
loading : boolean ;
}
const CalendarContext = createContext < CalendarContextType >();
export const useCalendar = () => {
const context = useContext ( CalendarContext );
if ( ! context ) {
throw new Error ( 'useCalendar must be used within CalendarProvider' );
}
return context ;
};
Calendar Optimization Techniques
Virtualization - Only render visible time slots
Memoization - Cache post calculations
Debounced Drag - Reduce update frequency during dragging
Lazy Loading - Load posts only for visible date range
const visiblePosts = useMemo (() => {
return posts . filter ( post => {
const date = dayjs ( post . publishDate );
return date . isAfter ( startDate ) &&
date . isBefore ( endDate );
});
}, [ posts , startDate , endDate ]);
Best Practices
Use Week View for Planning Week view gives you the best overview for content planning and scheduling.
Organize Channels by Priority Drag channels to reorder them with most important channels at the top.
Set Up Time Slots Configure time slots for each channel to enable smart auto-scheduling.
Use Filters Effectively Filter by channel or state to focus on specific content.
Next Steps
Post Scheduling Learn how to create and schedule posts
Analytics Track performance of your scheduled posts