# Canvas LMS

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 45 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Canvas LMS by creating an API Group with your institution's subdomain as the Base URL and a Bearer access token in the Authorization header. The critical trap to avoid: Canvas paginates via the HTTP Link header (not a JSON page field), so set per_page=100 and use a Dart Custom Action with the http package to follow rel=next links for complete course rosters and grade lists.

## Canvas is Clean REST — But Link-Header Pagination Trips Up Every AI-Built App

Canvas LMS exposes a well-structured REST API at each institution's own subdomain: https://yourschool.instructure.com/api/v1. Every endpoint — /courses, /courses/:id/assignments, /courses/:id/students/submissions — follows the same pattern. Authentication is a single Bearer access token that any Canvas account holder can generate without involving an IT department: go to Canvas Account → Settings → Approved Integrations → New Access Token, give it a name and an optional expiry date, and copy the token immediately (Canvas only shows it once).

The feature that breaks nearly every AI-generated FlutterFlow Canvas integration is pagination. Canvas does not put page information in the JSON body — it uses the HTTP Link response header, which contains URLs for the first, current, next, and last pages using the rel='next' convention. FlutterFlow's JSON path parser reads the response body only, so it cannot automatically follow these links. The practical workaround is to add per_page=100 as a query parameter to every API Call. Canvas returns up to 100 items per request, which covers most course rosters and assignment lists in a single call. For very large data sets you need a Dart Custom Action that follows the Link header iteratively.

Canvas API access is free for any Canvas account, and rate limits are generous — typically several hundred requests per minute per token, though exact numbers vary by institution configuration. A small number of institutions restrict API access entirely for security policy reasons, so confirm access is available before building your app.

## Before you start

- A Canvas LMS account at your institution (any role: student, instructor, or admin)
- Institution Canvas domain — the subdomain format is https://yourschool.instructure.com (check with your Canvas admin if unsure)
- A Canvas personal access token generated in Canvas Account → Settings → New Access Token
- FlutterFlow project open at app.flutterflow.io
- For full-roster pagination: familiarity with FlutterFlow Custom Code (Dart) and the http pub.dev package

## Step-by-step guide

### 1. Generate your Canvas access token

Open Canvas in your browser (your institution's Canvas URL, not instructure.com directly). Click your profile avatar in the top-left corner to open the navigation drawer, then click Account, then Settings. Scroll down to the Approved Integrations section and click the New Access Token button. Give the token a descriptive name such as FlutterFlow App Dev and optionally set an expiry date — for production apps, set a reasonable expiry and plan to rotate the token. Click Generate Token and copy the long alphanumeric string that appears. Canvas only displays the token once; if you close this dialog without copying it, you will need to delete the token and create a new one.

Important security note: this token carries the full permissions of the account that generated it. A teacher's token can read all student grades in their courses; a student's token can only read their own data. For a real multi-user app, each user authenticates with their own Canvas credentials and stores their own token — typically in Firestore or Supabase tied to their app user ID. Never hardcode a single shared token in the FlutterFlow API Group: it would expose whatever account generated it to every app user. For a personal prototype with a single user (yourself), storing your own token in App State is fine to get started.

**Expected result:** You have a Canvas access token string copied to your clipboard or a secure notes app. The Approved Integrations list in Canvas Settings shows the new token with its name and expiry.

### 2. Create a FlutterFlow API Group for Canvas

In FlutterFlow, click API Calls in the left navigation bar, then click + Add → Create API Group. Name the group Canvas LMS API. In the Base URL field enter your institution's API base URL in the format https://yourschool.instructure.com/api/v1 — replace yourschool with your actual Canvas subdomain. Do not include a trailing slash.

In the Headers section click + Add Header. Set the key to Authorization and the value to Bearer {{token}}. Then click + Add Variable on the API Group level, name it token with type String. This variable will receive the user's access token at runtime from App State or Firestore — it is never hardcoded in the group itself.

Add a second query parameter at the group level if you want it applied to all calls: click + Add Query Parameter, key per_page, value 100. This ensures Canvas returns up to 100 items per response, reducing the frequency of Link-header pagination issues.

Click Save. Your API Group is now configured and ready to receive individual API Calls for specific Canvas endpoints.

```
// Example API Group config reference
// Base URL: https://yourschool.instructure.com/api/v1
// Headers:
//   Authorization: Bearer {{token}}
// Group-level query param:
//   per_page: 100
```

**Expected result:** The Canvas LMS API group appears in the API Calls panel with the correct Base URL, Authorization header, and per_page query parameter.

### 3. Add API Calls for courses and assignments

Inside the Canvas LMS API group, click + Add API Call. Name it listCourses, set the method to GET, and set the endpoint path to /courses. Add a query variable enrollment_state with value active to return only courses the user is currently enrolled in. Click Save.

Add another API Call named listAssignments, method GET, endpoint /courses/{{courseId}}/assignments. Add a variable courseId of type String — this will be populated when the user taps a course on the courses screen. Add query params due_after={{cutoffDate}} if you want to filter by upcoming assignments.

For grade data, add a third API Call named listSubmissions, method GET, endpoint /courses/{{courseId}}/students/submissions, with a variable courseId. Note: this endpoint returns submission data for the authenticated user by default when using a student token. An instructor token returns all student submissions — a much larger data set that may require pagination handling.

For each API Call, use the Response & Test tab: click Test API Call, then paste a sample Canvas JSON response (you can get this by calling the endpoint in your browser while logged in). Click Generate JSON Paths and FlutterFlow will automatically create path expressions like $.courses[0].name, $.courses[0].id, and $.courses[0].term.name that you can bind directly to widget properties.

```
// Sample Canvas courses JSON path expressions
// $.id             → course ID (integer)
// $.name           → course name (string)
// $.course_code    → short course code
// $.enrollment_state  → active / completed
// $.term.name      → term/semester label
//
// Assignments:
// $.id
// $.name
// $.due_at         → ISO 8601 datetime string
// $.points_possible
```

**Expected result:** The API Calls panel shows listCourses, listAssignments, and optionally listSubmissions with JSON Paths auto-generated from sample responses. Testing listCourses in FlutterFlow returns your real Canvas course data.

### 4. Build the courses ListView and course detail screen

On your main screen, add a ListView widget. Open the Actions panel (or set the Backend Query in the widget's data source settings) and configure it to call listCourses from the Canvas LMS API group, passing the user's token from App State. Map the response to a list-type page variable named courses.

Inside the ListView's list item, add two Text widgets: one bound to the courses list item's name JSON path ($.name) and one bound to $.course_code. Add a visual container or Card widget for styling. Optionally add a badge showing $.enrollment_state.

Create a second screen named CourseDetail. When the user taps a course list item, add a Navigate action passing the selected course's id as a parameter to CourseDetail. On CourseDetail's OnPageLoad, call listAssignments with the passed courseId and the user's token from App State. Map the response to an assignments list variable. Add a ListView showing each assignment's name and due date.

Store the user's Canvas token in App State at app start. For a multi-user app, store tokens per user in Firestore under their UID and load the token when the user logs in — never expose another user's token to the wrong account.

**Expected result:** The courses screen shows a scrollable list of active courses pulled from Canvas. Tapping a course navigates to the detail screen showing assignments with due dates.

### 5. Add a Custom Action for Link-header pagination (large data sets)

For large grade lists or institution-wide rosters where per_page=100 is not enough, you need to follow Canvas's Link-header pagination in code. In FlutterFlow, go to Custom Code in the left nav, click + Add → Action, and name it fetchAllCanvasPages. In the Dependencies field add the http package (dart pub.dev package name: http, version: ^1.2.0). FlutterFlow will add this dependency automatically to the generated project.

The action takes two arguments: url (String) and token (String), and returns a list of dynamic JSON objects. It performs an initial GET to url, reads the Link response header to find the rel='next' URL, fetches that URL, and repeats until no rel='next' is found. It accumulates all items into a single Dart List and returns it.

Note: Custom Actions do not run in FlutterFlow's web preview Run mode — you must test on a physical device or an APK/IPA build. Use FlutterFlow's Test Mode to confirm the action compiles without errors, then deploy to a device for runtime validation. Use this action only when you need truly complete data sets; for most student-facing apps, per_page=100 on a single call is sufficient.

```
// Dart Custom Action — follow Canvas Link-header pagination
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<dynamic>> fetchAllCanvasPages(
    String url, String token) async {
  final List<dynamic> allItems = [];
  String? nextUrl = url;

  while (nextUrl != null) {
    final response = await http.get(
      Uri.parse(nextUrl),
      headers: {'Authorization': 'Bearer $token'},
    );

    if (response.statusCode != 200) {
      throw Exception('Canvas API error: ${response.statusCode}');
    }

    final List<dynamic> pageItems =
        jsonDecode(response.body) as List<dynamic>;
    allItems.addAll(pageItems);

    // Parse Link header for rel="next"
    final linkHeader = response.headers['link'] ?? '';
    nextUrl = _extractNextUrl(linkHeader);
  }

  return allItems;
}

String? _extractNextUrl(String linkHeader) {
  for (final part in linkHeader.split(',')) {
    if (part.contains('rel="next"')) {
      final match = RegExp(r'<([^>]+)>').firstMatch(part);
      return match?.group(1);
    }
  }
  return null;
}
```

**Expected result:** The Custom Action compiles without errors in FlutterFlow. When run on a device against a large Canvas course roster, it returns the complete list of items across all pages rather than just the first 100.

## Best practices

- Always add per_page=100 as a query parameter to Canvas API Calls — the default of 10 items per page causes silent truncation that is hard to diagnose in a running app.
- Store each user's Canvas token in Firestore or Supabase tied to their authenticated user ID — never hardcode a shared token in the API Group configuration.
- Confirm that the institution's Canvas subdomain and API access are working before building: visit https://yourschool.instructure.com/api/v1/courses in a browser while logged in and verify you see JSON.
- Handle 401 errors explicitly in the Action Flow Editor: display a message prompting the user to re-enter their Canvas token rather than showing a blank screen.
- Respect Canvas's timezone-aware date strings: always convert due_at and created_at fields to the device's local timezone before display, otherwise assignment due dates may appear off by a day.
- For read-only student-facing apps, request that users generate a token with the minimum required permissions and set an explicit expiry date.
- Use the Link-header Custom Action only for genuinely large data sets (>100 items) — for typical course views, per_page=100 is faster and simpler.
- Cache course list responses in App State or a local Firestore document to reduce API calls on repeated screen loads — Canvas data doesn't change between minutes.

## Use cases

### Student-facing mobile courses and assignments viewer

A FlutterFlow app shows a student their active course list, tapping a course reveals upcoming assignments sorted by due date. The app pulls from the Canvas REST API using the student's personal access token stored in Firestore. It serves as a lightweight companion to the full Canvas web interface.

Prompt example:

```
Build a Canvas courses screen that lists a student's active courses and shows assignments with due dates when they tap a course, using the Canvas REST API with a personal access token.
```

### Instructor grade dashboard for a single course

A FlutterFlow app lets an instructor view submission status and grades for all students in a specific course without opening the full Canvas web interface. The app calls /courses/:id/students/submissions and renders a sortable list by grade or submission date. Per_page=100 ensures the complete roster loads in one request.

Prompt example:

```
Create an instructor grade dashboard that fetches all student submissions for a selected course from Canvas and displays them in a sortable list with grade and submission status columns.
```

### Course announcement feed for a campus community app

A FlutterFlow campus app shows a unified announcement feed by calling the Canvas /courses/:id/discussion_topics endpoint for each enrolled course. Announcements are merged, sorted by posting date, and displayed in a reverse-chronological ListView that students can scroll through without switching between course pages.

Prompt example:

```
Build an announcements feed that fetches discussion topics flagged as announcements from multiple Canvas courses and merges them into a single date-sorted list.
```

## Troubleshooting

### Canvas API returns only 10 items even though the course has 30+ students or assignments

Cause: Canvas defaults to 10 items per page when no per_page parameter is specified. FlutterFlow's standard API Call fetches only the first page and cannot read the Link header to continue.

Solution: Add per_page=100 as a query parameter on each API Call in the FlutterFlow group. For data sets larger than 100 items, use the Dart Custom Action from Step 5 to follow Link-header pagination.

### 401 Unauthorized response from every Canvas API Call

Cause: The Bearer token is missing, incorrectly formatted, or the token has expired or been revoked in Canvas.

Solution: Open Canvas Account → Settings → Approved Integrations and confirm the token exists and has not expired. In FlutterFlow, verify the Authorization header is set to exactly Bearer {{token}} (with a space after Bearer) and that the token App State variable is populated before the API Call runs.

### Dates display one day earlier or later than the Canvas web interface

Cause: Canvas returns due_at timestamps in ISO 8601 format with UTC timezone offset. If FlutterFlow renders the date string directly without timezone conversion, the displayed date may differ from the user's local timezone.

Solution: Use FlutterFlow's DateTime formatting options on the Text widget binding to convert the ISO 8601 string to the user's local timezone before display. Alternatively, parse the date string in a Dart Custom Function that applies the device's local timezone offset.

### API Calls fail with a network error or return HTML instead of JSON

Cause: The institution subdomain in the API Group Base URL is incorrect, or the institution has restricted API access and Canvas is returning an error page rather than JSON.

Solution: Double-check the Base URL by visiting https://yourschool.instructure.com/api/v1/courses in your browser while logged in — you should see raw JSON. If you see an HTML error page or a login redirect, the institution may have disabled API access. Contact your Canvas administrator to confirm API access is enabled for external applications.

## Frequently asked questions

### Does every Canvas institution use the same API URL?

No. The Base URL is specific to each institution's Canvas domain, typically in the format https://yourschool.instructure.com/api/v1. Some institutions use custom domains (e.g. canvas.yourschool.edu). Check the URL in your browser when you are logged into Canvas and substitute it in FlutterFlow's API Group Base URL.

### Can I use one Canvas token for all my app users?

Technically yes, but you should not in a real app. A shared token carries the permissions of one Canvas account, so every user of your app would see that account's courses and grades — not their own. For a proper multi-user app, have each user generate their own token in Canvas Settings and store it in Firestore under their authenticated app user ID.

### Why does my Canvas ListView only show 10 items when the course has more?

Canvas defaults to 10 results per page and uses the HTTP Link response header for pagination — not a page number in the JSON body. FlutterFlow's standard API Call cannot read HTTP headers. Add per_page=100 as a query parameter on your API Call to retrieve up to 100 items in one request, which covers most use cases.

### Can I create or submit assignments through the Canvas API in FlutterFlow?

Yes, the Canvas REST API supports POST and PUT operations for creating submissions and updating grades. However, these write operations require instructor-level permissions on the access token. For student submission uploads, call the Submissions endpoint (POST /courses/:id/assignments/:assignment_id/submissions) with the file URL or text body. This is an Intermediate to Advanced flow requiring careful error handling.

### What happens if the user's Canvas token expires?

Canvas tokens expire on the date you set when creating them, or they can be manually revoked. When expired, every API Call returns 401 Unauthorized. Add a 401 error handler in your Action Flow that clears the stored token and navigates the user to a token re-entry screen. Encourage users to set a reminder to rotate their token before expiry.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/canvas-lms
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/canvas-lms
