Building Content Items

Copy Markdown View Source

Common content item patterns for deep linking responses. Each section shows a self-contained example you can adapt.

The simplest content item. The platform creates a link that launches back into your tool:

{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  url: "https://tool.example.com/activities/42",
  title: "Week 3 Activity"
)

Omit url to use the tool's base launch URL configured in the platform:

{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  title: "Activity from Tool"
)

Include a line_item so the platform auto-creates a gradebook column. You can then post scores to it using the grade service:

{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  url: "https://tool.example.com/quizzes/7",
  title: "Midterm Quiz",
  line_item: [
    score_maximum: 100,
    label: "Midterm Quiz",
    tag: "quiz",
    grades_released: true
  ]
)

score_maximum is required and must be positive. The label defaults to the content item's title if omitted.

Custom parameters are passed to your tool on every launch of the created link. Both keys and values must be strings:

{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  url: "https://tool.example.com/assignments",
  title: "Essay Submission",
  custom: %{
    "assignment_id" => "essay-3",
    "rubric" => "standard",
    "allow_late" => "true"
  }
)

Setting availability windows

Control when the link is accessible and when submissions are accepted:

{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  url: "https://tool.example.com/exams/final",
  title: "Final Exam",
  available: [
    start_date_time: "2025-05-01T08:00:00Z",
    end_date_time: "2025-05-01T12:00:00Z"
  ],
  submission: [
    end_date_time: "2025-05-01T11:30:00Z"
  ]
)

Both fields in each window are optional. You can set just an end time to leave the start open.

For URLs outside your tool. Use Link instead of LtiResourceLink when the resource isn't an LTI launch:

{:ok, link} = Ltix.DeepLinking.ContentItem.Link.new(
  url: "https://docs.example.com/getting-started",
  title: "Getting Started Guide",
  icon: [url: "https://docs.example.com/favicon.png", width: 32, height: 32]
)

Control how the platform opens the link with window or iframe:

{:ok, link} = Ltix.DeepLinking.ContentItem.Link.new(
  url: "https://videos.example.com/lecture-5",
  title: "Lecture 5 Recording",
  iframe: [src: "https://videos.example.com/embed/lecture-5", width: 800, height: 450]
)

Include embeddable HTML with embed:

{:ok, link} = Ltix.DeepLinking.ContentItem.Link.new(
  url: "https://interactive.example.com/sim/42",
  title: "Physics Simulation",
  embed: [html: ~s(<iframe src="https://interactive.example.com/embed/42"></iframe>)]
)

File with expiry

File URLs are expected to be short-lived. Set expires_at to tell the platform when the URL stops working:

{:ok, file} = Ltix.DeepLinking.ContentItem.File.new(
  url: "https://tool.example.com/exports/report-abc123.pdf",
  title: "Lab Report Template",
  media_type: "application/pdf",
  expires_at: "2025-03-15T12:00:00Z"
)

HTML fragment

Inline HTML the platform embeds directly in its page:

{:ok, fragment} = Ltix.DeepLinking.ContentItem.HtmlFragment.new(
  html: """
  <div class="tool-widget">
    <h3>Study Flashcards</h3>
    <p>25 cards covering Chapter 4</p>
  </div>
  """,
  title: "Chapter 4 Flashcards"
)

Prefer Link with embed

If your HTML renders a single resource that also has a direct URL, use Link with an embed instead. This gives the platform both the embeddable HTML and a fallback URL.

Image

An image the platform renders directly with an img tag:

{:ok, image} = Ltix.DeepLinking.ContentItem.Image.new(
  url: "https://tool.example.com/diagrams/architecture.png",
  title: "System Architecture",
  width: 1200,
  height: 800
)

Include a thumbnail for list views:

{:ok, image} = Ltix.DeepLinking.ContentItem.Image.new(
  url: "https://tool.example.com/photos/lab-setup.jpg",
  title: "Lab Setup Photo",
  thumbnail: [
    url: "https://tool.example.com/photos/lab-setup-thumb.jpg",
    width: 150,
    height: 100
  ]
)

Using extensions

Add vendor-specific properties keyed by fully qualified URLs. Extensions are merged into the top-level JSON output:

{:ok, link} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  url: "https://tool.example.com/activity/5",
  title: "Proctored Exam",
  extensions: %{
    "https://vendor.example.com/proctoring" => %{
      "required" => true,
      "duration_minutes" => 90
    }
  }
)

All five content item types support the extensions field.

Returning multiple items

Pass a list of mixed content item types to build_response/3:

{:ok, quiz} = Ltix.DeepLinking.ContentItem.LtiResourceLink.new(
  url: "https://tool.example.com/quizzes/1",
  title: "Quiz 1",
  line_item: [score_maximum: 50]
)

{:ok, reading} = Ltix.DeepLinking.ContentItem.Link.new(
  url: "https://docs.example.com/chapter-3",
  title: "Chapter 3 Reading"
)

{:ok, response} = Ltix.DeepLinking.build_response(context, [quiz, reading],
  msg: "Added 2 items"
)

Check settings.accept_multiple before offering multi-select. If accept_multiple is false, build_response/3 returns a ContentItemsExceedLimit error for more than one item.

Returning no items

Return an empty list with a message when the user cancels or an error occurs:

# User cancelled
{:ok, response} = Ltix.DeepLinking.build_response(context, [],
  msg: "No items selected"
)

# Something went wrong
{:ok, response} = Ltix.DeepLinking.build_response(context, [],
  error_message: "Could not load available activities",
  error_log: "ActivityService returned 503"
)

An empty items list is always valid regardless of platform constraints.