Reference → Custom Objects

Custom Objects

How SG-Core represents operator-defined record types.

Custom Objects in SG-Core is the engine-level system that lets operators add record types beyond the platform-default set. Where SG-Admin owns the operator-facing pillar, SG-Core owns the data shape and the integration hooks that make a custom type behave like a first-class record everywhere it appears — listings, edit surfaces, Pages/Posts authoring, public rendering.

Custom Objects — Engine

Inline reference mock
+ Add New
FieldValue
ModuleCustom Objects
Slugsg-core-custom-objects
SurfaceSg Core / Engine
NotesReplace with captured screenshot when available

What is this for?

Read this page when you need the engine-side definition: how a custom type is stored, what makes it eligible to behave like Pages or Posts, where the rendering hook fires, and what the relationship is to Custom Fields at the data layer.

Good use cases

  • You need to understand why a Custom Object type shows up in the Pages/Posts authoring picker.
  • You are scoping an integration that needs to read or write Custom Object records.
  • You are checking whether a planned Custom Object can be exposed publicly or stays internal.
  • You hit a question like "Why does my Custom Object render under /projects/<slug>/?"

What NOT to use this for

  • Operator how-to (creating types, setting fields, configuring public render) — that lives in SG-Admin Custom Objects.
  • Field-level reference (data types, validation rules) — that lives in Custom Fields.
  • Per-release shipped change — open the Changelog.

How this connects to other features

  • SG-Admin Custom Objects — the operator-facing pillar that creates and manages types.
  • Custom Fields — the schema layer attached to each type.
  • Pages & Posts — the default record types Custom Objects sits alongside.
  • SG-Builder — the page composer can list and detail Custom Object records via Posts blocks.

Type model

SG-Core treats every Custom Object type as a registered post-type. When an operator creates a Custom Object type in the admin, the engine assigns it a post-type identifier — typically __custom_object_<ID> where <ID> is the internal type identifier — and registers that string with every part of the engine that lists, filters, or renders post-types.

That post-type registration is what makes Custom Object records eligible everywhere a record-by-type picker exists. The Pages/Posts authoring view, the SG-Builder Posts component, the public router, the search index — they all read the same post-type registry.

Custom Objects — Registry

Inline reference mock
+ Add New
FieldValue
ModuleCustom Objects
Slugsg-core-custom-objects
SurfaceSg Core / Registry
NotesReplace with captured screenshot when available

A type at the engine level carries:

  • Type identifier — internal post-type string (__custom_object_<ID>).
  • Slug + label — operator-set name and URL slug.
  • Schema reference — pointer to the Custom Fields group attached to this type.
  • Public render flag — whether the engine should expose /<slug>/<record-slug>/ routes.
  • Template binding — optional per-type template assignment.

Persistence

Records of every Custom Object type are stored in the same tables the engine uses for Pages and Posts. The post-type column is the discriminator. The advantage of this shape: no per-type table is created, no per-type migration runs, and every cross-cutting feature (search, sitemap, revisions, scheduling) works on Custom Object records the same way it works on Pages.

Field values attached to each record are stored via the Custom Fields persistence layer. Each field belongs to a Custom Fields group; the group is bound to the Custom Object type; values are written per record.

Custom Objects — Storage

Inline reference mock
+ Add New
FieldValue
ModuleCustom Objects
Slugsg-core-custom-objects
SurfaceSg Core / Storage
NotesReplace with captured screenshot when available

Eligibility model

When SG-Core renders a record-type picker — for example the Pages authoring page-type dropdown, or the SG-Builder Posts component's source-type selector — it queries the post-type registry and shows every registered type. Custom Object types are returned alongside platform-default types (Pages, Posts, Products, Forms, Locations, Events) without any special-casing.

This means: anywhere you can pick a content type, your Custom Object types show up. Operator-side, that means a Project record can be the source of a featured-records block. Engine-side, that means there is one type registry, not two.


Public rendering

Each Custom Object type carries a public-render flag. When the flag is on, the engine listens for routes matching /<type-slug>/<record-slug>/. When a request hits that route, the engine looks up the matching record by post-type + slug, finds the bound template (per-type template if assigned, otherwise the type's archive/single-default), and renders.

When the flag is off, the engine still stores and edits the record but never produces a public URL. Internal-only types — operator notebooks, deal trackers, anything not meant for public view — use this state.

Custom Objects — Router

Inline reference mock
+ Add New
FieldValue
ModuleCustom Objects
Slugsg-core-custom-objects
SurfaceSg Core / Router
NotesReplace with captured screenshot when available

Render boundary

The router only mounts public routes for types where the flag is on. There is no "accidentally public" mode — a Custom Object type that hasn't been opted into public render will return 404 on any guessed URL.


Relationship with Custom Fields

Custom Fields is the schema mechanism Custom Objects uses to define structure. The relationship is one Custom Object type → one Custom Fields group → many fields per record.

When an operator adds a field to the group, every record of that type immediately carries the new field (empty until filled). When an operator removes a field, existing values are preserved at the storage layer but no longer surface in the edit UI or render template by default.

The schema lives next to the records, not separately. That means a backup of the site captures both: type definition + record values + field schema, all in one operation.


Integration points

The engine exposes Custom Object records through the same integration surfaces that work for Pages and Posts. That includes:

  • Search index — Custom Object records are indexed and returned in site search by default; opt-out is per-type.
  • Sitemap — public-render-on types with public-render-on records appear in the XML sitemap.
  • Revisions — every save creates a revision, same as Pages.
  • Scheduling + status — draft / published / private / scheduled status moves work identically.
  • Bulk-action surfaces — list-view bulk operations apply.

The shared-pipe design means most things "work" without per-feature integration. The exceptions are areas that need explicit per-type opt-in (typically render templates, sitemap inclusion rules).


Constraints and boundaries

Custom Objects in SG-Core is the engine layer. The operator pillar is in the admin.

What this engine layer does

  • Registers post-types in a single registry consumed everywhere.
  • Stores records in the same tables as Pages/Posts.
  • Routes public requests for opted-in types.
  • Indexes records in search.
  • Hooks into revisions, status, scheduling.

What this engine layer does not do

  • Define schemas — that is Custom Fields.
  • Render — that is the template + theme system.
  • Expose REST endpoints by default — integrations that need API access live behind explicit, per-type configuration.

Public boundary

The engine layer is publicly documented at this depth (post-type registry shape, storage discriminator, route mount semantics). Lower-level details (table column names, query plans, internal IDs) are not published.


Examples

Example 1 — Project type with public render

An agency adds a Project Custom Object type. Schema includes client name, hero image, dates, results summary. Public render is on; route mount is /projects/<slug>/. The agency creates Project records; each gets its own public URL; the Pages/Posts source-type picker shows Project alongside Pages; an SG-Builder Posts block on the homepage lists the latest 6 Projects with their hero images.

Example 2 — Internal-only Notes type

A marketing team adds a Field Note Custom Object type. Schema includes title, related campaign, observation, follow-up date. Public render is off. Records exist for team review; no public URL is produced; the type is excluded from the sitemap. The team uses the list view to filter by campaign and follow-up date.

Example 3 — Cross-feature reuse

The same Case Study Custom Object type is featured on the homepage (SG-Builder Posts block, latest 3), surfaced under /case-studies/<slug>/ (public render), included in site search, indexed in the sitemap, and shows up in the Pages/Posts source-type picker for the marketing site's resource hub. The type is registered once; every feature reads from the same registry.


Documentation guidance

This page is the engine-side reference. For operator how-to (creating, configuring, managing types) open SG-Admin Custom Objects. For field schema definition open Custom Fields.


Reading order

Read this page when you need to reason about how a Custom Object type behaves once registered — what surfaces it appears on, where its records live, when public routes mount, how it shares pipes with Pages and Posts. Pair with the admin Custom Objects pillar for the operator-side flow that creates and configures types.


Related reading


Vocabulary cross-reference

  • Post-type registry — the engine-wide table of registered record types. Custom Object types live here next to Pages, Posts, Products.
  • Type identifier — internal post-type string (__custom_object_<ID>).
  • Public render flag — per-type setting that controls whether /<slug>/<record-slug>/ routes are mounted.
  • Schema group — the Custom Fields group bound to a Custom Object type.
  • Record discriminator — the post-type column in the storage tables; what tells Pages records apart from Project records.

Design rationale

The post-type system at the heart of SG-Core's record model is what makes Custom Objects feel native instead of bolted-on. By storing every record class — Pages, Posts, Products, and every operator-defined type — in the same tables with a discriminator column, the engine gets one set of integration hooks for the price of zero. Pages already had revisions; Custom Objects get revisions. Pages already had scheduling; Custom Objects get scheduling. The cross-cutting features that took years to harden on Pages now apply to whatever record type an operator decides to add today.

The schema persistence layer follows the same logic. Field values for every record type — default or operator-defined — flow through the Custom Fields storage path. There is no second writer. Backups capture the full picture in one operation: type registration, schema definition, field values, public-render flags. Restore puts the site back exactly the way it was, including types that didn't exist when the platform shipped.

This shared-pipe choice is also why integrations rarely need per-type code. A reporting integration that reads recently-updated records reads from one query against one table; the post-type column tells it which records belong to which type. The integration doesn't have to know what types the operator created — it reads what's there.

Custom Objects — Pipes

Inline reference mock
+ Add New
FieldValue
ModuleCustom Objects
Slugsg-core-custom-objects
SurfaceSg Core / Pipes
NotesReplace with captured screenshot when available

Trade-offs

Storing every record class in one table means schema changes need to be careful — a column added for one type sees every type. SG-Core handles per-type fields through the schema persistence layer (Custom Fields values stored in the field-value tables, keyed by record + field), not by adding columns. New record-class capability that would need a new column is a platform-engineering decision, not an operator-level setting.

The same shared-pipe choice means cross-cutting features that don't make sense for every type need explicit opt-out. Sitemap inclusion, search indexing, and revisions all default on; the per-type configuration to opt out of any of them is operator-controlled.


Querying records across types

Because every record class shares one storage shape, queries that span types are first-class. A homepage that wants "the most recent thing we shipped, regardless of whether it was a Page, a Post, or a Project record" is one ordered query against one table, filtered by published status, ignoring the post-type column.

Queries that target one type are equally cheap — the post-type column is indexed; filtering by __custom_object_<ID> is the same shape as filtering by page or post. There is no separate join, no per-type table to consult.

Custom Objects — Shape

Inline reference mock
+ Add New
FieldValue
ModuleCustom Objects
Slugsg-core-custom-objects
SurfaceSg Core / Shape
NotesReplace with captured screenshot when available

For surfaces that mix types — a global search result, a unified activity feed, a dashboard "recent edits" panel — the engine returns records of every type the user has access to and lets the surface decide how to render. Custom Object records carry their type identifier in the result row; the surface can fall back to the default record renderer or branch to a per-type renderer.

Comparison with Pages and Posts

The single biggest question this page answers: in what way is a Custom Object different from a Page or a Post? Engine-side, the answer is "barely." All three are post-types in the same registry. All three store records in the same tables. All three flow through the same revisions, scheduling, search, and sitemap pipes. The differences are operator-facing.

A Page is a default post-type the platform ships. Its operator UI is tuned for landing-page composition: the editor opens directly into SG-Builder, the listing surfaces page-shaped concerns (template, parent, status), and the sitemap default is on. A Page exists because every site needs a "main pages" affordance.

A Post is the same engine-side, with operator-UI biased toward chronological / categorized content: list view sorts by date, archive surfaces (/blog/, /blog/category/...) are configured by default, RSS feeds emit Posts. A Post exists because most sites have time-series content.

A Custom Object is the operator-defined variant. The post-type registration is the same. The persistence is the same. The integration coverage is the same. The difference is that the operator chose its name, its slug, its schema, its public-render setting, and its template binding — none of those are platform defaults.

So when reasoning about behavior at the engine layer, anything true about Pages or Posts is true about Custom Objects unless explicitly carved out. The carve-outs are small:

  • Posts get RSS feed emission by default; Pages and Custom Objects don't.
  • Pages get the parent / child hierarchy affordance; Posts and Custom Objects don't.
  • Custom Objects get the operator-defined name, slug, schema, render flag, and template binding; Pages and Posts get platform defaults for those.

Beyond that handful of carve-outs, the engine treats all three the same.

Common patterns

A handful of Custom Object configurations show up often enough to be worth naming.

Public catalog pattern — public-render on, per-type template assigned, listing surface bound to an SG-Builder Posts block on a hub page. Used for things like Projects, Case Studies, Properties, Members. The shape: each record is a public detail page; one or more hub pages list the recent records or filter them by Custom Field values.

Internal registry pattern — public-render off, schema captures the operator-side data model, list view is the only surface. Used for things like Field Notes, Internal Tickets, Hand-off Logs. The shape: editor-only access, no public URL, no sitemap inclusion, search opt-out per-type.

Hybrid pattern — public-render on but each record carries an explicit published toggle inside the schema, and the public template only surfaces records with the toggle on. Used when most records should stay internal but a curated subset goes public — Press Mentions, Speaker Bios, Sponsor Profiles. The shape: type is public-eligible, individual records opt in.

Companion pattern — Custom Object type holds reference data that other content joins to. Schema captures the canonical record (e.g. an Author, a Brand, a Region); other Pages/Posts reference it by ID via a Custom Field of relation type. Used for things like editorial systems with shared author records or storefronts with shared region/currency lookup. The shape: the Custom Object is the source-of-truth row; the joining content carries the foreign key.

Custom Objects — status reference

Each pattern is a configuration, not a code path. The engine has no notion of "public catalog vs internal registry" — those are descriptions of how operators use the same primitives.

Edge behaviors

A handful of behaviors are worth surfacing because they answer recurring "what happens if..." questions.

What happens if a type is renamed mid-flight? The post-type identifier stays. Records keep their type binding. Public URLs change only if the slug changes; rename without slug change is invisible to public visitors.

What happens if a Custom Field group bound to a type is deleted? The records keep their stored field values; the edit UI loses the field surfaces. Restore the group within the trash retention window to recover the editing path; the values were never lost at the storage layer.

What happens if the public-render flag is flipped off after records are published? The router stops mounting the routes. Existing inbound links return 404. Search index entries persist until the next reindex. Sitemap entries disappear on next regeneration.

What happens if two operators edit the same Custom Object record at the same time? Standard last-write-wins applies, with the revisions log preserving the prior state. The revision view lets either operator restore.

Migration considerations

When a site is migrated into SGEN from another platform, Custom Object types that match an incoming content shape can be created up front and the imported records routed straight into them. The post-type registry is set before the import runs; each incoming record is assigned the matching post-type identifier and lands in the right type from the first save.

When a site is migrated out of SGEN, every Custom Object type and its records export through the same backup pipeline that handles Pages and Posts. The backup includes type registration, schema groups, field values, public-render flags, and template bindings. Restoring into a different SGEN site recreates the types with the same identifiers; restoring into a different platform requires the receiver to interpret the post-type column as the discriminator for record class.

Slug uniqueness

Slugs are how the public router resolves a request to a record. Within a single Custom Object type, record slugs are unique. Two Project records cannot share the same slug. Across types, slugs can repeat — a Project named recipe-of-the-week and a Recipe with the same slug coexist without colliding because the public route prefix differs (/projects/recipe-of-the-week/ vs /recipes/recipe-of-the-week/).

When a slug collision happens within a type — typically because the operator imports records from elsewhere and two carry the same source slug — the engine appends a numeric suffix to the second record on save. The operator can rename either record afterwards; renames go through the same public-URL change semantics covered in Operational notes.

Slug normalization runs on save: spaces become hyphens, casing folds to lowercase, characters outside the URL-safe set are stripped or transliterated. The normalized form is what gets stored and what the router matches against.

The slug-rewrite affordances at the platform level (Redirects pillar, bulk slug audit) apply to Custom Object records the same way they apply to Pages. There is no per-type slug-management surface; the existing tools cover it.

Per-type templates

A Custom Object type can carry an optional template binding. When set, the engine uses that template for the public-render path of records of that type. When not set, the engine falls back to the default record-render template (typically the same one Pages uses).

Template bindings are an SG-Builder concern — the operator picks a template inside SG-Builder when they configure the type's public render. The engine's role is only to honor the binding at render time. There is no separate template registry; templates are SG-Builder pages with a Template flag set, and any such page can be assigned to any type.

Indexing + search

Records of every Custom Object type are added to the site search index by default. The indexer reads each record's title, body content, and indexed Custom Field values, then writes the entry to the search store. Operators can opt a type out of search per-type if a Custom Object type holds private internal records that should never surface publicly.

The same default-on / per-type-opt-out shape applies to the XML sitemap, the RSS feeds (where applicable), and any future cross-cutting feed. The pattern is intentional: new Custom Object types start integrated, not isolated, and operators reduce coverage where they need to.


Operational notes

When the post-type system registers a new Custom Object type, the change is live for the engine immediately — there is no rebuild step, no cache flush an operator needs to trigger. Surfaces that read the registry on each request (the source-type pickers, the public router) pick up the new type on the next page load.

A handful of read paths cache the registry for performance — search index, sitemap. Those paths refresh on their own schedule (search index typically inside an hour; sitemap on next regeneration). The lag is intentional: those surfaces don't need millisecond-fresh registry reads, and the cache is what keeps them fast.

When a Custom Object type is renamed, the post-type identifier stays the same; only the operator-facing label and slug change. Existing records continue to belong to the same type. Public URLs change if the slug changes; SGEN does not auto-create redirects from the old slug — that is an operator decision and lives under the Redirects pillar.

When a Custom Object type is deleted, every record of that type is moved to trash, not hard-deleted. The trash retention applies; restoring the type within the retention window restores the records and their schema bindings together.


Maintenance discipline

Update this Reference when:

  • The engine adds new integration points Custom Objects participates in (e.g. a new bulk-action surface, a new search facet).
  • The persistence model shifts (e.g. table consolidation, schema-storage refactor).
  • The public-render hook moves (e.g. router rewrite changes the mount semantics).
  • Eligibility behavior changes anywhere (e.g. a new picker that filters out Custom Object types — would need to be documented as an exception).

Operator-side changes (new SG-Admin UI, new field types, new public-render UI affordances) belong in SG-Admin Custom Objects, not here.

Scope

This Reference covers the platform-level shape of custom objects: what the surface is responsible for, how it relates to neighboring surfaces, and the structural boundaries that hold across releases. Operator how-to and per-release change land on the linked operator-facing or changelog surfaces, not here.

Where to find it

Open your SG-Admin and navigate via the sidebar group that owns this surface. For platform-level reference (this page), the entry point is the SGEN documentation index at docs.sgen.com. For the operator-facing configuration screen, the entry point is the corresponding SG-Admin module page linked in Related features above.

On this page