Custom components#

This chapter describes how to use custom components in icalendar.

icalendar automatically handles X-components and IANA-components not in RFC 5545.

RFC 5545 defines two custom component types.

X-Components

Vendor-specific components, for example, X-MYAPP-SETTINGS.

IANA-Components

IANA-registered components, but not in RFC 5545.

icalendar preserves all custom components through dynamic component creation using ComponentFactory.

Parse custom components#

Parse custom components using either Component.from_ical() or Calendar.from_ical().

Component.from_ical()#

Parse any component type, including custom ones. The following example shows how to parse a component, then display its name and SUMMARY.

>>> from icalendar import Component
>>> ics_data = b"""BEGIN:X-MYCOMPONENT
... SUMMARY:Custom component example
... X-CUSTOM-PROP:Some value
... END:X-MYCOMPONENT
... """
>>> component = Component.from_ical(ics_data)
>>> component.name
'X-MYCOMPONENT'
>>> str(component["SUMMARY"])
'Custom component example'

Calendar.from_ical()#

Parse a calendar containing custom components. The following example shows how to parse a component, get its subcomponents, then display its name and UID.

>>> from icalendar import Calendar
>>> ics_data = b"""BEGIN:VCALENDAR
... VERSION:2.0
... PRODID:-//Example//EN
... BEGIN:X-CUSTOM
... UID:custom-1
... SUMMARY:A custom component
... END:X-CUSTOM
... END:VCALENDAR
... """
>>> cal = Calendar.from_ical(ics_data)
>>> custom = cal.subcomponents[0]
>>> custom.name
'X-CUSTOM'
>>> str(custom["UID"])
'custom-1'

Access custom components#

Custom components work exactly like standard components. The following example shows how to access a custom component to display its name and other attributes.

>>> from icalendar import Component
>>> # Create custom component using factory
>>> MyComp = Component.get_component_class("MYCOMP")
>>> comp = MyComp()
>>> comp.name
'MYCOMP'
>>>
>>> # Add properties
>>> comp.add("summary", "Test Summary")
>>> comp.add("x-custom-field", "Custom Value")
>>>
>>> # Access properties
>>> str(comp["SUMMARY"])
'Test Summary'
>>> str(comp.get("X-CUSTOM-FIELD"))
'Custom Value'
>>>
>>> # Add subcomponents
>>> from icalendar import Event
>>> event = Event()
>>> event.add("uid", "123")
>>> comp.add_component(event)
>>> len(comp.subcomponents)
1

Round-trip preservation#

Custom components are fully preserved during round-trip parsing. The following example demonstrates this feature.

>>> from icalendar import Calendar
>>> original = b"""BEGIN:VCALENDAR
... VERSION:2.0
... PRODID:-//Test//EN
... BEGIN:X-VENDOR-COMPONENT
... X-VENDOR-PROP:proprietary
... UID:vendor-123
... END:X-VENDOR-COMPONENT
... END:VCALENDAR
... """
>>> cal = Calendar.from_ical(original)
>>> regenerated = cal.to_ical()
>>> # Custom component and its properties are preserved
>>> b"X-VENDOR-COMPONENT" in regenerated
True
>>> b"X-VENDOR-PROP" in regenerated
True

Which from_ical()?#

Use Component.from_ical() for standalone components and fragments.

Use Calendar.from_ical() for complete iCalendar files. It also handles timezones.

The following example shows the different usage of both methods.

>>> from icalendar import Component, Calendar
>>>
>>> # Standalone custom component - use Component.from_ical()
>>> standalone = b"""BEGIN:X-MYCOMP
... UID:123
... END:X-MYCOMP
... """
>>> comp = Component.from_ical(standalone)
>>> comp.name
'X-MYCOMP'
>>>
>>> # Complete calendar - use Calendar.from_ical()
>>> calendar_data = b"""BEGIN:VCALENDAR
... VERSION:2.0
... PRODID:-//Test//EN
... BEGIN:X-MYCOMP
... UID:123
... END:X-MYCOMP
... END:VCALENDAR
... """
>>> cal = Calendar.from_ical(calendar_data)
>>> len(cal.subcomponents)
1

Register custom component classes#

Register custom component classes with special behavior. The following example shows how to create and register a custom component with validation and helper methods.

from icalendar import Component

class XAcmeComponent(Component):
    """Custom ACME component with validation."""

    name = "X-ACME"

    def validate(self):
        """Validate required properties."""
        required = ["UID", "X-ACME-ID"]
        for prop in required:
            if prop not in self:
                raise ValueError(f"Missing {prop}")

    def get_acme_id(self):
        """Get ACME ID."""
        return self.get("X-ACME-ID")

# Register the component
Component.register(XAcmeComponent)

After registration, parsing BEGIN:X-ACME uses your custom class:

>>> from icalendar import Component
>>> class XAcmeComponent(Component):
...     """Custom ACME component with validation."""
...     name = "X-ACME"
...     def validate(self):
...         """Validate required properties."""
...         required = ["UID", "X-ACME-ID"]
...         for prop in required:
...             if prop not in self:
...                 raise ValueError(f"Missing {prop}")
...     def get_acme_id(self):
...         """Get ACME ID."""
...         return self.get("X-ACME-ID")
>>> Component.register(XAcmeComponent)
>>> ical_data = b"""BEGIN:X-ACME
... UID:123
... X-ACME-ID:acme-1
... END:X-ACME
... """
>>> comp = Component.from_ical(ical_data)
>>> comp.validate()
>>> str(comp.get_acme_id())
'acme-1'

RFC 5545 compliance#

The icalendar library is fully compliant with RFC 5545 requirements for custom components.

Preserves unknown components

Custom components are never dropped.

Maintains data integrity

All properties and subcomponents are preserved.

Round-trip safe

Parse to serialize, and back to parse, produces equivalent results.

No special handling

X-components and IANA-components are treated identically.

icalendar implements a permissive approach. Rather than rejecting unknown components, it preserves them while making them accessible through the same API as standard components.

Nested custom components#

Custom components can contain standard components, and vice versa.

>>> from icalendar import Component, Event
>>> # Custom component containing a standard event
>>> ics_data = b"""BEGIN:X-CONTAINER
... SUMMARY:Container
... BEGIN:VEVENT
... UID:event-1
... DTSTART:20240101T120000Z
... SUMMARY:Event inside custom component
... END:VEVENT
... END:X-CONTAINER
... """
>>> container = Component.from_ical(ics_data)
>>> container.name
'X-CONTAINER'
>>> event = container.subcomponents[0]
>>> event.name
'VEVENT'
>>> str(event["SUMMARY"])
'Event inside custom component'

Use cases#

Custom components extend the functionality beyond icalendar’s core. Use cases include the following:

  • vendor extensions and proprietary features

  • experimental or draft component types

  • legacy system support

  • data preservation during round-trips

The following example shows how to add a feature for a calendar, where an application supports dark mode in its theme.

>>> from icalendar import Calendar, Component
>>> cal = Calendar()
>>> cal.add("prodid", "-//My App//EN")
>>> cal.add("version", "2.0")
>>> # Create custom component using factory
>>> CustomSettings = Component.get_component_class("X-MYAPP-SETTINGS")
>>> custom = CustomSettings()
>>> custom.add("uid", "settings-1")
>>> custom.add("x-theme", "dark")
>>> cal.add_component(custom)
>>> b"BEGIN:X-MYAPP-SETTINGS" in cal.to_ical()
True