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?