or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

api-client.mdbase-stream-classes.mdcrm-streams.mdcustom-objects.mdengagement-streams.mderror-handling.mdindex.mdmarketing-sales-streams.mdproperty-history-streams.mdsource-connector.mdweb-analytics.md

custom-objects.mddocs/

0

# Custom Objects

1

2

Support for HubSpot custom objects with dynamic schema generation and custom properties handling. Custom objects allow HubSpot users to create their own data structures beyond the standard CRM objects.

3

4

## Capabilities

5

6

### Custom Object Stream

7

8

Base class for all HubSpot custom object streams with dynamic schema support.

9

10

```python { .api }

11

class CustomObject(CRMSearchStream, ABC):

12

"""

13

Abstract base class for HubSpot custom object streams.

14

15

Provides dynamic schema generation and property handling

16

for user-defined custom objects in HubSpot.

17

"""

18

19

def __init__(

20

self,

21

entity: str,

22

schema: Mapping[str, Any],

23

fully_qualified_name: str,

24

custom_properties: Mapping[str, Any],

25

**kwargs

26

):

27

"""

28

Initialize custom object stream.

29

30

Parameters:

31

- entity: Custom object entity name

32

- schema: JSON schema for the custom object

33

- fully_qualified_name: Fully qualified object name from HubSpot

34

- custom_properties: Custom property definitions

35

- **kwargs: Additional stream parameters (api, start_date, etc.)

36

"""

37

```

38

39

### Custom Object Discovery

40

41

Methods for discovering and processing custom object metadata from HubSpot.

42

43

```python { .api }

44

# From API class

45

def get_custom_objects_metadata(self) -> Iterable[Tuple[str, str, Mapping[str, Any]]]:

46

"""

47

Discover custom objects in the HubSpot account.

48

49

Yields:

50

- Tuples of (entity_name, fully_qualified_name, schema, properties)

51

"""

52

53

def get_properties(self, raw_schema: Mapping[str, Any]) -> Mapping[str, Any]:

54

"""

55

Extract properties from custom object schema.

56

57

Parameters:

58

- raw_schema: Raw schema definition from HubSpot API

59

60

Returns:

61

- Dictionary mapping property names to property definitions

62

"""

63

64

def generate_schema(self, properties: Mapping[str, Any]) -> Mapping[str, Any]:

65

"""

66

Generate JSON schema from custom properties.

67

68

Parameters:

69

- properties: Property definitions

70

71

Returns:

72

- JSON schema for the custom object

73

"""

74

```

75

76

### Custom Object Stream Generation

77

78

Methods in SourceHubspot for creating custom object stream instances.

79

80

```python { .api }

81

def get_custom_object_streams(self, api: API, common_params: Mapping[str, Any]) -> Generator[CustomObject, None, None]:

82

"""

83

Generate custom object stream instances.

84

85

Parameters:

86

- api: API client instance

87

- common_params: Common stream parameters

88

89

Yields:

90

- CustomObject stream instances for each discovered custom object

91

"""

92

```

93

94

## Usage Examples

95

96

### Discovering Custom Objects

97

98

```python

99

from source_hubspot.streams import API

100

from source_hubspot import SourceHubspot

101

102

# Setup API client

103

api = API(credentials)

104

105

# Discover custom objects

106

print("Discovering custom objects...")

107

for entity, fqn, schema, properties in api.get_custom_objects_metadata():

108

print(f"\nCustom Object: {entity}")

109

print(f"Fully Qualified Name: {fqn}")

110

print(f"Properties: {list(properties.keys())}")

111

print(f"Schema type: {schema.get('type', 'unknown')}")

112

113

# Show property details

114

for prop_name, prop_def in properties.items():

115

prop_type = prop_def.get('type', 'unknown')

116

print(f" - {prop_name}: {prop_type}")

117

```

118

119

### Working with Custom Object Streams

120

121

```python

122

from source_hubspot import SourceHubspot

123

124

# Create source with custom objects enabled

125

source = SourceHubspot(catalog=None, config=config, state=None)

126

127

# Get all streams including custom objects

128

all_streams = source.streams(config)

129

130

# Filter for custom object streams

131

custom_streams = []

132

for stream in all_streams:

133

if hasattr(stream, 'entity') and hasattr(stream, 'fully_qualified_name'):

134

custom_streams.append(stream)

135

136

print(f"Found {len(custom_streams)} custom object streams:")

137

for stream in custom_streams:

138

print(f" - {stream.name} ({stream.entity})")

139

```

140

141

### Reading Custom Object Data

142

143

```python

144

# Assuming we have a custom object stream for "Vehicle" objects

145

for stream in custom_streams:

146

if stream.entity == "2-123456": # Custom object ID example

147

print(f"\nReading data from custom object: {stream.name}")

148

149

record_count = 0

150

for record in stream.read_records(sync_mode="full_refresh"):

151

record_count += 1

152

properties = record.get('properties', {})

153

154

# Display first few records

155

if record_count <= 3:

156

print(f"Record {record_count}:")

157

for prop_name, prop_value in properties.items():

158

print(f" {prop_name}: {prop_value}")

159

160

print(f"Total records: {record_count}")

161

break

162

```

163

164

### Custom Object Schema Analysis

165

166

```python

167

# Analyze custom object schemas in detail

168

api = API(credentials)

169

170

for entity, fqn, schema, properties in api.get_custom_objects_metadata():

171

print(f"\n=== Custom Object: {entity} ===")

172

print(f"Fully Qualified Name: {fqn}")

173

174

# Analyze schema structure

175

if 'properties' in schema:

176

schema_props = schema['properties']

177

print(f"Schema properties: {len(schema_props)}")

178

179

# Categorize property types

180

type_counts = {}

181

for prop_name, prop_schema in schema_props.items():

182

prop_type = prop_schema.get('type', 'unknown')

183

if prop_type not in type_counts:

184

type_counts[prop_type] = 0

185

type_counts[prop_type] += 1

186

187

print("Property type distribution:")

188

for prop_type, count in sorted(type_counts.items()):

189

print(f" {prop_type}: {count}")

190

191

# Analyze custom properties

192

print(f"Custom properties: {len(properties)}")

193

required_props = []

194

optional_props = []

195

196

for prop_name, prop_def in properties.items():

197

if prop_def.get('required', False):

198

required_props.append(prop_name)

199

else:

200

optional_props.append(prop_name)

201

202

if required_props:

203

print(f"Required properties: {', '.join(required_props)}")

204

if optional_props:

205

print(f"Optional properties: {len(optional_props)} total")

206

```

207

208

### Custom Object Stream Configuration

209

210

```python

211

# Manually create a custom object stream

212

from source_hubspot.streams import CustomObject

213

214

# This would typically be done automatically by SourceHubspot

215

custom_stream = CustomObject(

216

entity="2-123456", # HubSpot custom object ID

217

schema={

218

"type": "object",

219

"properties": {

220

"vehicle_make": {"type": "string"},

221

"vehicle_model": {"type": "string"},

222

"year": {"type": "integer"},

223

"price": {"type": "number"}

224

}

225

},

226

fully_qualified_name="p123456_vehicle",

227

custom_properties={

228

"vehicle_make": {"type": "string", "label": "Vehicle Make"},

229

"vehicle_model": {"type": "string", "label": "Vehicle Model"},

230

"year": {"type": "integer", "label": "Year"},

231

"price": {"type": "number", "label": "Price"}

232

},

233

api=api,

234

start_date="2023-01-01T00:00:00Z",

235

credentials=credentials

236

)

237

238

# Use the custom stream

239

for record in custom_stream.read_records(sync_mode="full_refresh"):

240

properties = record['properties']

241

print(f"Vehicle: {properties.get('vehicle_make')} {properties.get('vehicle_model')} ({properties.get('year')})")

242

print(f"Price: ${properties.get('price', 0):,.2f}")

243

```

244

245

### Custom Object Web Analytics (Experimental)

246

247

```python

248

# If experimental streams are enabled, custom objects get web analytics streams too

249

config_with_experimental = {

250

**config,

251

"enable_experimental_streams": True

252

}

253

254

source = SourceHubspot(catalog=None, config=config_with_experimental, state=None)

255

streams = source.streams(config_with_experimental)

256

257

# Find web analytics streams for custom objects

258

custom_web_analytics = [

259

s for s in streams

260

if "WebAnalytics" in s.__class__.__name__ and hasattr(s, 'parent')

261

]

262

263

print(f"Found {len(custom_web_analytics)} custom object web analytics streams")

264

for stream in custom_web_analytics:

265

print(f" - {stream.name}")

266

```

267

268

## Custom Object Properties

269

270

### Property Types

271

272

HubSpot custom objects support various property types:

273

274

```python { .api }

275

# Common custom property types

276

CUSTOM_FIELD_TYPE_TO_VALUE = {

277

bool: "boolean",

278

str: "string",

279

float: "number",

280

int: "integer"

281

}

282

283

# Schema type conversions

284

KNOWN_CONVERTIBLE_SCHEMA_TYPES = {

285

"bool": ("boolean", None),

286

"enumeration": ("string", None),

287

"date": ("string", "date"),

288

"date-time": ("string", "date-time"),

289

"datetime": ("string", "date-time"),

290

"json": ("string", None),

291

"phone_number": ("string", None)

292

}

293

```

294

295

### Property Schema Structure

296

297

```python { .api }

298

# Example custom property definition

299

{

300

"name": "property_name",

301

"label": "Display Label",

302

"type": "string", # or "number", "boolean", "datetime", etc.

303

"fieldType": "text", # HubSpot field type

304

"required": False,

305

"options": [], # For enumeration types

306

"description": "Property description"

307

}

308

```

309

310

## OAuth Scopes

311

312

Custom object streams require specific OAuth scopes:

313

314

- **Default Custom Objects**: `crm.schemas.custom.read`, `crm.objects.custom.read`

315

- **Custom Object Creation**: `crm.schemas.custom.write`, `crm.objects.custom.write`

316

- **Custom Object Properties**: Additional schema read/write scopes may be required

317

318

The connector automatically detects available custom objects based on granted scopes and includes only those that are accessible to the authenticated user.

319

320

## Limitations

321

322

- Custom object availability depends on HubSpot plan and permissions

323

- Property types must be supported by HubSpot's custom object system

324

- Schema changes to custom objects may require connector reconfiguration

325

- Some advanced custom object features may not be available through the API