Metadata Structure by Content Type

Content: Metadata Structure by Content Type

User Intent

"What metadata is captured for each content type? How do I access it?"

Operation

  • Concept: Content metadata fields

  • GraphQL Fields: Type-specific metadata objects

  • Entity Type: Content

  • Common Use Cases: Accessing email details, message info, document properties, image metadata, event details

Metadata Fields Overview

Each ContentType has a corresponding metadata field with type-specific properties automatically captured during ingestion.

TypeScript (Canonical)

import { Graphlit } from 'graphlit-client';
import { ContentTypes, FileTypes } from 'graphlit-client/dist/generated/graphql-types';

const graphlit = new Graphlit();

const content = await graphlit.getContent('content-id');

// Access metadata based on content type
if (content.content.type === ContentTypes.Email && content.content.email) {
  const email = content.content.email;
  console.log(`Subject: ${email.subject}`);
  console.log(`From: ${email.from[0].email}`);
  console.log(`To: ${email.to?.map(p => p.email).join(', ')}`);
  console.log(`Labels: ${email.labels?.join(', ')}`);
  console.log(`Attachments: ${email.attachmentCount}`);
}

if (content.content.type === ContentTypes.Message && content.content.message) {
  const msg = content.content.message;
  console.log(`Channel: ${msg.channelName}`);
  console.log(`Author: ${msg.author?.name} (${msg.author?.email})`);
  console.log(`Mentions: ${msg.mentions?.map(p => p.name).join(', ')}`);
}

if (content.content.fileType === FileTypes.Document && content.content.document) {
  const doc = content.content.document;
  console.log(`Pages: ${doc.pageCount}`);
  console.log(`Author: ${doc.author}`);
  console.log(`Words: ${doc.wordCount}`);
  console.log(`Encrypted: ${doc.isEncrypted}`);
}

Email Metadata (ContentTypes.Email)

Field: content.email (EmailMetadata)

Properties:

interface EmailMetadata {
  identifier: string;              // Message-ID
  threadIdentifier: string;        // In-Reply-To / References
  subject: string;
  from: PersonReference[];         // Sender
  to: PersonReference[];           // Recipients
  cc: PersonReference[];           // CC recipients
  bcc: PersonReference[];          // BCC recipients
  labels: string[];                // Gmail labels or folder names
  sensitivity: MailSensitivity;    // NORMAL, PERSONAL, PRIVATE, CONFIDENTIAL
  priority: MailPriority;          // LOW, NORMAL, HIGH
  importance: MailImportance;      // LOW, NORMAL, HIGH
  attachmentCount: number;
  unsubscribeUrl: string;          // Newsletter unsubscribe
  publicationName: string;         // Newsletter name
  publicationUrl: string;          // Newsletter URL
}

interface PersonReference {
  name: string;
  email: string;
  givenName: string;
  familyName: string;
}

Example Access:

const email = content.content.email;

// Sender details
console.log(`From: ${email.from[0].name} <${email.from[0].email}>`);

// All recipients
const allRecipients = [
  ...email.to || [],
  ...email.cc || [],
  ...email.bcc || []
];
console.log(`Total recipients: ${allRecipients.length}`);

// Thread detection
if (email.threadIdentifier) {
  console.log(`Part of thread: ${email.threadIdentifier}`);
}

// Newsletter detection
if (email.publicationName) {
  console.log(`Newsletter: ${email.publicationName}`);
}

Message Metadata (ContentTypes.Message)

Field: content.message (MessageMetadata)

Properties:

interface MessageMetadata {
  identifier: string;              // Message ID
  conversationIdentifier: string;  // Thread/conversation ID
  channelIdentifier: string;       // Channel ID
  channelName: string;             // Channel name (e.g., "engineering")
  author: PersonReference;         // Message author
  mentions: PersonReference[];     // @mentioned users
  attachmentCount: number;
  links: string[];                 // URLs in message
}

Example Access:

const msg = content.content.message;

// Author info
console.log(`Posted by: ${msg.author.name} in #${msg.channelName}`);

// Mentions
if (msg.mentions && msg.mentions.length > 0) {
  console.log(`Mentioned: ${msg.mentions.map(p => '@' + p.name).join(' ')}`);
}

// Links shared
if (msg.links && msg.links.length > 0) {
  console.log(`Links: ${msg.links.length}`);
}

// Thread detection
if (msg.conversationIdentifier !== msg.identifier) {
  console.log(`Reply in conversation: ${msg.conversationIdentifier}`);
}

Document Metadata (FileDocument)

Field: content.document (DocumentMetadata)

Properties:

interface DocumentMetadata {
  title: string;
  subject: string;
  summary: string;
  author: string;
  lastModifiedBy: string;
  publisher: string;
  description: string;
  keywords: string[];
  pageCount: number;
  worksheetCount: number;          // Excel
  slideCount: number;              // PowerPoint
  wordCount: number;
  lineCount: number;
  paragraphCount: number;
  isEncrypted: boolean;
  hasDigitalSignature: boolean;
}

Example Access:

const doc = content.content.document;

// Document stats
console.log(`${doc.pageCount} pages, ${doc.wordCount} words`);
console.log(`Author: ${doc.author}`);

// Excel-specific
if (doc.worksheetCount) {
  console.log(`Excel workbook: ${doc.worksheetCount} worksheets`);
}

// PowerPoint-specific
if (doc.slideCount) {
  console.log(`Presentation: ${doc.slideCount} slides`);
}

// Security
if (doc.isEncrypted) {
  console.log(` Encrypted document`);
}
if (doc.hasDigitalSignature) {
  console.log(`✓ Digitally signed`);
}

Image Metadata (FileImage)

Field: content.image (ImageMetadata)

Properties:

interface ImageMetadata {
  width: number;
  height: number;
  resolutionX: number;             // DPI horizontal
  resolutionY: number;             // DPI vertical
  bitsPerComponent: number;
  components: number;
  projectionType: ImageProjectionTypes;  // EQUIRECTANGULAR, etc.
  orientation: OrientationTypes;
  description: string;
  
  // Camera EXIF data
  make: string;                    // Camera manufacturer
  model: string;                   // Camera model
  software: string;                // Editing software
  lens: string;
  focalLength: number;             // mm
  exposureTime: string;            // e.g., "1/125"
  fNumber: string;                 // e.g., "f/2.8"
  iso: string;                     // e.g., "ISO 400"
  
  // GPS data
  heading: number;                 // Compass direction
  pitch: number;                   // Angle
}

Example Access:

const img = content.content.image;

// Basic info
console.log(`${img.width}x${img.height} pixels`);
console.log(`Resolution: ${img.resolutionX} DPI`);

// Camera info
if (img.make && img.model) {
  console.log(`Camera: ${img.make} ${img.model}`);
  console.log(`Lens: ${img.lens}`);
  console.log(`Settings: ${img.exposureTime} at ${img.fNumber}, ${img.iso}`);
}

// GPS location
if (content.content.location) {
  console.log(`Location: ${content.content.location.latitude}, ${content.content.location.longitude}`);
}

Audio Metadata (FileAudio)

Field: content.audio (AudioMetadata)

Properties:

interface AudioMetadata {
  title: string;
  description: string;
  author: string;
  keywords: string[];
  
  // Podcast-specific
  series: string;                  // Podcast series name
  episode: string;                 // Episode number
  episodeType: string;             // FULL, TRAILER, BONUS
  season: string;                  // Season number
  publisher: string;
  copyright: string;
  genre: string;
  
  // Technical
  bitrate: number;                 // bits per second
  channels: number;                // 1=mono, 2=stereo
  sampleRate: number;              // Hz
  bitsPerSample: number;
  duration: string;                // ISO 8601 duration
}

Example Access:

const audio = content.content.audio;

// Podcast info
if (audio.series) {
  console.log(`Podcast: ${audio.series}`);
  console.log(`Episode: ${audio.episode} (${audio.episodeType})`);
  if (audio.season) console.log(`Season: ${audio.season}`);
}

// Audio quality
console.log(`${audio.bitrate / 1000}kbps, ${audio.sampleRate}Hz`);
console.log(`${audio.channels === 1 ? 'Mono' : 'Stereo'}`);

// Duration
import { Duration } from 'luxon';
const dur = Duration.fromISO(audio.duration);
console.log(`Length: ${dur.toFormat('mm:ss')}`);

Video Metadata (FileVideo)

Field: content.video (VideoMetadata)

Properties:

interface VideoMetadata {
  width: number;
  height: number;
  duration: string;                // ISO 8601 duration
  title: string;
  description: string;
  keywords: string[];
  author: string;
  
  // Camera/device info
  make: string;
  model: string;
  software: string;
}

Example Access:

const video = content.content.video;

console.log(`${video.width}x${video.height}`);
console.log(`Duration: ${video.duration}`);
console.log(`Title: ${video.title}`);

if (video.make) {
  console.log(`Recorded on: ${video.make} ${video.model}`);
}

Event Metadata (ContentEvent)

Field: content.event (EventMetadata)

Properties:

interface EventMetadata {
  eventIdentifier: string;
  calendarIdentifier: string;
  subject: string;
  startDateTime: Date;
  endDateTime: Date;
  isAllDay: boolean;
  timezone: string;
  status: CalendarEventStatus;         // CONFIRMED, TENTATIVE, CANCELLED
  visibility: CalendarEventVisibility; // PUBLIC, PRIVATE, CONFIDENTIAL
  meetingLink: string;
  categories: string[];
  
  organizer: CalendarAttendee;
  attendees: CalendarAttendee[];
  reminders: CalendarReminder[];
  
  // Recurring events
  recurringEventIdentifier: string;
  isRecurring: boolean;
  recurrence: CalendarRecurrence;
}

interface CalendarAttendee {
  name: string;
  email: string;
  isOptional: boolean;
  isOrganizer: boolean;
  responseStatus: CalendarAttendeeResponseStatus;  // ACCEPTED, DECLINED, TENTATIVE, NEEDS_ACTION
}

Example Access:

const event = content.content.event;

console.log(`Event: ${event.subject}`);
console.log(`When: ${event.startDateTime} - ${event.endDateTime}`);
console.log(`Timezone: ${event.timezone}`);

// Organizer
console.log(`Organizer: ${event.organizer.name} <${event.organizer.email}>`);

// Attendees
console.log(`Attendees (${event.attendees?.length || 0}):`);
event.attendees?.forEach(att => {
  const status = att.responseStatus;
  const icon = att.isOptional ? '(optional)' : '(required)';
  console.log(`  ${att.name}: ${status} ${icon}`);
});

// Meeting link
if (event.meetingLink) {
  console.log(`Join: ${event.meetingLink}`);
}

// Recurring
if (event.isRecurring) {
  console.log(`Recurring: ${event.recurrence.pattern} every ${event.recurrence.interval}`);
}

Issue Metadata (ContentTypes.Issue)

Field: content.issue (IssueMetadata)

Properties:

interface IssueMetadata {
  identifier: string;              // Issue ID/key (e.g., "PROJ-123")
  title: string;
  project: string;                 // Project name
  team: string;                    // Team name (Linear)
  status: string;                  // Status name
  priority: string;                // Priority level
  type: string;                    // Issue type (Bug, Feature, etc.)
  labels: string[];
}

Example Access:

const issue = content.content.issue;

console.log(`${issue.identifier}: ${issue.title}`);
console.log(`Project: ${issue.project}`);
console.log(`Status: ${issue.status}`);
console.log(`Priority: ${issue.priority}`);
console.log(`Type: ${issue.type}`);

if (issue.labels && issue.labels.length > 0) {
  console.log(`Labels: ${issue.labels.join(', ')}`);
}

Post Metadata (ContentPost)

Field: content.post (PostMetadata)

Properties:

interface PostMetadata {
  identifier: string;              // Post ID
  title: string;
  author: PersonReference;
  upvotes: number;
  downvotes: number;
  commentCount: number;
  links: string[];
}

Example Access:

const post = content.content.post;

console.log(`${post.title}`);
console.log(`By: ${post.author.name}`);
console.log(`Score: ${post.upvotes - post.downvotes} (${post.upvotes}↑ ${post.downvotes}↓)`);
console.log(`Comments: ${post.commentCount}`);

if (post.links && post.links.length > 0) {
  console.log(`Links: ${post.links.join(', ')}`);
}

Access email metadata (snake_case fields)

if content.content.type == "EMAIL" and content.content.email: email = content.content.email print(f"Subject: {email.subject}") print(f"From: {email.from_[0].email}") # Note: from_ (reserved word) print(f"Attachments: {email.attachment_count}")

Access message metadata

if content.content.type == "MESSAGE" and content.content.message: msg = content.content.message print(f"Channel: {msg.channel_name}") print(f"Author: {msg.author.name}")

Access document metadata

if content.content.file_type == "DOCUMENT" and content.content.document: doc = content.content.document print(f"Pages: {doc.page_count}") print(f"Words: {doc.word_count}")


**C#**:
```csharp
using Graphlit;

var client = new Graphlit();

var content = await graphlit.GetContent("content-id");

// Access email metadata (PascalCase)
if (content.Content.Type == ContentTypes.Email && content.Content.Email != null)
{
    var email = content.Content.Email;
    Console.WriteLine($"Subject: {email.Subject}");
    Console.WriteLine($"From: {email.From[0].Email}");
    Console.WriteLine($"Attachments: {email.AttachmentCount}");
}

// Access message metadata
if (content.Content.Type == ContentTypes.Message && content.Content.Message != null)
{
    var msg = content.Content.Message;
    Console.WriteLine($"Channel: {msg.ChannelName}");
    Console.WriteLine($"Author: {msg.Author.Name}");
}

Developer Hints

Always Check Type First

//  WRONG - Might be null
console.log(content.content.email.subject);

//  CORRECT - Check type first
if (content.content.type === ContentTypes.Email && content.content.email) {
  console.log(content.content.email.subject);
}

Null-Safe Access

// Use optional chaining for nested properties
console.log(`Attachments: ${content.content.email?.attachmentCount || 0}`);
console.log(`Mentions: ${content.content.message?.mentions?.length || 0}`);

PersonReference Pattern

// PersonReference appears in multiple metadata types
function formatPerson(person: PersonReference): string {
  if (person.name && person.email) {
    return `${person.name} <${person.email}>`;
  }
  return person.name || person.email || 'Unknown';
}

// Use for email, message mentions, event attendees, etc.
console.log(formatPerson(content.content.email.from[0]));

Common Issues & Solutions

Issue: Metadata field is undefined Solution: Check content type matches expected type

// Check before accessing
if (content.content.type === ContentTypes.Email && content.content.email) {
  // Safe to access email metadata
}

Issue: Arrays are null instead of empty Solution: Use nullish coalescing

const labels = content.content.email?.labels || [];
const mentions = content.content.message?.mentions || [];

Issue: Date fields are strings not Date objects Solution: Parse ISO 8601 strings

const created = new Date(content.content.creationDate);
const eventStart = new Date(content.content.event.startDateTime);

Production Example

async function analyzeContent(contentId: string) {
  const content = await graphlit.getContent(contentId);
  
  console.log(`\n=== CONTENT ANALYSIS ===`);
  console.log(`ID: ${content.content.id}`);
  console.log(`Name: ${content.content.name}`);
  console.log(`Type: ${content.content.type}`);
  console.log(`Created: ${content.content.creationDate}`);
  
  // Type-specific metadata
  switch (content.content.type) {
    case ContentTypes.Email:
      if (content.content.email) {
        console.log(`\n Email Metadata:`);
        console.log(`  From: ${content.content.email.from[0].email}`);
        console.log(`  Subject: ${content.content.email.subject}`);
        console.log(`  Recipients: ${content.content.email.to?.length || 0}`);
        console.log(`  Labels: ${content.content.email.labels?.join(', ') || 'none'}`);
      }
      break;
      
    case ContentTypes.Message:
      if (content.content.message) {
        console.log(`\n💬 Message Metadata:`);
        console.log(`  Channel: ${content.content.message.channelName}`);
        console.log(`  Author: ${content.content.message.author?.name}`);
        console.log(`  Mentions: ${content.content.message.mentions?.length || 0}`);
      }
      break;
      
    case ContentTypes.File:
      if (content.content.fileType === FileTypes.Document && content.content.document) {
        console.log(`\n Document Metadata:`);
        console.log(`  Pages: ${content.content.document.pageCount}`);
        console.log(`  Words: ${content.content.document.wordCount}`);
        console.log(`  Author: ${content.content.document.author || 'Unknown'}`);
      } else if (content.content.fileType === FileTypes.Image && content.content.image) {
        console.log(`\n🖼 Image Metadata:`);
        console.log(`  Size: ${content.content.image.width}x${content.content.image.height}`);
        console.log(`  Camera: ${content.content.image.make} ${content.content.image.model}`);
      }
      break;
  }
  
  // Common properties
  console.log(`\nFile: ${content.content.fileName || 'N/A'}`);
  console.log(`Size: ${content.content.fileSize || 0} bytes`);
  console.log(`MIME: ${content.content.mimeType || 'N/A'}`);
}

Last updated

Was this helpful?