- π Production Ready: Rate limiting, middleware support, battle-tested
- π Comprehensive: 45 Canvas APIs fully implemented
- π‘οΈ Type Safe: Full PHP 8.1+ type declarations, strict_types, and PHPStan level 8
- π§ Developer Friendly: Intuitive Active Record pattern - just pass arrays!
- π Well Documented: Extensive examples, guides, and API reference
- β‘ Performance: Built-in pagination, caching support, and optimized queries
- π― Code Quality: PHP-CS-Fixer integration, PSR-12 compliance
use CanvasLMS\Config;
use CanvasLMS\Api\Courses\Course;
Config::setApiKey('your-api-key');
Config::setBaseUrl('https://canvas.instructure.com');
// It's that simple!
$courses = Course::get(); // Get first page
foreach ($courses as $course) {
echo $course->name . "\n";
}- Requirements
- Installation
- Configuration
- Usage Examples
- Supported APIs
- Advanced Features
- Testing
- Contributing
- Support
- PHP 8.1 or higher
- Composer
- Canvas LMS API token
- Extensions:
json,curl,mbstring
composer require jjuanrivvera/canvas-lms-kituse CanvasLMS\Config;
// Basic configuration
Config::setApiKey('your-api-key');
Config::setBaseUrl('https://canvas.instructure.com');
// Optional: Set account ID for scoped operations
Config::setAccountId(1);
// Optional: Configure middleware
Config::setMiddleware([
'retry' => ['max_attempts' => 3],
'rate_limit' => ['wait_on_limit' => true],
]);Canvas LMS Kit now supports OAuth 2.0 for user-specific authentication with automatic token refresh!
use CanvasLMS\Config;
use CanvasLMS\Auth\OAuth;
// Configure OAuth credentials
Config::setOAuthClientId('your-client-id');
Config::setOAuthClientSecret('your-client-secret');
Config::setOAuthRedirectUri('https://yourapp.com/oauth/callback');
Config::setBaseUrl('https://canvas.instructure.com');
// Step 1: Get authorization URL
$authUrl = OAuth::getAuthorizationUrl(['state' => 'random-state']);
// Redirect user to $authUrl
// Step 2: Handle callback
$tokenData = OAuth::exchangeCode($_GET['code']);
// Step 3: Use OAuth mode
Config::useOAuth();
// Now all API calls use OAuth with automatic token refresh!
$courses = Course::get(); // User's courses (first page)Perfect for containerized deployments and 12-factor apps:
# .env file
CANVAS_BASE_URL=https://canvas.instructure.com
CANVAS_API_KEY=your-api-key
# OR for OAuth:
CANVAS_OAUTH_CLIENT_ID=your-client-id
CANVAS_OAUTH_CLIENT_SECRET=your-client-secret
CANVAS_AUTH_MODE=oauth// Auto-detect from environment
Config::autoDetect();
// Ready to use!The SDK supports any PSR-3 compatible logger for comprehensive debugging and monitoring:
use CanvasLMS\Config;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Configure with Monolog
$logger = new Logger('canvas-lms');
$logger->pushHandler(new StreamHandler('logs/canvas.log', Logger::INFO));
Config::setLogger($logger);
// All API calls, OAuth operations, pagination, and file uploads are now logged!use Psr\Log\LoggerInterface;
public function __construct(LoggerInterface $logger) {
Config::setLogger($logger);
}// Enable detailed request/response logging
Config::setMiddleware([
'logging' => [
'enabled' => true,
'log_requests' => true,
'log_responses' => true,
'log_errors' => true,
'log_timing' => true,
'log_level' => \Psr\Log\LogLevel::DEBUG,
'sanitize_fields' => ['password', 'token', 'api_key', 'secret'],
'max_body_length' => 1000
]
]);What Gets Logged:
- β All API requests and responses (with sensitive data sanitization)
- β OAuth token operations (refresh, revoke, exchange)
- β Pagination operations with performance metrics
- β File upload progress (3-step process)
- β Rate limit headers and API costs
- β Error conditions with context
Security: The logging middleware automatically sanitizes sensitive fields like passwords, tokens, and API keys to prevent accidental exposure in logs.
The SDK supports Canvas's masquerading functionality, allowing administrators to perform API operations on behalf of other users. This is essential for administrative operations, support workflows, and testing.
Enable masquerading globally for all subsequent API calls:
use CanvasLMS\Config;
use CanvasLMS\Api\Courses\Course;
use CanvasLMS\Api\Users\User;
// Enable masquerading as user 12345
Config::asUser(12345);
// All API calls will now include as_user_id=12345
$course = Course::find(123); // Performed as user 12345
$assignments = $course->assignments(); // Also performed as user 12345
$enrollments = User::find(456)->enrollments(); // Also as user 12345
// Stop masquerading
Config::stopMasquerading();
// Subsequent calls are performed as the authenticated user
$normalCourse = Course::find(789); // No masqueradingIn multi-tenant environments, masquerading can be set per context:
// Set masquerading for production context only
Config::setContext('production');
Config::asUser(12345, 'production');
// Only affects production context operations
Config::setContext('staging');
$stagingUser = User::find(1); // Not masqueraded
Config::setContext('production');
$prodUser = User::find(1); // Masqueraded as user 12345- Permissions Required: The authenticated user must have appropriate Canvas permissions to masquerade
- Error Handling: Canvas will return 401/403 errors if permissions are insufficient
- Audit Trail: Consider logging when masquerading is active for compliance
// Check if masquerading is active
if (Config::isMasquerading()) {
$masqueradeUserId = Config::getMasqueradeUserId();
$logger->info("Performing operation as user {$masqueradeUserId}");
}Support Operations:
// Support agent helping a student
Config::asUser($studentId);
Assignment::setCourse(Course::find($courseId));
$submission = Assignment::find($assignmentId)->submissionForUser($studentId);
Config::stopMasquerading();Testing Permission-Based Features:
// Test different user roles
foreach ($testUsers as $userId => $role) {
Config::asUser($userId);
$visibleCourses = Course::get(); // Courses visible to this user
echo "User {$userId} ({$role}): sees " . count($visibleCourses) . " courses\n";
}
Config::stopMasquerading();Batch Operations for Multiple Users:
// Process enrollments for multiple users
$userIds = [123, 456, 789];
foreach ($userIds as $userId) {
Config::asUser($userId);
$enrollment = Enrollment::create([
'user_id' => $userId,
'course_id' => $courseId,
'enrollment_state' => 'active'
]);
echo "Enrolled user {$userId} successfully\n";
}
Config::stopMasquerading();Canvas LMS Kit provides three simple, consistent methods for handling paginated data across all resources:
// 1. get() - Fetch first page only (default: 10 items)
$courses = Course::get(); // First 10 courses
$courses = Course::get(['per_page' => 50]); // First 50 courses
// 2. paginate() - Get results with pagination metadata
$result = Course::paginate(['per_page' => 25]);
echo "Page {$result->getCurrentPage()} of {$result->getTotalPages()}";
echo "Courses on this page: {$result->getCount()}";
// 3. all() - Fetch ALL items from all pages automatically
$allCourses = Course::all(); // β οΈ Use with caution on large datasets!| Method | Use Case | Memory Usage | API Calls |
|---|---|---|---|
get() |
Dashboards, quick views, limited displays | Low (1 page) | 1 |
paginate() |
UI tables, batch processing, when you need metadata | Low (1 page) | 1 per page |
all() |
Complete data export, small datasets (< 1000 items) | High (all data) | As many as needed |
// β WRONG - May exhaust memory with large datasets
$allUsers = User::all(); // Could be 50,000+ users!
foreach ($allUsers as $user) {
processUser($user);
}
// β
BEST - stream() lazily yields one object at a time across all pages
foreach (User::stream(['per_page' => 100]) as $user) {
processUser($user);
}
// β
ALSO CORRECT - Process in batches using paginate()
$page = 1;
do {
$batch = User::paginate(['page' => $page++, 'per_page' => 100]);
foreach ($batch->getData() as $user) {
processUser($user);
}
// Optional: Add delay to respect rate limits
if ($batch->hasNext()) {
sleep(1);
}
} while ($batch->hasNext());// In your controller
$page = $_GET['page'] ?? 1;
$result = Course::paginate(['page' => $page, 'per_page' => 25]);
// In your view
foreach ($result->getData() as $course) {
echo "<tr><td>{$course->name}</td></tr>";
}
// Pagination controls
if ($result->hasPrev()) {
echo "<a href='?page=" . ($page - 1) . "'>Previous</a>";
}
if ($result->hasNext()) {
echo "<a href='?page=" . ($page + 1) . "'>Next</a>";
}
echo "Page {$result->getCurrentPage()} of {$result->getTotalPages()}";// For small datasets (< 1000 items)
Assignment::setCourse(Course::find(123));
$assignments = Assignment::all();
exportToCSV($assignments);
// For large datasets - stream to file
$csvFile = fopen('enrollments.csv', 'w');
$page = 1;
do {
$batch = Enrollment::paginate(['page' => $page++, 'per_page' => 500]);
foreach ($batch->getData() as $enrollment) {
fputcsv($csvFile, [
$enrollment->userId,
$enrollment->courseId,
$enrollment->enrollmentState
]);
}
} while ($batch->hasNext());
fclose($csvFile);// When you need just a subset
Assignment::setCourse(Course::find(123));
$recentAssignments = Assignment::get([
'per_page' => 10,
'order_by' => 'due_at'
]);
// When searching through all pages
$found = false;
$page = 1;
do {
$batch = User::paginate([
'page' => $page++,
'search_term' => 'john.doe@example.com'
]);
foreach ($batch->getData() as $user) {
if ($user->email === 'john.doe@example.com') {
$found = $user;
break 2; // Exit both loops
}
}
} while ($batch->hasNext() && !$found);The paginate() method returns a PaginationResult object with helpful methods:
$result = Course::paginate(['per_page' => 20]);
// Data access
$courses = $result->getData(); // Array of Course objects
$count = $result->getCount(); // Number of items on this page
$result->isEmpty(); // true if the page has no items
// Navigation
$result->hasNext(); // true/false
$result->hasPrev(); // true/false
$result->isFirstPage(); // true/false
$result->isLastPage(); // true/false
// Page information
$result->getCurrentPage(); // Current page number
$result->getTotalPages(); // Total number of pages
$result->getPerPage(); // Items per page
// URL access (for custom implementations)
$result->getNextUrl(); // Next page URL
$result->getPrevUrl(); // Previous page URL// π Small datasets (< 100 items): Safe to use all()
// Course-scoped resources require course context first
$course = Course::find(123);
Module::setCourse($course);
Section::setCourse($course);
$modules = Module::all();
$sections = Section::all();
// π Medium datasets (100-1000 items): Consider your use case
Assignment::setCourse($course);
$assignments = Assignment::get(['per_page' => 100]); // If you need subset
$assignments = Assignment::paginate(['per_page' => 100]); // If processing batches
$assignments = Assignment::all(); // If you need everything
// π Large datasets (1000+ items): Use paginate() for memory efficiency
$users = User::paginate(['per_page' => 100]);
$enrollments = Enrollment::paginate(['per_page' => 500]);
// Note: The SDK includes built-in rate limiting and retry logic,
// so fetching all pages is safe from an API perspective.
// The main consideration is memory usage for very large datasets.Important: Relationship methods on Course/User/Group instances return ALL pages for completeness:
$course = Course::find(123);
$modules = $course->modules(); // Returns ALL modules (all pages)
$enrollments = $course->enrollments(); // Returns ALL enrollments (could be thousands!)
// For large datasets, consider using pagination directly:
Enrollment::setCourse($course);
$paginatedEnrollments = Enrollment::paginate(['per_page' => 100]); // Memory efficient
// Or use paginate for control:
$paginatedModules = Module::paginate(['per_page' => 50]);π View Complete Pagination Guide for advanced examples and patterns.
use CanvasLMS\Api\Courses\Course;
// Get first page of courses (memory efficient)
$courses = Course::get();
// Get ALL courses across all pages automatically
// β οΈ Warning: Be cautious with large datasets (1000+ items)
$allCourses = Course::all();
// Get paginated results with metadata (recommended for large datasets)
$paginated = Course::paginate(['per_page' => 50]);
echo "Page {$paginated->getCurrentPage()} of {$paginated->getTotalPages()}";
// Find a specific course
$course = Course::find(123);
// Create a new course - just pass an array!
$course = Course::create([
'name' => 'Introduction to PHP',
'course_code' => 'PHP101',
'start_at' => '2025-02-01T00:00:00Z'
]);
// Update a course
$course->update([
'name' => 'Advanced PHP Programming'
]);
// Save changes with fluent interface support
$course->name = 'Updated Course Name';
$course->save()->enrollments(); // Save and immediately get enrollments
// Delete a course (also returns self for chaining)
$course->delete();use CanvasLMS\Api\Assignments\Assignment;
use CanvasLMS\Api\Submissions\Submission;
use CanvasLMS\Api\Courses\Course;
// Set course context for Assignment
$course = Course::find(123);
Assignment::setCourse($course);
// Create an assignment - simple array syntax
$assignment = Assignment::create([
'name' => 'Final Project',
'description' => 'Build a web application',
'points_possible' => 100,
'due_at' => '2025-03-15T23:59:59Z',
'submission_types' => ['online_upload', 'online_url']
]);
// Grade submissions (requires both Course and Assignment context)
Submission::setCourse($course);
Submission::setAssignment($assignment);
$submission = Submission::update($studentId, [
'posted_grade' => 95,
'comment' => 'Excellent work!'
]);use CanvasLMS\Api\Modules\Module;
use CanvasLMS\Api\Modules\ModuleItem;
// Get course and set context
$course = Course::find(123);
Module::setCourse($course);
// Create a module
$module = Module::create([
'name' => 'Week 1: Introduction',
'position' => 1,
'published' => true
]);
// Add items to the module (requires both course and module context)
ModuleItem::setCourse($course);
ModuleItem::setModule($module);
// Add an assignment to the module
$item = ModuleItem::create([
'title' => 'Week 1 Assignment',
'type' => 'Assignment',
'content_id' => $assignment->id,
'position' => 1,
'completion_requirement' => [
'type' => 'must_submit'
]
]);
// Or use the course instance method (recommended)
$modules = $course->modules();use CanvasLMS\Api\Users\User;
// Get current authenticated user instance
$currentUser = User::self();
// Canvas supports 'self' for these endpoints:
$profile = $currentUser->getProfile();
$activityStream = $currentUser->getActivityStream();
$todos = $currentUser->getTodoItems();
$groups = $currentUser->groups();
// Other methods require explicit user ID
$user = User::find(123);
$enrollments = $user->enrollments();
$courses = $user->courses();use CanvasLMS\Api\Groups\Group;
use CanvasLMS\Api\Groups\GroupMembership;
// Create a group in your account
$group = Group::create([
'name' => 'Study Group Alpha',
'description' => 'Weekly study sessions',
'is_public' => false,
'join_level' => 'invitation_only'
]);
// Add members to the group
$membership = $group->createMembership([
'user_id' => 456,
'workflow_state' => 'accepted'
]);
// Get group activity stream
$activities = $group->activityStream();
// Invite users by email
$group->invite(['student1@example.com', 'student2@example.com']);use CanvasLMS\Api\Files\File;
// Upload a file to a course
$file = File::upload([
'course_id' => 123,
'file_path' => '/path/to/document.pdf',
'name' => 'Course Syllabus.pdf',
'parent_folder_path' => 'course_documents'
]);Pass Canvas's include[]=user option when listing a course's files to get the uploader
alongside each file - handy for tracking down who to contact about a given file:
use CanvasLMS\Api\Courses\Course;
$course = Course::find(123);
// 'include' arrays are normalized to Canvas's include[]=user query form automatically
$files = $course->files(['include' => ['user']]);
foreach ($files as $file) {
$uploader = $file->getUser(); // CanvasLMS\Objects\UserDisplay|null
if ($uploader !== null) {
echo "{$file->getDisplayName()} uploaded by {$uploader->displayName} ({$uploader->htmlUrl})\n";
}
}use CanvasLMS\Api\FeatureFlags\FeatureFlag;
use CanvasLMS\Api\Courses\Course;
// Get feature flags for the account
$flags = FeatureFlag::get(); // First page
$allFlags = FeatureFlag::all(); // All flags
// Get a specific feature flag
$flag = FeatureFlag::find('new_gradebook');
// Enable a feature for the account
$flag->enable();
// Disable a feature
$flag->disable();
// Feature flags for a specific course
$course = Course::find(123);
$courseFlags = $course->featureFlags();
// Enable a feature for a specific course
$courseFlag = $course->getFeatureFlag('anonymous_marking');
$courseFlag->enable();use CanvasLMS\Api\Conversations\Conversation;
// Get conversations for the current user
$conversations = Conversation::get(['scope' => 'unread']); // First page
$allConversations = Conversation::all(['scope' => 'unread']); // All pages
// Create a new conversation
$conversation = Conversation::create([
'recipients' => ['user_123', 'course_456_students'],
'subject' => 'Assignment Feedback',
'body' => 'Great work on your recent submission!',
'group_conversation' => true
]);
// Add a message to existing conversation
$conversation->addMessage([
'body' => 'Following up on my previous message...',
'attachment_ids' => [789] // Attach files
]);
// Manage conversation state
$conversation->markAsRead();
$conversation->star();
$conversation->archive();
// Batch operations
Conversation::batchUpdate([1, 2, 3], ['event' => 'mark_as_read']);
Conversation::markAllAsRead();
// Get unread count
$unreadCount = Conversation::getUnreadCount();Some Canvas resources exist in multiple contexts (Account, Course, User, Group). Canvas LMS Kit follows an Account-as-Default convention for consistency:
use CanvasLMS\Api\Rubrics\Rubric;
use CanvasLMS\Api\ExternalTools\ExternalTool;
use CanvasLMS\Api\CalendarEvents\CalendarEvent;
use CanvasLMS\Api\Files\File;
// Direct calls default to Account context
$rubrics = Rubric::get(); // Account-level rubrics (first page)
$tools = ExternalTool::get(); // Account-level external tools (first page)
$events = CalendarEvent::get(); // Account-level calendar events (first page)
$files = File::get(); // Exception: User files (no account context)
// Course-specific access through Course instance
$course = Course::find(123);
$courseRubrics = $course->rubrics(); // Course-specific rubrics
$courseTools = $course->externalTools(); // Course-specific tools
$courseEvents = $course->calendarEvents(); // Course-specific events
$courseFiles = $course->files(); // Course-specific files
// Direct context access when needed
$userEvents = CalendarEvent::fetchByContext('user', 456);
$groupFiles = File::fetchByContext('groups', 789);Multi-Context Resources:
- Rubrics (Account/Course)
- External Tools (Account/Course)
- Calendar Events (Account/Course/User/Group)
- Files (User/Course/Group) - No Account context
- Groups (Account/Course/User)
- Content Migrations (Account/Course/Group/User)
When working with large Canvas instances (universities, enterprise organizations), be mindful of memory usage:
// β
GOOD: Memory-efficient for large datasets
$page = 1;
do {
$result = User::paginate(['page' => $page++, 'per_page' => 100]);
foreach ($result->getData() as $user) {
// Process batch of 100 users
}
} while ($result->hasNext());
// β
GOOD: Get only what you need
$recentCourses = Course::get(['per_page' => 20]); // Just first 20
// β οΈ CAUTION: Loads entire dataset into memory
$allUsers = User::all(); // Could be 50,000+ users!
$allEnrollments = Enrollment::all(); // Could be millions!
// β οΈ BETTER: Process in batches for large datasets
$page = 1;
do {
$batch = Enrollment::paginate(['page' => $page++, 'per_page' => 500]);
// Process batch
} while ($batch->hasNext());Memory Guidelines:
- Use
get()for dashboards and quick views (1 API call) - Use
paginate()for large datasets and UI tables (controlled memory) - Use
all()only when you need complete data AND know it's reasonably sized - Consider your server's memory limit when using
all()on production data
Some Canvas resources are strictly course-scoped and require setting the course context before use:
use CanvasLMS\Api\Pages\Page;
use CanvasLMS\Api\Quizzes\Quiz;
use CanvasLMS\Api\Modules\Module;
use CanvasLMS\Api\DiscussionTopics\DiscussionTopic;
use CanvasLMS\Api\Courses\Course;
// Get your course
$course = Course::find(123);
// Option 1: Set context for each API (required for direct API calls)
Page::setCourse($course);
Quiz::setCourse($course);
Module::setCourse($course);
DiscussionTopic::setCourse($course);
// Now you can use the APIs directly
$pages = Page::get(); // Get first page
$quizzes = Quiz::all(); // Get all quizzes
$modules = Module::get(); // Get first page
$discussions = DiscussionTopic::all(); // Get all discussions
// Option 2: Use course instance methods (recommended - no context setup needed)
$pages = $course->pages(); // Returns first page only
$quizzes = $course->quizzes(); // Returns first page only
$modules = $course->modules(); // Returns first page only
$discussions = $course->discussionTopics(); // Returns first page onlyImportant Notes:
- These APIs will throw an exception if you try to use them without setting the course context first.
- Relationship methods return FIRST PAGE ONLY for performance. To get all items:
// Get ALL modules for a course Module::setCourse($course); $allModules = Module::all(); // Gets all pages // Or paginate for control $paginated = Module::paginate(['per_page' => 50]);
use CanvasLMS\Api\Outcomes\Outcome;
use CanvasLMS\Api\OutcomeGroups\OutcomeGroup;
// Create an outcome group
$group = OutcomeGroup::create([
'title' => 'Critical Thinking Skills',
'description' => 'Core competencies for analytical thinking'
]);
// Create a learning outcome
$outcome = Outcome::create([
'title' => 'Analyze Complex Problems',
'description' => 'Student can break down complex problems into manageable parts',
'mastery_points' => 3,
'ratings' => [
['description' => 'Exceeds', 'points' => 4],
['description' => 'Mastery', 'points' => 3],
['description' => 'Near Mastery', 'points' => 2],
['description' => 'Below Mastery', 'points' => 1]
]
]);
// Link outcome to a group
$group->linkOutcome($outcome->id);
// Align outcome with an assignment
$assignment = Assignment::find(123);
$assignment->alignOutcome($outcome->id, [
'mastery_score' => 3,
'possible_score' => 4
]);use CanvasLMS\Api\Courses\Course;
use CanvasLMS\Api\ContentMigrations\ContentMigration;
// Copy content between courses
$course = Course::find(456);
$migration = $course->copyContentFrom(123, [
'except' => ['announcements', 'calendar_events']
]);
// Selective copy with specific items
$migration = $course->selectiveCopyFrom(123, [
'assignments' => [1, 2, 3],
'quizzes' => ['quiz-1', 'quiz-2'],
'modules' => [10, 11]
]);
// Import a Common Cartridge file
$migration = $course->importCommonCartridge('/path/to/course.imscc');
// Copy with date shifting
$migration = $course->copyWithDateShift(123, '2024-01-01', '2025-01-01', [
'shift_dates' => true,
'old_start_date' => '2024-01-01',
'new_start_date' => '2025-01-01'
]);
// Track migration progress
while (!$migration->isCompleted()) {
$progress = $migration->getProgress();
echo "Migration {$progress->workflow_state}: {$progress->completion}%\n";
sleep(5);
$migration->refresh();
}
// Handle migration issues
$issues = $migration->migrationIssues();
foreach ($issues as $issue) {
if ($issue->workflow_state === 'active') {
$issue->resolve();
}
}π Core Course Management
- β Courses - Full CRUD operations
- β Modules - Content organization
- β Module Items - Individual content items
- β Sections - Course sections
- β Tabs - Navigation customization
- β Pages - Wiki-style content
π₯ Users & Enrollment
- β Users - User management with self() pattern support
- β Enrollments - Course enrollments
- β Admins - Administrative roles
- β Accounts - Account management
π Assessment & Grading
- β Assignments - Assignment management
- β Quizzes - Quiz creation and management
- β Quiz Submissions - Student attempts
- β Submissions - Assignment submissions
- β Submission Comments - Feedback
- β Rubrics - Grading criteria and assessment
- β Rubric Associations - Link rubrics to assignments
- β Rubric Assessments - Grade with rubrics
- β Outcomes - Learning objectives and competencies
- β Outcome Groups - Organize learning outcomes
- β Outcome Imports - Import outcome data
- β Outcome Results - Track student achievement
π¬ Communication & Collaboration
- β Discussion Topics - Forums and discussions
- β Groups - Student groups and collaboration
- β Group Categories - Organize and manage groups
- β Group Memberships - Group member management
- β Conferences - Web conferencing integration
- β Conversations - Internal messaging system
- β Announcements - Course announcements extending DiscussionTopics
π§ Tools & Integration
- β Files - File management and uploads
- β External Tools - LTI integrations
- β Module Assignment Overrides - Custom dates
- β Calendar Events - Event management
- β Appointment Groups - Scheduling
- β Progress - Async operation tracking
- β Content Migrations - Import/export course content
- β Migration Issues - Handle import problems
- β Feature Flags - Manage Canvas feature toggles
- β Brand Configs - Theme variables and shared brand configurations
- β Gradebook History - Grade change audit trail and submission version tracking
- β Course Reports - Asynchronous report generation (grade exports, student data)
- β Developer Keys - OAuth API key management for Canvas integrations
- β Login API - User authentication credentials and login methods
- β Analytics - Learning analytics data (account, course, user level)
- β Bookmarks - User bookmark management for Canvas resources
- β MediaObjects - Media files and captions/subtitles management
// The SDK automatically handles rate limiting and retries
$course = Course::find(123); // Protected by middleware
// Canvas API Rate Limiting (3000 requests/hour)
// β
Automatic throttling when approaching limits
// β
Smart backoff strategies
// β
Transparent to your application// Manage multiple Canvas instances
Config::setContext('production');
$prodCourse = Course::find(123);
Config::setContext('test');
$testCourse = Course::find(456);// Three simple methods for all your pagination needs:
// 1. get() - Fetch first page only (fast, memory efficient)
$courses = Course::get(['per_page' => 100]);
// 2. all() - Fetch ALL items across all pages automatically
$allCourses = Course::all();
// 3. paginate() - Get results with pagination metadata
$result = Course::paginate(['per_page' => 50]);
echo "Page {$result->getCurrentPage()} of {$result->getTotalPages()}";
echo "Courses on this page: {$result->getCount()}";
// Access the data
foreach ($result->getData() as $course) {
echo $course->name;
}
// Navigate pages
if ($result->hasNext()) {
$nextPage = Course::paginate([
'page' => $result->getCurrentPage() + 1,
'per_page' => 50,
]);
}All save() and delete() methods return the instance, enabling method chaining:
// Save and continue working with the object
$course->name = 'New Name';
$enrollments = $course->save()->enrollments();
// Chain multiple operations
Assignment::setCourse($course);
$assignment = new Assignment();
$assignment->name = 'Final Project';
$assignment->pointsPossible = 100;
$submissions = $assignment->save()->submissions();
// Error handling with fluent interface
try {
$user->email = 'new@example.com';
$user->save()->enrollments(); // Save and get enrollments
} catch (CanvasApiException $e) {
// Handle error - no more silent failures!
}// Efficient relationship loading
$course = Course::find(123);
$students = $course->getStudents();
$assignments = $course->assignments();
$modules = $course->modules();Make direct API calls to arbitrary Canvas URLs using the Canvas facade class:
use CanvasLMS\Canvas;
// Follow pagination URLs returned by Canvas
$courses = Course::paginate();
while ($nextUrl = $courses->getNextUrl()) {
$nextPage = Canvas::get($nextUrl);
// Process next page data
}
// Call custom or undocumented endpoints
$analytics = Canvas::get('/api/v1/courses/123/analytics');
$customData = Canvas::post('/api/v1/custom/endpoint', [
'key' => 'value'
]);
// Download files directly
$file = File::find(456);
$content = Canvas::get($file->url);
file_put_contents('download.pdf', $content);
// Process webhook callbacks
$webhookData = json_decode($request->getContent(), true);
$resource = Canvas::get($webhookData['resource_url']);
// All HTTP methods supported
$response = Canvas::get($url); // GET request
$response = Canvas::post($url, $data); // POST request
$response = Canvas::put($url, $data); // PUT request
$response = Canvas::delete($url); // DELETE request
$response = Canvas::patch($url, $data); // PATCH request
$response = Canvas::request($url, 'HEAD'); // Any HTTP methodFeatures:
- β Automatic authentication (API key or OAuth)
- β Response parsing (JSON/non-JSON)
- β Security validation (domain allowlisting, HTTPS enforcement)
- β Supports absolute URLs and relative paths
- β All middleware applied (rate limiting, retries, logging)
Canvas LMS Kit uses the Account-as-Default convention for multi-context resources:
// Direct API calls use Account context (Config::getAccountId())
$groups = Group::get(); // First page of groups in the account
$allGroups = Group::all(); // All groups in the account
$rubrics = Rubric::get(); // First page of rubrics in the account
$migrations = ContentMigration::all(); // All migrations in the account
// Course-specific access via Course instance methods
$course = Course::find(123);
$courseGroups = $course->groups(); // Groups in this course
$courseRubrics = $course->rubrics(); // Rubrics in this course
$courseMigrations = $course->contentMigrations(); // Migrations in this course
// User-specific access via User instance methods
$user = User::find(456);
$userGroups = $user->groups(); // User's groups
$userMigrations = $user->contentMigrations(); // User's migrations
// Group-specific access via Group instance methods
$group = Group::find(789);
$groupMigrations = $group->contentMigrations(); // Group's migrationsWhy Account-as-Default?
- β Consistency across all multi-context resources
- β Respects Canvas hierarchy (Account β Course β User/Group)
- β Clean separation of concerns
- β No confusion about which context is being used
π Full Context Management Guide
# Using Docker (recommended)
docker compose exec php composer test # Run PHPUnit tests
docker compose exec php composer check # Run all checks (CS, PHPStan, tests)
docker compose exec php composer cs # Check PSR-12 coding standards
docker compose exec php composer cs-fix # Fix coding standards automatically
docker compose exec php composer phpstan # Run static analysis (level 8)
# Local development (requires PHP 8.1+)
composer test # Run PHPUnit tests
composer check # Run all checks
composer cs # Check coding standards
composer cs-fix # Fix coding standards automatically
composer phpstan # Static analysis- PHP-CS-Fixer: Automatically fixes code to comply with PSR-12 standards
- PHPStan: Static analysis at level 8 for maximum type safety
- PHPUnit: Comprehensive test suite with 2,300+ tests
- Strict Types: All files use
declare(strict_types=1)for type safety
We welcome contributions! Please see our Contributing Guidelines.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
composer check) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
|
|
- π Wiki Documentation - Comprehensive guides
- π API Reference - Detailed API docs
- π¬ GitHub Discussions - Community forum
- π§ Email: jjuanrivvera@gmail.com
If you find this project helpful, please consider giving it a star on GitHub! It helps others discover the project and motivates continued development.
